@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,110 @@
|
|
|
1
|
+
import { time_shell } from '#scripts/time-runtime/time-shell'
|
|
2
|
+
import { time_transcript_line } from '#scripts/time-runtime/time-transcript-line'
|
|
3
|
+
import { piped_verification } from './piped-verification'
|
|
4
|
+
import { rule_value } from './rule-value'
|
|
5
|
+
import { shell_segments } from './shell-segments'
|
|
6
|
+
|
|
7
|
+
// The `piped-verification` refusals named by the filter that drew them, for `josh rule:value --fresh`.
|
|
8
|
+
//
|
|
9
|
+
// **Which shape to move from the refusal to the rewrite is read here, not in a transcript by hand.**
|
|
10
|
+
// The refusal text is one fixed reason, so the row's `refused` count says how often and never why; the
|
|
11
|
+
// command that drew it is the call the errored result answers, paired by its `tool_use_id`
|
|
12
|
+
// (joshuafolkken/kit#5266). A shape is named by the first that holds, in the order below: the filters
|
|
13
|
+
// that hide a verdict first, the chain — a line of more than one command, a heredoc's body lines
|
|
14
|
+
// included — only where no filter explains the refusal.
|
|
15
|
+
|
|
16
|
+
const SHAPE = {
|
|
17
|
+
head: 'head',
|
|
18
|
+
grep: 'grep',
|
|
19
|
+
script: 'awk|sed|tee',
|
|
20
|
+
chain: 'chain',
|
|
21
|
+
other: 'other',
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
type RefusalShape = (typeof SHAPE)[keyof typeof SHAPE]
|
|
25
|
+
type ShapeCounts = Map<RefusalShape, number>
|
|
26
|
+
|
|
27
|
+
const HEAD_FILTER = /^head\b/u
|
|
28
|
+
const SCRIPT_FILTER = /^(?:awk|sed|tee)\b/u
|
|
29
|
+
// The reason's opening, matched as `rule-value.ts` matches a refusal; a text without the cheap mark —
|
|
30
|
+
// the reason's ASCII words, which the rewrite note does not share — is skipped unparsed.
|
|
31
|
+
const SIGNATURE = piped_verification.PIPED_VERIFICATION_REASON.slice(
|
|
32
|
+
0,
|
|
33
|
+
rule_value.REASON_SIGNATURE_LENGTH,
|
|
34
|
+
)
|
|
35
|
+
const CHEAP_MARK = 'piped verification: a pipeline exits'
|
|
36
|
+
const HEADING = 'piped-verification refusals by shape:'
|
|
37
|
+
const SHAPE_GAP = ' · '
|
|
38
|
+
|
|
39
|
+
function filter_shape(filters: ReadonlyArray<string>): RefusalShape | undefined {
|
|
40
|
+
if (filters.some((piece) => HEAD_FILTER.test(piece))) return SHAPE.head
|
|
41
|
+
if (filters.some((piece) => piped_verification.EARLY_EXIT_GREP.test(piece))) return SHAPE.grep
|
|
42
|
+
|
|
43
|
+
return filters.some((piece) => SCRIPT_FILTER.test(piece)) ? SHAPE.script : undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shape_of(command: string): RefusalShape {
|
|
47
|
+
const by_filter = filter_shape(piped_verification.check_filters(command))
|
|
48
|
+
|
|
49
|
+
if (by_filter !== undefined) return by_filter
|
|
50
|
+
|
|
51
|
+
const segments = shell_segments.segments_of(time_shell.unquoted(command))
|
|
52
|
+
|
|
53
|
+
return segments.length > 1 ? SHAPE.chain : SHAPE.other
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface Pairing {
|
|
57
|
+
commands: Map<string, string>
|
|
58
|
+
refused: Array<string>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type Block = NonNullable<ReturnType<typeof time_transcript_line.parse_line>>['blocks'][number]
|
|
62
|
+
|
|
63
|
+
function is_refusal(block: Block): boolean {
|
|
64
|
+
if (block.error_text === '') return false
|
|
65
|
+
|
|
66
|
+
return time_transcript_line.refusal_body(block.error_text).startsWith(SIGNATURE)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pair_block(block: Block, pairing: Pairing): void {
|
|
70
|
+
if (block.id !== '') pairing.commands.set(block.id, time_shell.bash_command(block.input))
|
|
71
|
+
if (is_refusal(block)) pairing.refused.push(block.result_id)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function blocks_of(text: string): Array<Block> {
|
|
75
|
+
return text.split('\n').flatMap((line) => time_transcript_line.parse_line(line)?.blocks ?? [])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The commands one transcript's `piped-verification` refusals answered.
|
|
79
|
+
function refused_commands(text: string): Array<string> {
|
|
80
|
+
if (!text.includes(CHEAP_MARK)) return []
|
|
81
|
+
|
|
82
|
+
const pairing: Pairing = { commands: new Map(), refused: [] }
|
|
83
|
+
|
|
84
|
+
for (const block of blocks_of(text)) pair_block(block, pairing)
|
|
85
|
+
|
|
86
|
+
return pairing.refused
|
|
87
|
+
.map((id) => pairing.commands.get(id) ?? '')
|
|
88
|
+
.filter((command) => command !== '')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function count(text: string, counts: ShapeCounts): void {
|
|
92
|
+
for (const command of refused_commands(text)) {
|
|
93
|
+
const shape = shape_of(command)
|
|
94
|
+
|
|
95
|
+
counts.set(shape, (counts.get(shape) ?? 0) + 1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function render(counts: ShapeCounts): string {
|
|
100
|
+
const cells = Object.values(SHAPE).map(
|
|
101
|
+
(shape) => `${shape} ${(counts.get(shape) ?? 0).toString()}`,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return `${HEADING} ${cells.join(SHAPE_GAP)}`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const piped_verification_shapes = { HEADING, SHAPE, count, refused_commands, render, shape_of }
|
|
108
|
+
|
|
109
|
+
export { piped_verification_shapes }
|
|
110
|
+
export type { RefusalShape, ShapeCounts }
|
|
@@ -40,6 +40,7 @@ import { shell_segments } from './shell-segments'
|
|
|
40
40
|
// change. The Issue that filed this rule names `eval` for that reason.
|
|
41
41
|
const VERIFICATION_COMMANDS: ReadonlySet<string> = new Set([
|
|
42
42
|
'check',
|
|
43
|
+
'check:related',
|
|
43
44
|
'cspell',
|
|
44
45
|
'cspell:dot',
|
|
45
46
|
'eval',
|
|
@@ -97,6 +98,13 @@ function pipelines_of(command: string): Array<Array<string>> {
|
|
|
97
98
|
.map((segment) => segment.split('|').map((piece) => piece.trim()))
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// The filters behind each check on the line — what a refusal is named by (`piped-verification-shapes.ts`).
|
|
102
|
+
function check_filters(command: string): Array<string> {
|
|
103
|
+
return pipelines_of(command).flatMap(([head = '', ...filters]) =>
|
|
104
|
+
is_verification_command(head) ? filters : [],
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
100
108
|
function command_pieces(command: string): Array<string> {
|
|
101
109
|
return pipelines_of(command).flat()
|
|
102
110
|
}
|
|
@@ -125,7 +133,12 @@ function keeps_verdict_intact(command: string): boolean {
|
|
|
125
133
|
// `grep` stopping at its first match — closes the pipe on a check still writing, and under `pipefail` the
|
|
126
134
|
// check's SIGPIPE turns a passing run into exit 141; `head` also cuts the verdict line josh prints last.
|
|
127
135
|
// `cut`, `sort`, `uniq` and `wc` read to the end as well, and a `grep … | cut -c1-250` narrowing was the
|
|
128
|
-
// commonest masking still refused after #3875 (joshuafolkken/kit#3920).
|
|
136
|
+
// commonest masking still refused after #3875 (joshuafolkken/kit#3920). `tee` passes every line on, and an
|
|
137
|
+
// `awk` or `sed` script reads to the end where it can neither exit nor reach past the pipe — read on the
|
|
138
|
+
// typed piece, since the script is quoted: an `awk` program with no `exit`, `nextfile`, `system`, `getline` or `|`,
|
|
139
|
+
// and a `sed` script of `p`, `d` and `s///` alone (joshuafolkken/kit#5266). A quoted `<`, `>` or `&`
|
|
140
|
+
// still refuses below: inside `awk` a `>` may be `print > "f"`, which no reading here tells from a
|
|
141
|
+
// comparison.
|
|
129
142
|
//
|
|
130
143
|
// **Each masked check is wrapped on its own, and the rest of the line runs as typed.** Most of the
|
|
131
144
|
// refusals left after #3570 were a check chained beside other work — `vitest …; pnpm josh lint:related
|
|
@@ -143,9 +156,18 @@ function keeps_verdict_intact(command: string): boolean {
|
|
|
143
156
|
const PIPEFAIL_OPEN = '(set -o pipefail; '
|
|
144
157
|
const PIPEFAIL_CLOSE = ')'
|
|
145
158
|
const COMMENT_START = /(?:^|\s)#/u
|
|
146
|
-
const FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc)
|
|
159
|
+
const FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc|tee)(?=\s|$)/u
|
|
147
160
|
const EARLY_EXIT_GREP =
|
|
148
161
|
/^grep\b.*\s(?:-[a-zA-Z]*[lLmq]|--(?:quiet|silent|max-count|files-with(?:out)?-match))/u
|
|
162
|
+
const AWK_PROGRAM = /^awk\s+(?:-F\s*\S+\s+)?'[^']*'$/u
|
|
163
|
+
const AWK_EARLY_EXIT = /\b(?:exit|nextfile|system|getline)\b|\|/u
|
|
164
|
+
const SED_TEXT = String.raw`(?:[^/\\']|\\.)*`
|
|
165
|
+
const SED_ADDRESS = String.raw`(?:\d+|\$|/${SED_TEXT}/)`
|
|
166
|
+
const SED_COMMAND = String.raw`(?:${SED_ADDRESS}(?:,${SED_ADDRESS})?)?!?(?:p|d|s/${SED_TEXT}/${SED_TEXT}/[gip\d]*)`
|
|
167
|
+
const SED_PROGRAM = new RegExp(
|
|
168
|
+
String.raw`^sed\s+(?:-[nEr]+\s+)*'${SED_COMMAND}(?:;\s*${SED_COMMAND})*'$`,
|
|
169
|
+
'u',
|
|
170
|
+
)
|
|
149
171
|
|
|
150
172
|
const ALLOWED_OPERATORS = /2>&1|&&/gu
|
|
151
173
|
const SIDE_EFFECT_SYNTAX = /[&<>`]|\$\(/u
|
|
@@ -156,14 +178,40 @@ const PIPEFAIL_NOTE =
|
|
|
156
178
|
"`(set -o pipefail; …)` to carry the check's status, and the rest of the line ran as typed. Read " +
|
|
157
179
|
'the printed verdict, never the exit code alone — `prompts/collaboration-workflow/output-bounds.md`.'
|
|
158
180
|
|
|
159
|
-
function
|
|
160
|
-
|
|
181
|
+
function is_full_reading_script(typed: string): boolean {
|
|
182
|
+
if (AWK_PROGRAM.test(typed)) return !AWK_EARLY_EXIT.test(typed)
|
|
183
|
+
|
|
184
|
+
return SED_PROGRAM.test(typed)
|
|
161
185
|
}
|
|
162
186
|
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
// A filter's name and flags are read on the blanked piece, its quoted script on the typed one.
|
|
188
|
+
function reads_to_the_end(piece: string, typed: string): boolean {
|
|
189
|
+
if (FULL_READING_FILTER.test(piece)) return !EARLY_EXIT_GREP.test(piece)
|
|
165
190
|
|
|
166
|
-
return
|
|
191
|
+
return is_full_reading_script(typed)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The typed text at the offsets of the blanked pieces, each `gap` characters apart — the blanked line
|
|
195
|
+
// keeps every offset, so a cut found there is the same cut in the typed one.
|
|
196
|
+
function typed_slices(raw: string, pieces: ReadonlyArray<string>, gap: number): Array<string> {
|
|
197
|
+
const offset = { at: 0 }
|
|
198
|
+
|
|
199
|
+
return pieces.map((piece) => {
|
|
200
|
+
const typed = raw.slice(offset.at, offset.at + piece.length)
|
|
201
|
+
|
|
202
|
+
offset.at += piece.length + gap
|
|
203
|
+
|
|
204
|
+
return typed
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function is_filtered_check(pieces: ReadonlyArray<string>, typed: ReadonlyArray<string>): boolean {
|
|
209
|
+
const [head = '', ...filters] = pieces.map((piece) => piece.trim())
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
is_verification_command(head) &&
|
|
213
|
+
filters.every((piece, index) => reads_to_the_end(piece, typed[index + 1]?.trim() ?? ''))
|
|
214
|
+
)
|
|
167
215
|
}
|
|
168
216
|
|
|
169
217
|
function has_side_effect_syntax(command: string): boolean {
|
|
@@ -173,9 +221,13 @@ function has_side_effect_syntax(command: string): boolean {
|
|
|
173
221
|
// The side-effect syntax is read on the typed part, since a double-quoted `$(cmd)` still runs; the pipe
|
|
174
222
|
// cut and the grouping are read on the blanked one, since a quoted `|` or `(` is text.
|
|
175
223
|
function is_rewritable_part(raw: string, judged: string): boolean {
|
|
176
|
-
const
|
|
224
|
+
const pieces = judged.split('|')
|
|
177
225
|
|
|
178
|
-
return
|
|
226
|
+
return (
|
|
227
|
+
!has_side_effect_syntax(raw) &&
|
|
228
|
+
!GROUP_SYNTAX.test(judged) &&
|
|
229
|
+
is_filtered_check(pieces, typed_slices(raw, pieces, 1))
|
|
230
|
+
)
|
|
179
231
|
}
|
|
180
232
|
|
|
181
233
|
// Where the part's code ends: an unquoted `#` opens a comment, which would swallow the closing `)`.
|
|
@@ -210,14 +262,10 @@ function pipefail_command(command: string): string | undefined {
|
|
|
210
262
|
|
|
211
263
|
if (judged === undefined || !is_masked_verification(command)) return undefined
|
|
212
264
|
|
|
213
|
-
const
|
|
214
|
-
const parts =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
offset.at += part.length
|
|
218
|
-
|
|
219
|
-
return rewritten_part(raw, part)
|
|
220
|
-
})
|
|
265
|
+
const pieces = shell_segments.parts_of(judged)
|
|
266
|
+
const parts = typed_slices(command, pieces, 0).map((raw, index) =>
|
|
267
|
+
rewritten_part(raw, pieces[index] ?? ''),
|
|
268
|
+
)
|
|
221
269
|
|
|
222
270
|
const rewritten = parts.includes(undefined) ? undefined : parts.join('')
|
|
223
271
|
|
|
@@ -236,9 +284,11 @@ function pipefail_input(call: GuardedCall): Record<string, unknown> | undefined
|
|
|
236
284
|
}
|
|
237
285
|
|
|
238
286
|
const piped_verification = {
|
|
287
|
+
EARLY_EXIT_GREP,
|
|
239
288
|
PIPED_VERIFICATION_REASON,
|
|
240
289
|
PIPEFAIL_NOTE,
|
|
241
290
|
VERIFICATION_COMMANDS,
|
|
291
|
+
check_filters,
|
|
242
292
|
is_masked_verification,
|
|
243
293
|
keeps_verdict_intact,
|
|
244
294
|
pipefail_input,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { time_shell } from '#scripts/time-runtime/time-shell'
|
|
2
2
|
import { bash_triggers } from './bash-triggers'
|
|
3
|
+
import { early_heartbeat } from './early-heartbeat'
|
|
4
|
+
import { shell_segments } from './shell-segments'
|
|
3
5
|
|
|
4
6
|
// The trigger and the delivered text behind the `poll-loop` row of `delivered-rules.ts`.
|
|
5
7
|
// The hand-written wait loop a lane child improvises over a backgrounded
|
|
@@ -23,8 +25,7 @@ import { bash_triggers } from './bash-triggers'
|
|
|
23
25
|
// back on the run's self-restraint — the thing that let these loops through in the first place.
|
|
24
26
|
|
|
25
27
|
// A `while` or `until` header, in either spelling a poll loop reaches for. The trailing whitespace is
|
|
26
|
-
// what separates the keyword from a path that merely begins with it (`until.txt`)
|
|
27
|
-
// by omission — it iterates a fixed list rather than probing a state.
|
|
28
|
+
// what separates the keyword from a path that merely begins with it (`until.txt`).
|
|
28
29
|
const LOOP_KEYWORD = /\b(?:while|until)\s/u
|
|
29
30
|
// A `sleep` command with any argument — the pause between probes that makes a loop a poll rather than
|
|
30
31
|
// a stream reader. Read anywhere in the line rather than at a command position, because the loop body
|
|
@@ -52,10 +53,72 @@ const PROBE_HEADERS: ReadonlyArray<RegExp> = [
|
|
|
52
53
|
String.raw`grep\s+-[A-Za-z]*q`,
|
|
53
54
|
].map((probe) => new RegExp(`${WHILE_HEADER}${probe}`, 'u'))
|
|
54
55
|
|
|
56
|
+
// **A counted `for` that sleeps is a poll with a deadline** (joshuafolkken/kit#4800). A lane child
|
|
57
|
+
// waited out a backgrounded reviewer with `for i in $(seq 1 30); do sleep 10; done`, and the
|
|
58
|
+
// completion notification sat queued until the loop returned — the same held-back exit a `while` poll
|
|
59
|
+
// costs, spelled as a count. A header that counts — `$(seq …)`, a `{1..N}` range, or `((…))` — has no
|
|
60
|
+
// list to work through, so its `sleep` is the whole point. A `for` over a real list (`for f in *.ts`)
|
|
61
|
+
// is ordinary work and passes, sleep or not. The `sleep` is read in the loop's body only — from the
|
|
62
|
+
// header to its first `done` — so a counted loop beside an unrelated `sleep` is not a wait (review
|
|
63
|
+
// round 1); a nested loop's inner `done` cuts the body short, which under-refuses and so fails safe.
|
|
64
|
+
const COUNTED_FOR = new RegExp(
|
|
65
|
+
String.raw`${COMMAND_POSITION}for(?:\s*\(\(|\s+\w+\s+in\s+(?:\$\(\s*seq\b|\{\d+\.\.))`,
|
|
66
|
+
'u',
|
|
67
|
+
)
|
|
68
|
+
const THROUGH_FIRST_DONE = /^[\s\S]*?\bdone\b/u
|
|
69
|
+
|
|
70
|
+
// **A sleep chain in front of a read is the same poll, unrolled** (joshuafolkken/kit#5115). A lane
|
|
71
|
+
// child watched a backgrounded task with `sleep 20; sleep 20; sleep 20; tail -c 1500 …/x.output` and
|
|
72
|
+
// `sleep 20; ps -p 123`: no loop keyword, so the rows above miss it, and a read segment makes it
|
|
73
|
+
// work rather than a bare timer, so `early-heartbeat` stands down. Its sleep is only there for the
|
|
74
|
+
// read after it, which the completion notification makes unnecessary. The call is cut where the shell
|
|
75
|
+
// starts a program (`shell_segments.commands_of`), and every cut is a sleep, an inert marker
|
|
76
|
+
// (`early-heartbeat`'s own reading of both), or a read of a file or a process — at least one read,
|
|
77
|
+
// which is what keeps it disjoint from `early-heartbeat`'s bare timer. A short settle (`sleep 3; tail`)
|
|
78
|
+
// is below the threshold and passes.
|
|
79
|
+
const SETTLE_SECONDS = 10
|
|
80
|
+
const NO_SECONDS = 0
|
|
81
|
+
const READ_COMMAND = /^(?:tail|cat|head|ps|stat|grep|wc|ls|pgrep|test|kill\s+-0|\[\[?)(?=\s|$)/u
|
|
82
|
+
|
|
83
|
+
function slept_seconds(segments: ReadonlyArray<string>): number {
|
|
84
|
+
return segments.reduce(
|
|
85
|
+
(total, segment) => total + (early_heartbeat.sleep_seconds(segment) ?? NO_SECONDS),
|
|
86
|
+
NO_SECONDS,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Only the sleep in front of the first read counts: `tail x; sleep 20` reads first and waits after,
|
|
91
|
+
// which is not a wait *for* the read.
|
|
92
|
+
function is_sleep_then_read(plain: string): boolean {
|
|
93
|
+
const segments = shell_segments.commands_of(plain).filter((segment) => segment !== '')
|
|
94
|
+
const first_work = segments.findIndex((segment) => !early_heartbeat.is_wait_or_inert(segment))
|
|
95
|
+
const waited = first_work === -1 ? NO_SECONDS : slept_seconds(segments.slice(0, first_work))
|
|
96
|
+
|
|
97
|
+
if (waited < SETTLE_SECONDS) return false
|
|
98
|
+
|
|
99
|
+
return segments
|
|
100
|
+
.slice(first_work)
|
|
101
|
+
.every((segment) => early_heartbeat.is_wait_or_inert(segment) || READ_COMMAND.test(segment))
|
|
102
|
+
}
|
|
103
|
+
|
|
55
104
|
function is_state_wait(plain: string): boolean {
|
|
56
105
|
return UNTIL_HEADER.test(plain) || PROBE_HEADERS.some((header) => header.test(plain))
|
|
57
106
|
}
|
|
58
107
|
|
|
108
|
+
function is_counted_sleep(plain: string): boolean {
|
|
109
|
+
const header = COUNTED_FOR.exec(plain)
|
|
110
|
+
|
|
111
|
+
if (header === null) return false
|
|
112
|
+
|
|
113
|
+
const rest = plain.slice(header.index)
|
|
114
|
+
|
|
115
|
+
return SLEEP_COMMAND.test(THROUGH_FIRST_DONE.exec(rest)?.[0] ?? rest)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function is_sleeping_loop(plain: string): boolean {
|
|
119
|
+
return (LOOP_KEYWORD.test(plain) && SLEEP_COMMAND.test(plain)) || is_counted_sleep(plain)
|
|
120
|
+
}
|
|
121
|
+
|
|
59
122
|
// **A `while read … done < file` is a stream reader, not a poll**, even when its body sleeps to
|
|
60
123
|
// throttle processing. Its condition consumes input rather
|
|
61
124
|
// than probing for a state — a poll never reads its condition, it *tests* it (grep, wc, a file check)
|
|
@@ -80,18 +143,18 @@ function is_poll_loop(command: string): boolean {
|
|
|
80
143
|
if (is_stream_reader(plain)) return false
|
|
81
144
|
if (is_state_wait(plain)) return true
|
|
82
145
|
|
|
83
|
-
return
|
|
146
|
+
return is_sleeping_loop(plain) || is_sleep_then_read(plain)
|
|
84
147
|
}
|
|
85
148
|
|
|
86
149
|
// The instruction in the shape a refusal can carry: why the command's exit is the thing to wait on,
|
|
87
150
|
// and the one route that delivers that exit. The measurement behind it is in
|
|
88
151
|
// `docs/maintainers/rule-delivery-rationale.md`.
|
|
89
152
|
const POLL_LOOP_REASON =
|
|
90
|
-
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'`pnpm josh
|
|
94
|
-
'every occurrence.'
|
|
153
|
+
'⛔ output poll loop: a `while`/`until` or counted `for` loop, or a `sleep` chain before a read, ' +
|
|
154
|
+
'does not know when the task finished. `run_in_background` and end the turn — its exit ' +
|
|
155
|
+
're-invokes you; a background `Agent` is waited on by its completion notification (lane child: ' +
|
|
156
|
+
'bare `sleep 20`, `pnpm josh run:liveness`); CI by `pnpm josh followup` ' +
|
|
157
|
+
'(`background-commands.md`). Fires on every occurrence.'
|
|
95
158
|
|
|
96
159
|
// The row itself, so `delivered-rules.ts` spreads one entry. `decide` returns true so it refuses every
|
|
97
160
|
// occurrence; it declares no `keeps` — waiting on the task exit is the *absence* of a loop, not a
|
|
@@ -106,7 +169,7 @@ const ROW = {
|
|
|
106
169
|
title: 'Output poll loop',
|
|
107
170
|
topic: '`.claude/skills/workflow-commands/background-commands.md`',
|
|
108
171
|
fires:
|
|
109
|
-
"a hand-written `while` / `until` wait loop over a command's output, a marker file or a process. Every occurrence",
|
|
172
|
+
"a hand-written `while` / `until` wait loop over a command's output, a marker file or a process, a counted `for` that sleeps, or a `sleep` chain of 10 s or more before a read of a file or process. Every occurrence",
|
|
110
173
|
quiet: 'the command started with `run_in_background` — its exit ends the wait',
|
|
111
174
|
},
|
|
112
175
|
}
|
|
@@ -99,9 +99,9 @@ function is_pr_merge(command: string): boolean {
|
|
|
99
99
|
const PR_MERGE_REASON =
|
|
100
100
|
'⛔ direct PR merge: `gh pr merge`, a `gh api` / `curl` call to `…/pulls/<N>/merge`, a ' +
|
|
101
101
|
'`mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file ' +
|
|
102
|
-
'merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup`
|
|
103
|
-
'run authorizes it; otherwise leave it to the user (`
|
|
104
|
-
'every occurrence.'
|
|
102
|
+
'merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` if the ' +
|
|
103
|
+
'run authorizes it; otherwise leave it to the user (`operating-rules.md` → git-rules). This rule ' +
|
|
104
|
+
'fires on every occurrence.'
|
|
105
105
|
|
|
106
106
|
const ROW = {
|
|
107
107
|
id: 'pr-merge',
|
|
@@ -110,7 +110,7 @@ const ROW = {
|
|
|
110
110
|
decide: (): boolean => true,
|
|
111
111
|
about: {
|
|
112
112
|
title: 'Direct PR merge',
|
|
113
|
-
topic: '`
|
|
113
|
+
topic: '`operating-rules.md` → "git-rules"',
|
|
114
114
|
fires:
|
|
115
115
|
'`gh pr merge`, a `gh api` / `curl` call to `…/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file. Every occurrence',
|
|
116
116
|
quiet:
|
|
@@ -118,6 +118,6 @@ const ROW = {
|
|
|
118
118
|
},
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const pr_merge = { PR_MERGE_REASON, ROW, is_pr_merge }
|
|
121
|
+
const pr_merge = { PR_MERGE_REASON, ROW, is_graphql_call, is_pr_merge }
|
|
122
122
|
|
|
123
123
|
export { pr_merge }
|
|
@@ -139,7 +139,7 @@ function uncut_lane_issue(state: LaneCutState): string | undefined {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// **The cut is taken only when the current context is worth its resume**.
|
|
142
|
-
// Below the
|
|
142
|
+
// Below the model-priced `pnpm josh cost --cut` threshold a short lane has no accumulation a cut would drop, so the
|
|
143
143
|
// gate runs uncut rather than paying for a relaunch; an unmeasurable session keeps the old
|
|
144
144
|
// unconditional cut as the safety net. The verdict is
|
|
145
145
|
// `cost_cli.session_verdict`'s, the same statistic and threshold `run:cut` and the implementation-phase
|
|
@@ -163,8 +163,8 @@ function is_uncut_gate(command: string, state: LaneCutState = current_state()):
|
|
|
163
163
|
const PRE_GATE_CUT_REASON =
|
|
164
164
|
'⛔ pre-gate cut: this checkout is a lane dispatched for this issue and the gate is the boundary to ' +
|
|
165
165
|
'cut at. Issue `pnpm josh run:cut <N>`: on `cut`, end the turn immediately — no Telegram is owed; ' +
|
|
166
|
-
'on any other verdict (`not-a-lane`, `busy`, …),
|
|
167
|
-
'(`.claude/skills/workflow-commands/pre-gate-cut.md`).'
|
|
166
|
+
'on `split-unresolved`, answer its notice and rerun it; on any other verdict (`not-a-lane`, `busy`, …), ' +
|
|
167
|
+
'reissue the gate — this fires once per run (`.claude/skills/workflow-commands/pre-gate-cut.md`).'
|
|
168
168
|
|
|
169
169
|
// **The pre-gate cut is two acts, one call apart**, so its `rule:value`
|
|
170
170
|
// denominator is only the runs that reached the boundary: a run that issues the cut's entry check and,
|
|
@@ -2,7 +2,9 @@ import path from 'node:path'
|
|
|
2
2
|
import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
|
|
3
3
|
import { json_value } from '#scripts/lib/json-value'
|
|
4
4
|
import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
|
|
5
|
+
import { time_shell } from '#scripts/time-runtime/time-shell'
|
|
5
6
|
import { bash_triggers } from './bash-triggers'
|
|
7
|
+
import { edit_tool } from './edit-tool'
|
|
6
8
|
import { git_argv } from './git-argv'
|
|
7
9
|
import { shell_segments } from './shell-segments'
|
|
8
10
|
|
|
@@ -22,7 +24,7 @@ import { shell_segments } from './shell-segments'
|
|
|
22
24
|
// It fires on every occurrence, for the reason `git-force.ts` does.
|
|
23
25
|
|
|
24
26
|
const READ_TOOL = 'Read'
|
|
25
|
-
const
|
|
27
|
+
const { is_edit_tool } = edit_tool
|
|
26
28
|
const SECRET_FILES: ReadonlySet<string> = new Set(['.env', '.dev.vars'])
|
|
27
29
|
const ENVIRONMENT_VARIANT_PREFIX = '.env.'
|
|
28
30
|
// The two variants `.gitignore` un-ignores: they are tracked and hold placeholders, not secrets. The
|
|
@@ -31,10 +33,22 @@ const TRACKED_VARIANTS: ReadonlySet<string> = new Set(['.env.example', '.env.tes
|
|
|
31
33
|
const SETTINGS_FILE = path.join('.claude', 'settings.json')
|
|
32
34
|
const WHITESPACE = /\s+/u
|
|
33
35
|
const TOKEN_QUOTES = /["']/gu
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
const
|
|
36
|
+
// Each character of a redirect operator (`<`, `>`, `>>`, `2>`, `&>`) — and the clobber `>|` whole —
|
|
37
|
+
// is spaced out to a mark, so the word after the last one is the file it reads or writes.
|
|
38
|
+
const REDIRECT = />\||[<>]/gu
|
|
39
|
+
const REDIRECT_MARK = '<'
|
|
40
|
+
// Programs that run their quoted argument as a script.
|
|
41
|
+
const SHELLS: ReadonlySet<string> = new Set(['bash', 'eval', 'sh', 'zsh'])
|
|
42
|
+
// The body of a `$(…)` (innermost) or a backtick command substitution.
|
|
43
|
+
const SUBSTITUTION = /\$\((?<dollar>[^()]*)\)|`(?<backtick>[^`]*)`/gu
|
|
44
|
+
// Programs that print a named file's bytes or run them, and programs that write one: a write could set
|
|
45
|
+
// a guard switch, so `cp .env.example .env` is refused too (kit #4660) — the person makes the file.
|
|
46
|
+
const SHELL_PROGRAMS: ReadonlySet<string> = new Set([
|
|
47
|
+
'cp',
|
|
48
|
+
'install',
|
|
49
|
+
'ln',
|
|
50
|
+
'mv',
|
|
51
|
+
'tee',
|
|
38
52
|
'.',
|
|
39
53
|
'awk',
|
|
40
54
|
'base64',
|
|
@@ -88,33 +102,82 @@ function is_environment_path(token: string): boolean {
|
|
|
88
102
|
return SECRET_FILES.has(name) || name.startsWith(ENVIRONMENT_VARIANT_PREFIX)
|
|
89
103
|
}
|
|
90
104
|
|
|
91
|
-
// A shell read reaches the same bytes a `Read` does,
|
|
92
|
-
//
|
|
93
|
-
// the
|
|
94
|
-
// 2.1.289, kit #3602), so
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
// A shell read reaches the same bytes a `Read` does, and a shell write changes the file an `Edit`
|
|
106
|
+
// would, so a segment that runs one of the programs (`cat .env`, `source .env`, `tee .env`) or
|
|
107
|
+
// redirects the file (`< .env`, `>> .env`) is refused too. Claude Code applies the `Read` deny rules to
|
|
108
|
+
// the readers it recognizes, but `source .env.local` passes them (measured on 2.1.289, kit #3602), so
|
|
109
|
+
// the variants are matched here as well.
|
|
110
|
+
//
|
|
111
|
+
// A quoted `>` is text (`git commit -m "x >> .env"`), so the marks are read on the quote-blanked
|
|
112
|
+
// segment, whose offsets match the original; a shell's quoted script (`sh -c "echo x > .env"`) runs,
|
|
113
|
+
// so its marks are read as written, as is a heredoc line, which `blanked` does not cut.
|
|
114
|
+
function redirect_marks_of(segment: string, program: string | undefined): string {
|
|
115
|
+
if (program !== undefined && SHELLS.has(program)) return segment
|
|
116
|
+
|
|
117
|
+
return time_shell.blanked(segment) ?? segment
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function spaced_redirects(segment: string, program: string | undefined): string {
|
|
121
|
+
const marks = redirect_marks_of(segment, program)
|
|
122
|
+
|
|
123
|
+
return segment.replaceAll(REDIRECT, (mark, offset: number) =>
|
|
124
|
+
marks.startsWith(mark, offset) ? ` ${REDIRECT_MARK} ` : mark,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function is_environment_shell_segment(segment: string): boolean {
|
|
129
|
+
const call = git_argv.program_of(segment)
|
|
130
|
+
const words = spaced_redirects(segment, call?.name)
|
|
98
131
|
.split(WHITESPACE)
|
|
99
132
|
.filter((word) => word !== '')
|
|
100
133
|
const is_redirected = words.some(
|
|
101
|
-
(word, index) => word ===
|
|
134
|
+
(word, index) => word === REDIRECT_MARK && is_environment_path(words[index + 1] ?? ''),
|
|
102
135
|
)
|
|
103
|
-
const call = git_argv.program_of(segment)
|
|
104
136
|
|
|
105
137
|
if (is_redirected || call === undefined) return is_redirected
|
|
106
138
|
|
|
107
|
-
return
|
|
139
|
+
return SHELL_PROGRAMS.has(call.name) && call.args.some((word) => is_environment_path(word))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// A shell's quoted script holds commands of its own (`bash -c "true; cat .env"`), so it is cut again
|
|
143
|
+
// without regard to quotes.
|
|
144
|
+
function script_commands_of(segment: string): Array<string> {
|
|
145
|
+
const program = git_argv.program_of(segment)?.name
|
|
146
|
+
|
|
147
|
+
if (program === undefined || !SHELLS.has(program)) return []
|
|
148
|
+
|
|
149
|
+
return shell_segments.commands_of(segment)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A command substitution runs even inside double quotes (`echo "$(echo x > .env)"`), which the
|
|
153
|
+
// quote-blanked reading takes for text, so its body is cut out as commands of their own.
|
|
154
|
+
function substitution_commands_of(segment: string): Array<string> {
|
|
155
|
+
return [...segment.matchAll(SUBSTITUTION)].flatMap((match) =>
|
|
156
|
+
shell_segments.commands_of(match.groups?.['dollar'] ?? match.groups?.['backtick'] ?? ''),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function segments_of(command: string): Array<string> {
|
|
161
|
+
return shell_segments
|
|
162
|
+
.quote_aware_commands_of(command)
|
|
163
|
+
.flatMap((segment) => [
|
|
164
|
+
segment,
|
|
165
|
+
...script_commands_of(segment),
|
|
166
|
+
...substitution_commands_of(segment),
|
|
167
|
+
])
|
|
108
168
|
}
|
|
109
169
|
|
|
110
|
-
function
|
|
111
|
-
return
|
|
170
|
+
function is_environment_shell_access(command: string): boolean {
|
|
171
|
+
return segments_of(command).some((segment) => is_environment_shell_segment(segment))
|
|
112
172
|
}
|
|
113
173
|
|
|
114
|
-
|
|
115
|
-
|
|
174
|
+
// A `Read` lands the secret in the transcript; an `Edit` / `Write` could set a guard switch.
|
|
175
|
+
function is_environment_access(call: GuardedCall): boolean {
|
|
176
|
+
if (call.name === READ_TOOL || is_edit_tool(call.name)) {
|
|
177
|
+
return is_environment_path(file_path_of(call))
|
|
178
|
+
}
|
|
116
179
|
|
|
117
|
-
return bash_triggers.on_bash_command(
|
|
180
|
+
return bash_triggers.on_bash_command(is_environment_shell_access)(call)
|
|
118
181
|
}
|
|
119
182
|
|
|
120
183
|
// The repository the settings file belongs to is two levels above it (`<root>/.claude/settings.json`).
|
|
@@ -122,7 +185,7 @@ function is_consumer_settings_edit(
|
|
|
122
185
|
call: GuardedCall,
|
|
123
186
|
is_kit_consumer: (root: string) => boolean,
|
|
124
187
|
): boolean {
|
|
125
|
-
if (!
|
|
188
|
+
if (!is_edit_tool(call.name)) return false
|
|
126
189
|
|
|
127
190
|
const file_path = path.resolve(file_path_of(call))
|
|
128
191
|
|
|
@@ -135,11 +198,12 @@ function is_protected_file_call(
|
|
|
135
198
|
call: GuardedCall,
|
|
136
199
|
is_kit_consumer: (root: string) => boolean = doctor_consumer.is_kit_consumer,
|
|
137
200
|
): boolean {
|
|
138
|
-
return
|
|
201
|
+
return is_environment_access(call) || is_consumer_settings_edit(call, is_kit_consumer)
|
|
139
202
|
}
|
|
140
203
|
|
|
141
204
|
const PROTECTED_FILE_REASON =
|
|
142
|
-
'⛔ protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets
|
|
205
|
+
'⛔ protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets and are written only ' +
|
|
206
|
+
'by the person, and a consumer ' +
|
|
143
207
|
'`.claude/settings.json` is owned by `josh sync` (a change belongs upstream in kit). Leave these ' +
|
|
144
208
|
'files alone, or ask the user. It fires on every occurrence.'
|
|
145
209
|
|
|
@@ -154,7 +218,7 @@ const ROW = {
|
|
|
154
218
|
title: 'Protected file',
|
|
155
219
|
topic: '`operating-rules.md`',
|
|
156
220
|
fires:
|
|
157
|
-
'a `Read` of `.env
|
|
221
|
+
'a `Read`, `Edit` or `Write` of `.env` (or a shell read or write of it), or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence',
|
|
158
222
|
quiet: "kit itself, or the user's `~/.claude/settings.json`",
|
|
159
223
|
},
|
|
160
224
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { reply_prose } from './reply-prose'
|
|
2
|
+
|
|
1
3
|
// Whether a reply is written in the session language. The session-language
|
|
2
4
|
// line reaches a turn only through `UserPromptSubmit`, so a turn opened by a background completion or a
|
|
3
5
|
// Stop hook refusal answered in whatever language it was just shown, and kept answering in it.
|
|
@@ -31,14 +33,6 @@ const MATCH_SHARE_FLOOR = 0.5
|
|
|
31
33
|
// unless no line of the reply is longer.
|
|
32
34
|
const MIN_VOTING_LETTERS = 8
|
|
33
35
|
|
|
34
|
-
const CODE_PATTERNS: ReadonlyArray<RegExp> = [
|
|
35
|
-
/```[\s\S]*?(?:```|$)/gu,
|
|
36
|
-
/`[^`\n]*`/gu,
|
|
37
|
-
/\]\([^)\s]*\)/gu,
|
|
38
|
-
/https?:\/\/\S+/gu,
|
|
39
|
-
]
|
|
40
|
-
// A run of printable ASCII; one holding a slash is a bare path the reply printed outside backticks.
|
|
41
|
-
const ASCII_RUN_PATTERN = /[!-~]+/gu
|
|
42
36
|
const LETTER_PATTERN = /\p{L}/gu
|
|
43
37
|
// A letter of another script — `Common` and `Inherited` letters (`µ`) belong to no language of their own.
|
|
44
38
|
const NON_LATIN_LETTER_PATTERN = /(?![\p{Script=Latn}\p{Script=Common}\p{Script=Inherited}])\p{L}/u
|
|
@@ -63,14 +57,6 @@ function letter_pattern(script: string): RegExp | undefined {
|
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
|
|
66
|
-
function prose_of(message: string): string {
|
|
67
|
-
let text = message
|
|
68
|
-
|
|
69
|
-
for (const pattern of CODE_PATTERNS) text = text.replaceAll(pattern, ' ')
|
|
70
|
-
|
|
71
|
-
return text.replaceAll(ASCII_RUN_PATTERN, (run) => (run.includes('/') ? ' ' : run))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
function letter_count(text: string): number {
|
|
75
61
|
return text.match(LETTER_PATTERN)?.length ?? 0
|
|
76
62
|
}
|
|
@@ -105,7 +91,7 @@ function is_mismatch(message: string, lang: string): boolean {
|
|
|
105
91
|
const pattern = letter_pattern(script)
|
|
106
92
|
if (pattern === undefined) return false
|
|
107
93
|
|
|
108
|
-
const prose = prose_of(message)
|
|
94
|
+
const prose = reply_prose.prose_of(message)
|
|
109
95
|
if (letter_count(prose) < MIN_PROSE_LETTERS) return false
|
|
110
96
|
|
|
111
97
|
return matches_share(prose, script, pattern) < MATCH_SHARE_FLOOR
|