@joshuafolkken/kit 1.1784.0 → 1.2116.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 +60 -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 +386 -360
- 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 +16 -9
- 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,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { package_path } from '#scripts/init/init-paths'
|
|
3
|
+
import { markdown_section } from './markdown-section'
|
|
4
|
+
|
|
5
|
+
// One section of the Issue body template, cut from the fenced template in `issue-template.md` — its
|
|
6
|
+
// single source — so a refusal for a missing heading can print the section to paste back in rather than
|
|
7
|
+
// sending the filer to reopen the document (joshuafolkken/kit#5206).
|
|
8
|
+
const TEMPLATE_PATH = 'prompts/collaboration-workflow/issue-template.md'
|
|
9
|
+
const TEMPLATE_FENCE = '````'
|
|
10
|
+
|
|
11
|
+
// The lines inside the document's four-backtick template fence; an empty array when it has none.
|
|
12
|
+
function template_lines(document: string): ReadonlyArray<string> {
|
|
13
|
+
const lines = markdown_section.lines_of(document)
|
|
14
|
+
const start = lines.findIndex((line) => line.startsWith(TEMPLATE_FENCE))
|
|
15
|
+
const end = lines.findIndex((line, index) => index > start && line.trim() === TEMPLATE_FENCE)
|
|
16
|
+
|
|
17
|
+
if (start === -1 || end === -1) return []
|
|
18
|
+
|
|
19
|
+
return lines.slice(start + 1, end)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The heading and its template lines, trailing blank lines dropped; `undefined` when the template has no
|
|
23
|
+
// such section.
|
|
24
|
+
function section_of(document: string, heading: string): string | undefined {
|
|
25
|
+
const template = template_lines(document).join('\n')
|
|
26
|
+
|
|
27
|
+
if (!markdown_section.has_line(template, heading)) return undefined
|
|
28
|
+
|
|
29
|
+
return [heading, ...markdown_section.section_lines(template, heading)].join('\n').trimEnd()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A package without the template prints the bare problem rather than failing the lint.
|
|
33
|
+
function section(heading: string): string | undefined {
|
|
34
|
+
const template_path = package_path(TEMPLATE_PATH)
|
|
35
|
+
|
|
36
|
+
if (!existsSync(template_path)) return undefined
|
|
37
|
+
|
|
38
|
+
return section_of(readFileSync(template_path, 'utf8'), heading)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const issue_template_section = { section, section_of }
|
|
42
|
+
|
|
43
|
+
export { issue_template_section }
|
|
@@ -21,6 +21,12 @@ import { z } from 'zod'
|
|
|
21
21
|
// body withheld, costing one `josh issue:ack` rather than a missed correction, and its marker is not
|
|
22
22
|
// trusted.
|
|
23
23
|
//
|
|
24
|
+
// **A body edit is a person's update only when the issue's author has write access.** GitHub does not
|
|
25
|
+
// say who edited a body, and its author can always edit it, so a body under an author without write
|
|
26
|
+
// access is never handed to a run as an update: its edit becomes a notice with the text withheld,
|
|
27
|
+
// which asks for nothing and holds no merge. A `MEMBER` / `COLLABORATOR` author whose lookup failed
|
|
28
|
+
// is treated like their comment: the edit is announced with its text withheld and stays pending.
|
|
29
|
+
//
|
|
24
30
|
// **Comment edits are out of scope.** A new comment carries an id above the baseline's; an edited
|
|
25
31
|
// one keeps its id and is not seen. The body is compared whole, so its edits are.
|
|
26
32
|
|
|
@@ -33,6 +39,8 @@ const UNKNOWN_TIME = '(undated)'
|
|
|
33
39
|
const UNVERIFIED_BODY =
|
|
34
40
|
"(body withheld — the author's write access could not be checked; read it with `josh issue:read`)"
|
|
35
41
|
|
|
42
|
+
const WITHHELD_BODY_EDIT = `The body of this run's issue was edited, but its author is not ${gh_author_association.TRUSTED_LIST}, so the edit is withheld: it is not an instruction to this run and holds nothing — carry on.`
|
|
43
|
+
|
|
36
44
|
const BODY_HEADING = 'Body edited:'
|
|
37
45
|
const REMOVED_PREFIX = '- '
|
|
38
46
|
const ADDED_PREFIX = '+ '
|
|
@@ -62,6 +70,8 @@ interface WatchComment {
|
|
|
62
70
|
|
|
63
71
|
interface WatchSnapshot {
|
|
64
72
|
body: string
|
|
73
|
+
// Whether the issue's author has write access — `undefined` when the lookup failed.
|
|
74
|
+
is_body_trusted: boolean | undefined
|
|
65
75
|
comments: ReadonlyArray<WatchComment>
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -171,9 +181,20 @@ function new_person_comments(
|
|
|
171
181
|
)
|
|
172
182
|
}
|
|
173
183
|
|
|
184
|
+
function is_body_edited(baseline: WatchBaseline, snapshot: WatchSnapshot): boolean {
|
|
185
|
+
return to_lf(baseline.body) !== to_lf(snapshot.body)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function unread_body(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
|
|
189
|
+
if (snapshot.is_body_trusted === true) return body_section(baseline.body, snapshot.body)
|
|
190
|
+
if (snapshot.is_body_trusted === false || !is_body_edited(baseline, snapshot)) return undefined
|
|
191
|
+
|
|
192
|
+
return `${BODY_HEADING}${LINE_SEPARATOR}${UNVERIFIED_BODY}`
|
|
193
|
+
}
|
|
194
|
+
|
|
174
195
|
// The unread update as the text a run is shown, or `undefined` when a person changed nothing.
|
|
175
196
|
function unread_diff(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
|
|
176
|
-
const body =
|
|
197
|
+
const body = unread_body(baseline, snapshot)
|
|
177
198
|
const comments = new_person_comments(baseline, snapshot).map((comment) =>
|
|
178
199
|
comment_section(comment),
|
|
179
200
|
)
|
|
@@ -182,11 +203,20 @@ function unread_diff(baseline: WatchBaseline, snapshot: WatchSnapshot): string |
|
|
|
182
203
|
return sections.length === 0 ? undefined : sections.join(SECTION_SEPARATOR)
|
|
183
204
|
}
|
|
184
205
|
|
|
206
|
+
// The notice for a body edit `unread_diff` withholds, or `undefined` when there is none.
|
|
207
|
+
function withheld_notice(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
|
|
208
|
+
if (snapshot.is_body_trusted !== false || !is_body_edited(baseline, snapshot)) return undefined
|
|
209
|
+
|
|
210
|
+
return WITHHELD_BODY_EDIT
|
|
211
|
+
}
|
|
212
|
+
|
|
185
213
|
const issue_watch_diff = {
|
|
186
214
|
RUN_MARKER,
|
|
215
|
+
WITHHELD_BODY_EDIT,
|
|
187
216
|
baseline_of,
|
|
188
217
|
parse_comments,
|
|
189
218
|
unread_diff,
|
|
219
|
+
withheld_notice,
|
|
190
220
|
}
|
|
191
221
|
|
|
192
222
|
export type { WatchBaseline, WatchComment, WatchSnapshot }
|
|
@@ -22,7 +22,16 @@ const POLL_INTERVAL_MS = 180_000
|
|
|
22
22
|
const POLL_TIMEOUT_MS = 5000
|
|
23
23
|
|
|
24
24
|
function is_new_to_run(record: WatchRecord | undefined): record is WatchRecord {
|
|
25
|
-
|
|
25
|
+
const news = record === undefined ? undefined : issue_watch.news_of(record)
|
|
26
|
+
|
|
27
|
+
return news !== undefined && news !== record?.shown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A withheld body edit is shown as its own notice: it asks for no `josh issue:ack`.
|
|
31
|
+
function message_of(record: WatchRecord): string {
|
|
32
|
+
return record.pending === undefined
|
|
33
|
+
? (record.notice ?? '')
|
|
34
|
+
: issue_watch_gate.unread_message(record)
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
async function new_update_reason(now: Date): Promise<string | undefined> {
|
|
@@ -36,7 +45,7 @@ async function new_update_reason(now: Date): Promise<string | undefined> {
|
|
|
36
45
|
|
|
37
46
|
issue_watch.mark_shown(record)
|
|
38
47
|
|
|
39
|
-
return
|
|
48
|
+
return message_of(record)
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
async function watch_reason(now: Date = new Date()): Promise<string | undefined> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { gh_author_association } from '#scripts/gh/gh-author-association'
|
|
1
2
|
import { git_gh_api_path } from '#scripts/gh/git-gh-api-path'
|
|
2
3
|
import { git_gh_conditional } from '#scripts/gh/git-gh-conditional'
|
|
3
4
|
import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
|
|
@@ -30,10 +31,18 @@ const record_schema = z.object({
|
|
|
30
31
|
checked_at: z.string(),
|
|
31
32
|
etag: z.string().optional(),
|
|
32
33
|
pending: z.string().optional(),
|
|
34
|
+
// A body edit withheld from the run (`issue_watch_diff.withheld_notice`) — shown, never a hold.
|
|
35
|
+
notice: z.string().optional(),
|
|
33
36
|
shown: z.string().optional(),
|
|
34
37
|
})
|
|
35
38
|
|
|
36
|
-
const issue_schema = z.looseObject({
|
|
39
|
+
const issue_schema = z.looseObject({
|
|
40
|
+
body: z.string().nullish(),
|
|
41
|
+
state: z.string(),
|
|
42
|
+
...gh_author_association.author_fields_schema,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
type IssueRead = z.infer<typeof issue_schema>
|
|
37
46
|
|
|
38
47
|
type WatchRecord = z.infer<typeof record_schema>
|
|
39
48
|
|
|
@@ -71,17 +80,28 @@ interface FetchRequest {
|
|
|
71
80
|
timeout_ms?: number | undefined
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
async function
|
|
83
|
+
async function read_comments(
|
|
75
84
|
request: FetchRequest,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
): Promise<Fetched> {
|
|
79
|
-
const comments = await issue_watch_diff.parse_comments(
|
|
85
|
+
): Promise<WatchSnapshot['comments'] | undefined> {
|
|
86
|
+
return await issue_watch_diff.parse_comments(
|
|
80
87
|
await git_gh_issue_read.issue_list_comments(request.issue, request.timeout_ms),
|
|
81
88
|
request.timeout_ms,
|
|
82
89
|
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function read_snapshot(
|
|
93
|
+
request: FetchRequest,
|
|
94
|
+
issue: IssueRead,
|
|
95
|
+
etag: string | undefined,
|
|
96
|
+
): Promise<Fetched> {
|
|
97
|
+
const [comments, is_body_trusted] = await Promise.all([
|
|
98
|
+
read_comments(request),
|
|
99
|
+
gh_author_association.trust_of(gh_author_association.author_of(issue, request.timeout_ms)),
|
|
100
|
+
])
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
if (comments === undefined) return UNREADABLE
|
|
103
|
+
|
|
104
|
+
return { kind: 'read', snapshot: { body: issue.body ?? '', is_body_trusted, comments }, etag }
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
async function fetch_issue(request: FetchRequest): Promise<Fetched> {
|
|
@@ -98,7 +118,7 @@ async function fetch_issue(request: FetchRequest): Promise<Fetched> {
|
|
|
98
118
|
if (fields === undefined) return UNREADABLE
|
|
99
119
|
if (fields.state === CLOSED_STATE) return { kind: 'closed' }
|
|
100
120
|
|
|
101
|
-
return await read_snapshot(request, fields
|
|
121
|
+
return await read_snapshot(request, fields, read.etag)
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
// `acknowledged_at` is when a person's update was last taken in — the baseline's own read, or an
|
|
@@ -115,6 +135,20 @@ function fresh_record(
|
|
|
115
135
|
return { issue, ...baseline, acknowledged_at, checked_at, etag: fetched.etag }
|
|
116
136
|
}
|
|
117
137
|
|
|
138
|
+
// The previous `acknowledged_at` when a re-read of the same issue takes nothing in, so a `josh
|
|
139
|
+
// run:entry` re-run keeps a review briefed before it valid; `undefined` (now) otherwise.
|
|
140
|
+
function kept_acknowledgement(
|
|
141
|
+
previous: WatchRecord | undefined,
|
|
142
|
+
issue: string,
|
|
143
|
+
fetched: Extract<Fetched, { kind: 'read' }>,
|
|
144
|
+
): string | undefined {
|
|
145
|
+
if (previous?.issue !== issue) return undefined
|
|
146
|
+
|
|
147
|
+
const diff = issue_watch_diff.unread_diff(previous, fetched.snapshot)
|
|
148
|
+
|
|
149
|
+
return diff === undefined ? previous.acknowledged_at : undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
118
152
|
// Called by `josh run:entry` once it has printed the issue. `false` when the issue could not be read,
|
|
119
153
|
// which leaves no record — a watch with no baseline would call the whole issue unread.
|
|
120
154
|
async function record_baseline(
|
|
@@ -126,7 +160,9 @@ async function record_baseline(
|
|
|
126
160
|
|
|
127
161
|
if (fetched.kind !== 'read') return false
|
|
128
162
|
|
|
129
|
-
|
|
163
|
+
const acknowledged_at = kept_acknowledgement(read_record(root), issue, fetched)
|
|
164
|
+
|
|
165
|
+
write_record(fresh_record(issue, fetched, now, acknowledged_at), root)
|
|
130
166
|
|
|
131
167
|
return true
|
|
132
168
|
}
|
|
@@ -145,8 +181,9 @@ function with_fetched(record: WatchRecord, fetched: Fetched, checked_at: string)
|
|
|
145
181
|
if (fetched.kind !== 'read') return { ...record, checked_at }
|
|
146
182
|
|
|
147
183
|
const pending = issue_watch_diff.unread_diff(record, fetched.snapshot)
|
|
184
|
+
const notice = issue_watch_diff.withheld_notice(record, fetched.snapshot)
|
|
148
185
|
|
|
149
|
-
return { ...record, checked_at, etag: fetched.etag, pending }
|
|
186
|
+
return { ...record, checked_at, etag: fetched.etag, pending, notice }
|
|
150
187
|
}
|
|
151
188
|
|
|
152
189
|
interface PollOptions {
|
|
@@ -191,9 +228,14 @@ async function poll(options: PollOptions, root?: string): Promise<WatchRecord |
|
|
|
191
228
|
return await refresh(record, options, now, root)
|
|
192
229
|
}
|
|
193
230
|
|
|
194
|
-
//
|
|
231
|
+
// What the hook shows: a pending update, else a withheld edit's notice.
|
|
232
|
+
function news_of(record: WatchRecord): string | undefined {
|
|
233
|
+
return record.pending ?? record.notice
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Marks the news shown, so the hook stops once per update rather than on every call.
|
|
195
237
|
function mark_shown(record: WatchRecord, root?: string): WatchRecord {
|
|
196
|
-
return write_record({ ...record, shown: record
|
|
238
|
+
return write_record({ ...record, shown: news_of(record) }, root)
|
|
197
239
|
}
|
|
198
240
|
|
|
199
241
|
interface Acknowledged {
|
|
@@ -230,6 +272,7 @@ const issue_watch = {
|
|
|
230
272
|
clear,
|
|
231
273
|
clear_here,
|
|
232
274
|
mark_shown,
|
|
275
|
+
news_of,
|
|
233
276
|
poll,
|
|
234
277
|
read_record,
|
|
235
278
|
record_baseline,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { markdown_section } from './markdown-section'
|
|
3
|
+
|
|
4
|
+
// joshuafolkken/kit#4621: a follow-up measurement ("measure K merges after #N") took a lane for 20–40
|
|
5
|
+
// minutes to run one command and compare its value with a bound — work `backlog:drive` can do itself.
|
|
6
|
+
// The body declares that command and the bound once, under the background heading, so the drive runs
|
|
7
|
+
// it when the issue comes due and closes it on a pass; only a miss or an unreadable value takes a lane.
|
|
8
|
+
//
|
|
9
|
+
// **The value is the command's last non-empty output line, read as one number.** Anything else — a
|
|
10
|
+
// unit, a sentence, a failure — is unreadable, so a person never relies on how a free-text line parses.
|
|
11
|
+
|
|
12
|
+
const BACKGROUND_HEADING = '## 背景'
|
|
13
|
+
const PREFIX = '- 計測宣言:'
|
|
14
|
+
// joshuafolkken/kit#5144: a follow-up whose value no one command prints (an Actions record, an external
|
|
15
|
+
// service) says why under the same heading instead, so it files without a declaration.
|
|
16
|
+
const ABSENT_PREFIX = '- 計測宣言なし:'
|
|
17
|
+
const ABSENT = `${ABSENT_PREFIX} <理由>`
|
|
18
|
+
const DECLARATION = `${PREFIX} \`<command>\` <=|<|>=|> <number>`
|
|
19
|
+
const VALUE_PATTERN =
|
|
20
|
+
/^`(?<command>[^`\s][^`]*)`\s*(?<operator><=|>=|<|>)\s*(?<threshold>-?\d+(?:\.\d+)?)$/u
|
|
21
|
+
const NUMBER_PATTERN = /^-?\d+(?:\.\d+)?$/u
|
|
22
|
+
// The refusal ends in a line ready to paste (joshuafolkken/kit#5225).
|
|
23
|
+
const EXAMPLE = `${PREFIX} \`pnpm josh lane:net 5168\` <= 40`
|
|
24
|
+
const MALFORMED = `\`${PREFIX}\` must be written ${DECLARATION}\n${EXAMPLE}`
|
|
25
|
+
const declaration_schema = z.object({
|
|
26
|
+
command: z.string().trim(),
|
|
27
|
+
operator: z.enum(['<=', '<', '>=', '>']),
|
|
28
|
+
threshold: z.coerce.number(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
type MeasureDeclaration = z.infer<typeof declaration_schema>
|
|
32
|
+
type Operator = MeasureDeclaration['operator']
|
|
33
|
+
type MeasureVerdict = { kind: 'pass' | 'fail'; value: number } | { kind: 'unreadable' }
|
|
34
|
+
|
|
35
|
+
const COMPARE: Readonly<Record<Operator, (value: number, threshold: number) => boolean>> = {
|
|
36
|
+
'<=': (value, threshold) => value <= threshold,
|
|
37
|
+
'<': (value, threshold) => value < threshold,
|
|
38
|
+
'>=': (value, threshold) => value >= threshold,
|
|
39
|
+
'>': (value, threshold) => value > threshold,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The value after `prefix` under the background heading, or `undefined` when the body has no such line.
|
|
43
|
+
function background_value(body: string, prefix: string): string | undefined {
|
|
44
|
+
const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
|
|
45
|
+
const line = markdown_section
|
|
46
|
+
.unfenced_lines(background)
|
|
47
|
+
.map((text) => text.trim())
|
|
48
|
+
.find((text) => text.startsWith(prefix))
|
|
49
|
+
|
|
50
|
+
return line?.slice(prefix.length).trim()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function declared_value(body: string): string | undefined {
|
|
54
|
+
return background_value(body, PREFIX)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Whether the body writes a declaration line — a malformed one is `problems`' refusal — or says why it
|
|
58
|
+
// has none; an empty reason says nothing.
|
|
59
|
+
function is_stated(body: string): boolean {
|
|
60
|
+
if (declared_value(body) !== undefined) return true
|
|
61
|
+
|
|
62
|
+
return (background_value(body, ABSENT_PREFIX) ?? '').length > 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parsed(value: string): MeasureDeclaration | undefined {
|
|
66
|
+
const read = declaration_schema.safeParse(VALUE_PATTERN.exec(value)?.groups)
|
|
67
|
+
|
|
68
|
+
return read.success ? read.data : undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The declaration, or `undefined` when there is none or it does not parse — `issue:file` refuses a
|
|
72
|
+
// malformed one, and the drive hands an issue it cannot read to a lane as before.
|
|
73
|
+
function parse(body: string): MeasureDeclaration | undefined {
|
|
74
|
+
const value = declared_value(body)
|
|
75
|
+
|
|
76
|
+
return value === undefined ? undefined : parsed(value)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function problems(body: string): ReadonlyArray<string> {
|
|
80
|
+
const value = declared_value(body)
|
|
81
|
+
|
|
82
|
+
return value === undefined || parsed(value) !== undefined ? [] : [MALFORMED]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function value_of(output: string): number | undefined {
|
|
86
|
+
const last = output
|
|
87
|
+
.split('\n')
|
|
88
|
+
.map((line) => line.trim())
|
|
89
|
+
.findLast((line) => line.length > 0)
|
|
90
|
+
|
|
91
|
+
return last !== undefined && NUMBER_PATTERN.test(last) ? Number(last) : undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function judge(declaration: MeasureDeclaration, output: string): MeasureVerdict {
|
|
95
|
+
const value = value_of(output)
|
|
96
|
+
|
|
97
|
+
if (value === undefined) return { kind: 'unreadable' }
|
|
98
|
+
|
|
99
|
+
const is_pass = COMPARE[declaration.operator](value, declaration.threshold)
|
|
100
|
+
|
|
101
|
+
return { kind: is_pass ? 'pass' : 'fail', value }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function format(declaration: MeasureDeclaration): string {
|
|
105
|
+
return `\`${declaration.command}\` ${declaration.operator} ${String(declaration.threshold)}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const measure_declaration = {
|
|
109
|
+
ABSENT,
|
|
110
|
+
DECLARATION,
|
|
111
|
+
EXAMPLE,
|
|
112
|
+
PREFIX,
|
|
113
|
+
format,
|
|
114
|
+
is_stated,
|
|
115
|
+
judge,
|
|
116
|
+
parse,
|
|
117
|
+
problems,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type { MeasureDeclaration, MeasureVerdict }
|
|
121
|
+
export { measure_declaration }
|
|
@@ -1,65 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
import { execSync } from 'node:child_process'
|
|
3
2
|
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { gh_author_association } from '#scripts/gh/gh-author-association'
|
|
5
|
-
import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
|
|
6
|
-
import { parse_json_object_or_undefined } from '#scripts/git/parse-json-array'
|
|
7
3
|
import { issue_number_shape } from '#scripts/issue/issue-number-shape'
|
|
8
|
-
import { error_text } from '#scripts/lib/error-message'
|
|
9
4
|
import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
|
|
10
|
-
import { z } from 'zod'
|
|
11
5
|
import { baseline_measure, type Baseline } from './baseline-measure'
|
|
12
|
-
import {
|
|
6
|
+
import { trusted_measure } from './trusted-measure'
|
|
13
7
|
|
|
14
8
|
// `josh measure:rerun <N>` — read a behavior-change Issue body after merge, re-run each baseline
|
|
15
9
|
// command, and print the before/after pair. A value that did not move means
|
|
16
10
|
// the premise the rule rested on is refuted, so a line is appended to the observation ledger — reusing
|
|
17
11
|
// that append-only, same-key mechanism rather than a second one.
|
|
18
12
|
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// commands cannot make it safe; what can is who wrote it. The input is therefore the Issue number, not
|
|
22
|
-
// a file, so the author's `author_association` arrives with the body from the same REST read, and only
|
|
23
|
-
// a body written by someone with write access to the repository is run.
|
|
13
|
+
// The baseline is shell, so only a body whose author can write to the repository is run
|
|
14
|
+
// (`trusted-measure.ts`).
|
|
24
15
|
|
|
25
16
|
const FAILURE_EXIT_CODE = 1
|
|
26
17
|
const ARGV_OFFSET = 2
|
|
27
|
-
const ISSUE_FIELDS = 'body,author_association,user'
|
|
28
|
-
const UNKNOWN_ASSOCIATION = 'unknown'
|
|
29
18
|
const USAGE = 'Usage: josh measure:rerun <issue-number>'
|
|
30
19
|
const NO_BASELINE = 'no `command → value` baseline found in ## ベースライン — nothing to re-run'
|
|
31
20
|
const DATE_END = 10
|
|
32
|
-
// One baseline command's budget — the same minute `josh-harness.ts` gives one command it spawns. A
|
|
33
|
-
// baseline is a count or a digest that answers in seconds, so a command still running after a minute
|
|
34
|
-
// is stuck, and without a bound it would hold the whole re-measurement open with nothing printed.
|
|
35
|
-
const MEASURE_TIMEOUT_MS = 60_000
|
|
36
|
-
const issue_schema = z.object({
|
|
37
|
-
body: z.string().nullish(),
|
|
38
|
-
author_association: z.string().nullish(),
|
|
39
|
-
user: z.looseObject({ login: z.string() }).nullish(),
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
type IssueFields = z.infer<typeof issue_schema>
|
|
43
|
-
type BodyRead = { kind: 'trusted'; body: string } | { kind: 'refused'; reason: string }
|
|
44
|
-
|
|
45
|
-
// The command's stdout, trimmed to one value. A failing or timed-out command yields its message so the
|
|
46
|
-
// pair still prints and the remaining baselines still run, rather than aborting the whole
|
|
47
|
-
// re-measurement.
|
|
48
|
-
function measure(command: string): string {
|
|
49
|
-
try {
|
|
50
|
-
return execSync(command, { encoding: 'utf8', timeout: MEASURE_TIMEOUT_MS }).trim()
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return `(command failed: ${error_text.message_of(error)})`
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
21
|
|
|
56
22
|
interface Outcome {
|
|
57
23
|
pair: string
|
|
58
24
|
ledger_line: string | undefined
|
|
59
25
|
}
|
|
60
26
|
|
|
27
|
+
// A failing or timed-out command yields its message so the pair still prints and the remaining
|
|
28
|
+
// baselines still run, rather than aborting the whole re-measurement.
|
|
61
29
|
function rerun_one(baseline: Baseline, date: string): Outcome {
|
|
62
|
-
const after =
|
|
30
|
+
const after = trusted_measure.text_of(trusted_measure.run_command(baseline.command))
|
|
63
31
|
const is_unchanged = baseline_measure.is_no_change(baseline.value, after)
|
|
64
32
|
|
|
65
33
|
return {
|
|
@@ -83,37 +51,6 @@ function today(now: Date): string {
|
|
|
83
51
|
return now.toISOString().slice(0, DATE_END)
|
|
84
52
|
}
|
|
85
53
|
|
|
86
|
-
async function read_issue(issue_number: string): Promise<IssueFields | undefined> {
|
|
87
|
-
const json = await git_gh_issue_read.issue_view_json(issue_number, ISSUE_FIELDS)
|
|
88
|
-
|
|
89
|
-
return json === undefined ? undefined : parse_json_object_or_undefined(json, issue_schema)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function untrusted(issue_number: string, issue: IssueFields): BodyRead {
|
|
93
|
-
const association = issue.author_association ?? UNKNOWN_ASSOCIATION
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
kind: 'refused',
|
|
97
|
-
reason: `refusing to run the baseline of ${session_cite.issue(issue_number)}: its author (${association}) is not ${gh_author_association.TRUSTED_LIST}`,
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// The body, only when its author can write to the repository. A read that fails is refused as well:
|
|
102
|
-
// an unknown author is not a trusted one.
|
|
103
|
-
async function read_trusted_body(issue_number: string): Promise<BodyRead> {
|
|
104
|
-
const issue = await read_issue(issue_number)
|
|
105
|
-
|
|
106
|
-
if (issue === undefined) {
|
|
107
|
-
return { kind: 'refused', reason: `could not read issue ${session_cite.issue(issue_number)}` }
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const author = { login: issue.user?.login, association: issue.author_association }
|
|
111
|
-
|
|
112
|
-
if (!(await gh_author_association.is_trusted(author))) return untrusted(issue_number, issue)
|
|
113
|
-
|
|
114
|
-
return { kind: 'trusted', body: issue.body ?? '' }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
54
|
async function rerun(body: string, now: Date): Promise<number> {
|
|
118
55
|
const baselines = baseline_measure.parse_baselines(body)
|
|
119
56
|
|
|
@@ -141,7 +78,7 @@ async function run(issue_number: string | undefined, now: Date): Promise<number>
|
|
|
141
78
|
return FAILURE_EXIT_CODE
|
|
142
79
|
}
|
|
143
80
|
|
|
144
|
-
const read = await read_trusted_body(issue_number)
|
|
81
|
+
const read = await trusted_measure.read_trusted_body(issue_number)
|
|
145
82
|
|
|
146
83
|
if (read.kind === 'refused') {
|
|
147
84
|
console.error(read.reason)
|
|
@@ -50,6 +50,27 @@ function refusal_of(plan: string): string | undefined {
|
|
|
50
50
|
return is_bare_none(entries) ? BARE_NONE : undefined
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
// The lines `[start, end)` the section spans in `visible`, or `undefined` when the plan has none.
|
|
54
|
+
function section_range(visible: ReadonlyArray<string>): [number, number] | undefined {
|
|
55
|
+
const start = visible.findIndex((line) => line.trim() === HEADING)
|
|
56
|
+
|
|
57
|
+
if (start === -1) return undefined
|
|
58
|
+
const end = visible.slice(start + 1).findIndex((line) => ANY_HEADING.test(line.trim()))
|
|
59
|
+
|
|
60
|
+
return [start, end === -1 ? visible.length : start + 1 + end]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// `plan` with the section, heading included, removed — its entries name the files that read what the
|
|
64
|
+
// plan changes, not the files it edits (joshuafolkken/kit#4923).
|
|
65
|
+
function without(plan: string): string {
|
|
66
|
+
const range = section_range(markdown_section.unfenced_lines(plan))
|
|
67
|
+
|
|
68
|
+
if (range === undefined) return plan
|
|
69
|
+
const lines = markdown_section.lines_of(plan)
|
|
70
|
+
|
|
71
|
+
return [...lines.slice(0, range[0]), ...lines.slice(range[1])].join('\n')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const plan_consumers_section = { HEADING, refusal_of, without }
|
|
54
75
|
|
|
55
76
|
export { plan_consumers_section }
|
|
@@ -10,6 +10,8 @@ const REPRODUCTION_HEADING = '## 再現'
|
|
|
10
10
|
// A backticked command span on a line that is not inside a fenced block. `- `grep …`` and a bare
|
|
11
11
|
// `` `grep …` `` both match; prose with no backticked command does not.
|
|
12
12
|
const COMMAND_SPAN = /`[^`]+`/u
|
|
13
|
+
// A template placeholder span (a backticked `<...>`) the filer never replaced — not a command.
|
|
14
|
+
const PLACEHOLDER_SPAN = /`<[^`]*>`/gu
|
|
13
15
|
// A fenced code block boundary — a backtick or tilde fence. The actual output goes between an opening
|
|
14
16
|
// and a closing fence, so a valid reproduction carries at least two of these lines. Tilde is accepted
|
|
15
17
|
// so the template can show the block without nesting a backtick fence inside its own ```md example.
|
|
@@ -28,7 +30,7 @@ function has_command_line(lines: ReadonlyArray<string>): boolean {
|
|
|
28
30
|
const first_fence = lines.findIndex((line) => FENCE_LINE.test(line))
|
|
29
31
|
const open_lines = first_fence === -1 ? lines : lines.slice(0, first_fence)
|
|
30
32
|
|
|
31
|
-
return open_lines.some((line) => COMMAND_SPAN.test(line))
|
|
33
|
+
return open_lines.some((line) => COMMAND_SPAN.test(line.replaceAll(PLACEHOLDER_SPAN, '')))
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
// An opened-and-closed output block is two fence lines, so counting them is enough — a section with a
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { split_assess, type SizeMeasurement } from '#scripts/split/split-assess'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { SPLIT_ROUTE_LABEL } from './issue-labels'
|
|
4
|
+
import { markdown_section } from './markdown-section'
|
|
5
|
+
|
|
6
|
+
// joshuafolkken/kit#4675: a request clearly over the split guide reached a lane as one Issue because
|
|
7
|
+
// nothing asked for its size when it was filed, and a split is cheapest at filing time. Every filing
|
|
8
|
+
// declares its estimate under the background heading, so it stays on the Issue; one over the guide is
|
|
9
|
+
// refused unless it is filed as a split or says why it stays one Issue.
|
|
10
|
+
//
|
|
11
|
+
// **The guide is `split_assess`'s, never a copy** — the measured diff and the declared estimate are
|
|
12
|
+
// judged by one `verdict_for`. An epic (a body carrying `## Split rationale`) declares no estimate:
|
|
13
|
+
// its children do.
|
|
14
|
+
|
|
15
|
+
const BACKGROUND_HEADING = '## 背景'
|
|
16
|
+
const EPIC_HEADING = '## Split rationale'
|
|
17
|
+
const PREFIX = '- 規模見積もり:'
|
|
18
|
+
const { REASON_PREFIX } = split_assess
|
|
19
|
+
const DECLARATION = `${PREFIX} 約 <N> ファイル / <M> 行(テスト除く)`
|
|
20
|
+
// Full- or half-width parentheses alike: the two read the same to the person typing the line.
|
|
21
|
+
const VALUE_PATTERN =
|
|
22
|
+
/^約\s*(?<files>\d+)\s*ファイル\s*\/\s*(?<lines>\d+)\s*行\s*[((]テスト除く[))]$/u
|
|
23
|
+
const MISSING = `missing size estimate: declare \`${DECLARATION}\` under ${BACKGROUND_HEADING}`
|
|
24
|
+
// The refusal ends in a line ready to paste (joshuafolkken/kit#5225).
|
|
25
|
+
const EXAMPLE = `${PREFIX} 約 3 ファイル / 60 行(テスト除く)`
|
|
26
|
+
const MALFORMED = `\`${PREFIX}\` must be written ${DECLARATION}\n${EXAMPLE}`
|
|
27
|
+
const estimate_schema = z.object({ files: z.coerce.number(), lines: z.coerce.number() })
|
|
28
|
+
|
|
29
|
+
function declared(body: string, prefix: string): string | undefined {
|
|
30
|
+
const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
|
|
31
|
+
const line = markdown_section
|
|
32
|
+
.unfenced_lines(background)
|
|
33
|
+
.map((text) => text.trim())
|
|
34
|
+
.find((text) => text.startsWith(prefix))
|
|
35
|
+
|
|
36
|
+
return line?.slice(prefix.length).trim()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function is_epic(body: string): boolean {
|
|
40
|
+
return markdown_section.has_line(body, EPIC_HEADING)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The declared estimate judged against the guide, or `undefined` when there is none or it does not parse.
|
|
44
|
+
function parse(body: string): SizeMeasurement | undefined {
|
|
45
|
+
const read = estimate_schema.safeParse(VALUE_PATTERN.exec(declared(body, PREFIX) ?? '')?.groups)
|
|
46
|
+
|
|
47
|
+
if (!read.success) return undefined
|
|
48
|
+
const { files, lines } = read.data
|
|
49
|
+
|
|
50
|
+
return { files, lines, verdict: split_assess.verdict_for(files, lines) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The shape check `issue:lint` makes: the declaration is there and parses. The guide needs the route,
|
|
54
|
+
// which only `issue:file` knows, so it is `guide_problem`'s.
|
|
55
|
+
function problems(body: string): ReadonlyArray<string> {
|
|
56
|
+
if (is_epic(body)) return []
|
|
57
|
+
if (declared(body, PREFIX) === undefined) return [MISSING]
|
|
58
|
+
|
|
59
|
+
return parse(body) === undefined ? [MALFORMED] : []
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A split filing, or one that declares why it stays one Issue, passes over the guide.
|
|
63
|
+
function is_excused(body: string, route: string | undefined): boolean {
|
|
64
|
+
return route === SPLIT_ROUTE_LABEL || (declared(body, REASON_PREFIX) ?? '') !== ''
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function refusal(estimate: SizeMeasurement): string {
|
|
68
|
+
const counted = `size estimate ${String(estimate.files)} file(s) / ${String(estimate.lines)} line(s) exceeds the guide (${String(split_assess.FILE_GUIDE)} files / ${String(split_assess.LINE_GUIDE)} lines)`
|
|
69
|
+
|
|
70
|
+
return `${counted} — file it split (\`--route split\`) or declare \`${REASON_PREFIX} <理由>\`; split-assessment.md → "${split_assess.QUESTION_SECTION}"`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The filing refusal for an estimate over the guide; `undefined` under it, on a split filing, or with
|
|
74
|
+
// the reason declared. A missing or malformed estimate is `problems`' refusal, not this one.
|
|
75
|
+
function guide_problem(body: string, route: string | undefined): string | undefined {
|
|
76
|
+
const estimate = parse(body)
|
|
77
|
+
|
|
78
|
+
if (estimate?.verdict !== split_assess.SPLIT_VERDICT) return undefined
|
|
79
|
+
|
|
80
|
+
return is_excused(body, route) ? undefined : refusal(estimate)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The declared file count, or `undefined` when there is no estimate or it does not parse.
|
|
84
|
+
function declared_files(body: string): number | undefined {
|
|
85
|
+
return parse(body)?.files
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const size_estimate = { declared_files, guide_problem, problems }
|
|
89
|
+
|
|
90
|
+
export { size_estimate }
|