@joshuafolkken/kit 1.1514.0 → 1.1784.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/investigator.md +1 -1
- package/.claude/settings.json +5 -5
- package/.claude/skills/epic-commands/SKILL.md +26 -65
- package/.claude/skills/epic-commands/epic-bundle.md +3 -1
- package/.claude/skills/workflow-commands/SKILL.md +5 -5
- package/.claude/skills/workflow-commands/background-commands.md +5 -5
- package/.claude/skills/workflow-commands/backlogrun-child.md +10 -8
- package/.claude/skills/workflow-commands/backlogrun-handoff-report.md +7 -25
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +17 -13
- package/.claude/skills/workflow-commands/backlogrun-park.md +14 -23
- package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -2
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +5 -5
- package/.claude/skills/workflow-commands/backlogrun-steps.md +10 -10
- package/.claude/skills/workflow-commands/backlogrun.md +1 -1
- package/.claude/skills/workflow-commands/chain-rule.md +13 -7
- package/.claude/skills/workflow-commands/delegation.md +6 -5
- package/.claude/skills/workflow-commands/entry-sequence.md +9 -9
- package/.claude/skills/workflow-commands/followup.md +9 -31
- package/.claude/skills/workflow-commands/fullrun-steps.md +36 -45
- package/.claude/skills/workflow-commands/fullrun.md +2 -2
- package/.claude/skills/workflow-commands/halfrun.md +17 -25
- package/.claude/skills/workflow-commands/issue-comments.md +16 -12
- package/.claude/skills/workflow-commands/issue-scout.md +2 -2
- package/.claude/skills/workflow-commands/kickoff.md +24 -23
- package/.claude/skills/workflow-commands/latest-gate.md +7 -12
- package/.claude/skills/workflow-commands/needs-human-review.md +9 -14
- package/.claude/skills/workflow-commands/observation-filing.md +4 -36
- package/.claude/skills/workflow-commands/observation-ledger.md +1 -1
- package/.claude/skills/workflow-commands/pre-gate-cut.md +13 -30
- package/.claude/skills/workflow-commands/prerequisite.md +10 -16
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/prrun.md +3 -7
- package/.claude/skills/workflow-commands/retrospective.md +1 -1
- package/.claude/skills/workflow-commands/split-assessment.md +13 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +9 -9
- package/.codex/hooks.json +5 -5
- package/.github/actions/setup-node/action.yml +9 -0
- package/.github/actions/setup-pnpm/action.yml +1 -1
- package/.github/dependabot.yml +16 -0
- package/.github/release.yml +0 -2
- package/.github/workflows/ci.yml +120 -10
- package/.github/workflows/publish.yml +8 -1
- package/.github/workflows/sonar-qube.yml +1 -0
- package/.prettierignore +2 -0
- package/.vscode/tasks.json +1 -2
- package/CLAUDE.md +3 -7
- package/README.md +4 -2
- package/SECURITY.md +1 -1
- package/dist/CLAUDE.md +3 -7
- package/dist/commands/document-read-cli.js +882 -0
- package/dist/commands/{document-section-cli.js → document-section-main.js} +79 -37
- package/dist/commands/read-set-cli.js +1867 -0
- package/dist/commands/run-prep-main.js +21653 -0
- package/dist/commands/run-step-cli.js +23880 -0
- package/dist/config-merge/index.js +5 -1
- package/dist/hooks/audit-provision.js +20 -35
- package/dist/hooks/{chunk-DGT54IH5.js → chunk-AZX4FGQ3.js} +63 -17
- package/dist/hooks/chunk-CWMBGKYW.js +6483 -0
- package/dist/hooks/chunk-O2YBIDSI.js +4306 -0
- package/dist/hooks/{chunk-TNUMY6NL.js → chunk-PT5R6UJD.js} +4193 -1900
- package/dist/hooks/chunk-TUIFS5E7.js +72 -0
- package/dist/hooks/{chunk-723HKZO5.js → chunk-VWWCFGWK.js} +1 -1
- package/dist/hooks/{chunk-APUO6U4J.js → chunk-WR2KQAYZ.js} +1843 -440
- package/dist/hooks/{chunk-O6GDRNJB.js → chunk-XGBNSWEI.js} +248 -46
- package/dist/hooks/chunk-YCDOIR3I.js +3456 -0
- package/dist/hooks/codex-hook-adapter.js +10 -22
- package/dist/hooks/format-edited.js +5 -6
- package/dist/hooks/inputs.json +443 -317
- package/dist/hooks/pretool-guard.js +7 -25
- package/dist/hooks/session-lang.js +44 -51
- package/dist/hooks/stop-guard.js +7716 -2187
- package/dist/josh.js +6002 -3583
- package/dist/self-sync-guard/index.js +74 -10
- package/dist/version/index.js +115 -406
- package/eslint/base.js +1 -1
- package/lefthook/base.yml +3 -1
- package/package.json +10 -48
- package/pnpm-workspace.yaml +4 -2
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +1 -7
- package/prompts/collaboration-workflow/issue-citation.md +1 -1
- package/prompts/collaboration-workflow/issue-template.md +33 -16
- package/prompts/collaboration-workflow/lane-instructions.md +1 -2
- package/prompts/collaboration-workflow/measurement.md +37 -0
- package/prompts/collaboration-workflow/operating-rules.md +4 -6
- package/prompts/collaboration-workflow/output-bounds.md +2 -7
- package/prompts/collaboration-workflow/plan-comment.md +3 -42
- package/prompts/collaboration-workflow/principles.md +5 -0
- package/prompts/collaboration-workflow/report-format.md +31 -60
- package/prompts/collaboration-workflow/residency.md +2 -2
- package/prompts/collaboration-workflow/shell-body.md +6 -16
- package/prompts/collaboration-workflow/turn-batching.md +0 -2
- package/prompts/collaboration-workflow/upstream-interrupt.md +14 -4
- package/prompts/collaboration-workflow/wip-cap.md +1 -1
- package/prompts/collaboration-workflow.md +3 -7
- package/prompts/refactoring.md +1 -1
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +41 -21
- package/prompts/testing-guide.md +8 -43
- package/scripts/agent/agent-event.ts +15 -15
- package/scripts/agent/agent-exit-record.ts +13 -1
- package/scripts/agent/api-auth.ts +41 -0
- package/scripts/agent/api-outage.ts +1 -1
- package/scripts/auto-ok/auto-ok-cli.ts +14 -16
- package/scripts/auto-ok/auto-ok-judge-read.ts +12 -3
- package/scripts/auto-ok/auto-ok-judge-write.ts +46 -19
- package/scripts/auto-ok/auto-ok-judge.ts +13 -2
- package/scripts/auto-ok/auto-ok-main.ts +17 -0
- package/scripts/backlog/backlog-arrival.ts +14 -3
- package/scripts/backlog/backlog-budget-cli.ts +7 -33
- package/scripts/backlog/backlog-budget-main.ts +16 -0
- package/scripts/backlog/backlog-budget.ts +2 -3
- package/scripts/backlog/backlog-defect-priority.ts +4 -3
- package/scripts/backlog/backlog-drive-adopt.ts +67 -0
- package/scripts/backlog/backlog-drive-args.ts +4 -5
- package/scripts/backlog/backlog-drive-cli.ts +113 -28
- package/scripts/backlog/backlog-drive-hold.ts +162 -0
- package/scripts/backlog/backlog-drive-idle.ts +151 -0
- package/scripts/backlog/backlog-drive-judge.ts +25 -0
- package/scripts/backlog/backlog-drive-memory.ts +126 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +5 -10
- package/scripts/backlog/backlog-drive-named.ts +53 -6
- package/scripts/backlog/backlog-drive-offer-argv.ts +3 -8
- package/scripts/backlog/backlog-drive-paused.ts +169 -0
- package/scripts/backlog/backlog-drive-restore.ts +30 -7
- package/scripts/backlog/backlog-drive-seats.ts +18 -8
- package/scripts/backlog/backlog-drive-token-handling.ts +38 -0
- package/scripts/backlog/backlog-drive.ts +79 -75
- package/scripts/backlog/backlog-next-main.ts +16 -0
- package/scripts/backlog/backlog-next-shared.ts +73 -0
- package/scripts/backlog/backlog-next.ts +48 -16
- package/scripts/backlog/backlog-occupied.ts +52 -39
- package/scripts/backlog/backlog-offer-cli.ts +6 -21
- package/scripts/backlog/backlog-offer-exclude.ts +15 -0
- package/scripts/backlog/backlog-offer-main.ts +12 -0
- package/scripts/backlog/backlog-offer.ts +36 -3
- package/scripts/backlog/backlog-overlap.ts +22 -4
- package/scripts/backlog/backlog-plan-read.ts +6 -2
- package/scripts/backlog/backlog-plan.ts +18 -4
- package/scripts/backlog/backlog-pool.ts +10 -10
- package/scripts/backlog/backlog-rank.ts +10 -2
- package/scripts/backlog/backlog-ready.ts +112 -30
- package/scripts/backlog/backlog-stalled-detect.ts +109 -36
- package/scripts/backlog/backlog-stalled.ts +52 -16
- package/scripts/backlog/backlog-wait-cause.ts +86 -0
- package/scripts/backlog/backlog-waves.ts +39 -13
- package/scripts/backlog/backlogrun-args.ts +19 -7
- package/scripts/backlog/backlogrun-cli.ts +19 -5
- package/scripts/backlog/backlogrun-launch.ts +6 -0
- package/scripts/backlog/backlogrun-restart.ts +97 -0
- package/scripts/backlog/backlogrun-stop.ts +79 -7
- package/scripts/behavior/behavior-cli.ts +2 -2
- package/scripts/behavior/behavior-rules.ts +47 -24
- package/scripts/build/build-bin.ts +2 -16
- package/scripts/build/build-claude-md.ts +0 -7
- package/scripts/build/build-commands.ts +0 -13
- package/scripts/build/build-hooks.ts +49 -48
- package/scripts/build/hook-bundle-stamp.ts +29 -11
- package/scripts/build/local-bin.ts +1 -1
- package/scripts/cases/cases-cli.ts +25 -13
- package/scripts/cases/cases-interaction-scan.ts +134 -0
- package/scripts/cases/cases-interaction.ts +183 -0
- package/scripts/cases/cases-logic.ts +39 -6
- package/scripts/ci/pr-classification-main.ts +18 -0
- package/scripts/ci/pr-classification.ts +10 -17
- package/scripts/clone/clone-scan.ts +1 -1
- package/scripts/cost-runtime/cost-blocks.ts +22 -37
- package/scripts/cost-runtime/cost-cli.ts +31 -14
- package/scripts/cost-runtime/cost-main.ts +12 -0
- package/scripts/cost-runtime/cost-transcript.ts +1 -0
- package/scripts/delegation/delegation-policy.ts +17 -4
- package/scripts/delegation/duplicate-read-guard.ts +0 -8
- package/scripts/delegation/investigation-guard.ts +0 -8
- package/scripts/delegation/investigation-reads.ts +3 -4
- package/scripts/dependabot/dependabot-cooldown.ts +194 -0
- package/scripts/discovery/repo-discovery.ts +24 -6
- package/scripts/doctor/doctor-consumer.ts +2 -5
- package/scripts/document/document-byte-budget.ts +8 -6
- package/scripts/document/document-scan.ts +1 -1
- package/scripts/document/document-section-cli.ts +0 -8
- package/scripts/document/document-section-main.ts +12 -0
- package/scripts/epic/epic-audit-cli.ts +12 -6
- package/scripts/epic/epic-bundle-apply.ts +137 -0
- package/scripts/epic/epic-bundle-cli.ts +32 -121
- package/scripts/epic/epic-bundle-format.ts +116 -0
- package/scripts/epic/epic-bundle-main.ts +17 -0
- package/scripts/epic/epic-busy.ts +5 -12
- package/scripts/epic/epic-classify.ts +24 -15
- package/scripts/epic/epic-cli-argv.ts +31 -0
- package/scripts/epic/epic-cli-decide.ts +42 -0
- package/scripts/epic/epic-cli.ts +6 -38
- package/scripts/epic/epic-close.ts +112 -55
- package/scripts/epic/epic-cross-repo.ts +4 -7
- package/scripts/epic/epic-decide.ts +119 -0
- package/scripts/epic/epic-decision.ts +9 -0
- package/scripts/epic/epic-fetch.ts +4 -0
- package/scripts/epic/epic-graph.ts +2 -0
- package/scripts/epic/epic-issue.ts +5 -1
- package/scripts/epic/epic-label-age.ts +1 -1
- package/scripts/epic/epic-lane-offer.ts +39 -4
- package/scripts/epic/epic-next-main.ts +24 -0
- package/scripts/epic/epic-next.ts +6 -24
- package/scripts/epic/epic-outside-blocker.ts +6 -2
- package/scripts/epic/epic-parse.ts +9 -3
- package/scripts/epic/epic-read.ts +1 -0
- package/scripts/epic/epic-reconcile.ts +39 -0
- package/scripts/epic/epic-run.ts +10 -1
- package/scripts/epic/epic-solo.ts +50 -14
- package/scripts/epic/epic.ts +41 -7
- package/scripts/exports/unused-members-cli.ts +2 -2
- package/scripts/followup/followup-checks-event.ts +8 -3
- package/scripts/followup/followup-coderabbit-wait.ts +82 -0
- package/scripts/followup/followup-review-gates.ts +81 -0
- package/scripts/followup/git-followup-stages.ts +13 -3
- package/scripts/followup/git-followup-workflow.ts +19 -66
- package/scripts/followup/git-pr-followup-wrapup.ts +9 -14
- package/scripts/followup/git-pr-followup.ts +36 -8
- package/scripts/fresh-machine/fresh-machine-cli.ts +1 -1
- package/scripts/fresh-machine/fresh-machine-run.ts +8 -8
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +1 -1
- package/scripts/gate/busy-average.ts +58 -0
- package/scripts/gate/check-cpu.ts +60 -0
- package/scripts/gate/check-inputs.ts +39 -95
- package/scripts/gate/check-reuse.ts +33 -54
- package/scripts/gate/command-memory.ts +192 -0
- package/scripts/gate/core-admission.ts +218 -28
- package/scripts/gate/core-budget-timing.ts +37 -0
- package/scripts/gate/core-budget.ts +80 -98
- package/scripts/gate/core-wait-record.ts +48 -0
- package/scripts/gate/core-wait.ts +37 -5
- package/scripts/gate/core-weights.ts +86 -0
- package/scripts/gate/free-cores.ts +27 -4
- package/scripts/gate/gate-ledger.ts +32 -4
- package/scripts/gate/gate-plan.ts +41 -34
- package/scripts/gate/gate-report.ts +14 -2
- package/scripts/gate/gate-run-steps.ts +53 -8
- package/scripts/gate/gate-skip.ts +12 -2
- package/scripts/gate/gate-steps.ts +12 -1
- package/scripts/gate/input-fingerprint.ts +42 -0
- package/scripts/gate/machine-capacity.ts +65 -31
- package/scripts/gate/moved-paths.ts +67 -0
- package/scripts/gate/project-checks.ts +5 -12
- package/scripts/gate/scoped-green.ts +12 -8
- package/scripts/gate/since-green.ts +62 -0
- package/scripts/gate/toolchain-inputs.ts +115 -0
- package/scripts/gate/verification-gate.ts +27 -24
- package/scripts/gate/verification-inputs.ts +250 -0
- package/scripts/gh/gh-author-association.ts +96 -9
- package/scripts/gh/git-ai-review-scan.ts +4 -2
- package/scripts/gh/git-gh-api-path.ts +15 -0
- package/scripts/gh/git-gh-branch-rules.ts +73 -0
- package/scripts/gh/git-gh-conditional.ts +5 -2
- package/scripts/gh/git-gh-issue-read.ts +5 -1
- package/scripts/gh/git-gh-issue-write.ts +67 -29
- package/scripts/gh/git-gh-issue.ts +7 -3
- package/scripts/gh/git-gh-poll-read.ts +89 -0
- package/scripts/gh/git-gh-pr-auto-merge.ts +8 -3
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr-snapshot.ts +33 -20
- package/scripts/gh/git-gh-pr.ts +27 -15
- package/scripts/gh/git-pr-checks-eval.ts +39 -7
- package/scripts/gh/git-pr-checks-parse.ts +39 -9
- package/scripts/gh/git-pr-merge-checked.ts +80 -0
- package/scripts/git/base-tree.ts +19 -2
- package/scripts/git/changed-file-scope.ts +6 -4
- package/scripts/git/changed-paths.ts +18 -1
- package/scripts/git/git-diff-reads.ts +27 -2
- package/scripts/git/git-grep-hits.ts +85 -0
- package/scripts/git/git-location-environment.ts +1 -1
- package/scripts/git/git-merge-conflicts.ts +83 -0
- package/scripts/git/git-preflight.ts +37 -12
- package/scripts/git/git-push-transport.ts +1 -2
- package/scripts/git/git-schemas.ts +17 -7
- package/scripts/git/git-spawn.ts +1 -2
- package/scripts/git/git-status.ts +4 -4
- package/scripts/git/main-merge-main.ts +12 -0
- package/scripts/git/main-merge-regenerate.ts +2 -2
- package/scripts/git/main-merge.ts +25 -9
- package/scripts/git/main-sync-main.ts +12 -0
- package/scripts/git/main-sync.ts +0 -9
- package/scripts/git/stash/git-stash.ts +27 -4
- package/scripts/git/stash/{stash-pop-args.ts → stash-args.ts} +13 -10
- package/scripts/git/stash/stash-pop-cli.ts +6 -12
- package/scripts/git/stash/stash-pop-main.ts +12 -0
- package/scripts/git/stash/stash-push-cli.ts +76 -0
- package/scripts/git/stash/stash-push-main.ts +12 -0
- package/scripts/hooks/batch-guard.ts +0 -8
- package/scripts/hooks/check-commit-message-main.ts +8 -0
- package/scripts/hooks/check-commit-message.ts +4 -7
- package/scripts/hooks/format-edited-file.ts +8 -24
- package/scripts/hooks/pre-commit-lint.ts +30 -3
- package/scripts/hooks/pre-push-unit.ts +4 -2
- package/scripts/hooks/pretool-guard-cli.ts +1 -8
- package/scripts/hooks/pretool-guard.ts +32 -19
- package/scripts/hooks/reserved-run.ts +67 -9
- package/scripts/hooks/run-hook.sh +13 -4
- package/scripts/hooks/shell-rules-notice.ts +37 -0
- package/scripts/hooks/stop-actions.ts +2 -21
- package/scripts/hooks/stop-guard.ts +13 -9
- package/scripts/init/hook-command-rewrite.ts +19 -5
- package/scripts/init/hook-launch.ts +15 -17
- package/scripts/init/init-actions.ts +0 -6
- package/scripts/init/init-ai-copy.ts +10 -36
- package/scripts/init/init-bootstrap.ts +9 -8
- package/scripts/init/init-dry-run-report.ts +1 -1
- package/scripts/init/init-dry-run.ts +1 -1
- package/scripts/init/init-file-action.ts +7 -9
- package/scripts/init/init-logic-json-merge.ts +2 -2
- package/scripts/init/init-logic-migrate.ts +9 -82
- package/scripts/init/init-logic.ts +3 -2
- package/scripts/init/init-main.ts +12 -0
- package/scripts/init/init.ts +5 -12
- package/scripts/init/kit-development-versions.ts +3 -7
- package/scripts/init/kit-written-paths.ts +2 -2
- package/scripts/init/project-profile.ts +5 -4
- package/scripts/init/tsconfig-preset-migration.ts +2 -1
- package/scripts/init/vscode-tasks.ts +1 -1
- package/scripts/issue/defect-origin-cli.ts +106 -0
- package/scripts/issue/defect-origin-line.ts +60 -0
- package/scripts/issue/defect-origin-main.ts +11 -0
- package/scripts/issue/defect-origin.ts +114 -0
- package/scripts/issue/defect-rate-cli.ts +34 -78
- package/scripts/issue/defect-rate.ts +0 -16
- package/scripts/issue/defect-report-cli.ts +128 -0
- package/scripts/issue/defect-report-format.ts +56 -0
- package/scripts/issue/defect-report-main.ts +11 -0
- package/scripts/issue/defect-report.ts +160 -0
- package/scripts/issue/expected-effect-lint.ts +1 -0
- package/scripts/issue/issue-already-done-cli.ts +62 -0
- package/scripts/issue/issue-body-or-comment.ts +40 -0
- package/scripts/issue/issue-cite-cli.ts +0 -9
- package/scripts/issue/issue-cite-main.ts +12 -0
- package/scripts/issue/issue-classification.ts +12 -7
- package/scripts/issue/issue-comment-cli.ts +10 -24
- package/scripts/issue/issue-comment-main.ts +12 -0
- package/scripts/issue/issue-evidence-exit.ts +100 -0
- package/scripts/issue/issue-file-cli.ts +29 -7
- package/scripts/issue/issue-file.ts +19 -19
- package/scripts/issue/issue-fold-cli.ts +2 -11
- package/scripts/issue/issue-fold-main.ts +12 -0
- package/scripts/issue/issue-labels.ts +81 -90
- package/scripts/issue/issue-lint-cli.ts +2 -10
- package/scripts/issue/issue-lint-main.ts +12 -0
- package/scripts/issue/issue-measured-cli.ts +56 -0
- package/scripts/issue/issue-merged.ts +19 -2
- package/scripts/issue/issue-not-before-label.ts +135 -0
- package/scripts/issue/issue-not-before.ts +111 -0
- package/scripts/issue/issue-number-shape.ts +15 -1
- package/scripts/issue/issue-park.ts +28 -0
- package/scripts/issue/issue-plan-cli.ts +151 -0
- package/scripts/issue/issue-priority.ts +151 -0
- package/scripts/issue/issue-rank.ts +7 -1
- package/scripts/issue/issue-read-cli.ts +3 -10
- package/scripts/issue/issue-read-main.ts +12 -0
- package/scripts/issue/issue-read.ts +29 -5
- package/scripts/issue/issue-release-cli.ts +0 -10
- package/scripts/issue/issue-release-main.ts +13 -0
- package/scripts/issue/issue-release.ts +7 -12
- package/scripts/issue/issue-sample-gate.ts +192 -0
- package/scripts/issue/issue-sample-source.ts +76 -0
- package/scripts/issue/issue-scout-cli.ts +0 -11
- package/scripts/issue/issue-scout-main.ts +14 -0
- package/scripts/issue/issue-state-cli.ts +0 -10
- package/scripts/issue/issue-state-main.ts +12 -0
- package/scripts/issue/issue-watch-diff.ts +56 -15
- package/scripts/issue/issue-watch.ts +2 -1
- package/scripts/issue/issue-wip.ts +2 -7
- package/scripts/issue/measure-rerun-cli.ts +13 -8
- package/scripts/issue/plan-consumers-cli.ts +39 -0
- package/scripts/issue/plan-consumers-main.ts +11 -0
- package/scripts/issue/plan-consumers-section.ts +55 -0
- package/scripts/issue/plan-consumers.ts +127 -0
- package/scripts/josh/agent-session-environment.ts +1 -1
- package/scripts/josh/command-usage.ts +15 -43
- package/scripts/josh/file-map-stamp.ts +49 -16
- package/scripts/josh/hook-decision.ts +32 -20
- package/scripts/josh/josh-command-map.ts +4 -1
- package/scripts/josh/josh-command-types.ts +12 -6
- package/scripts/josh/josh-commands-agent-hooks.ts +83 -0
- package/scripts/josh/josh-commands-ai.ts +157 -158
- package/scripts/josh/josh-commands-backlog.ts +82 -16
- package/scripts/josh/josh-commands-clone.ts +1 -0
- package/scripts/josh/josh-commands-development.ts +111 -71
- package/scripts/josh/josh-commands-document.ts +43 -1
- package/scripts/josh/josh-commands-epic.ts +171 -0
- package/scripts/josh/josh-commands-hooks.ts +25 -2
- package/scripts/josh/josh-commands-issue-write.ts +181 -0
- package/scripts/josh/josh-commands-issue.ts +124 -24
- package/scripts/josh/josh-commands-lane.ts +103 -2
- package/scripts/josh/josh-commands-lint.ts +19 -8
- package/scripts/josh/josh-commands-maintenance.ts +112 -4
- package/scripts/josh/josh-commands-pause.ts +29 -0
- package/scripts/josh/josh-commands-progress.ts +87 -0
- package/scripts/josh/josh-commands-project.ts +138 -9
- package/scripts/josh/josh-commands-review.ts +124 -0
- package/scripts/josh/josh-commands-run.ts +211 -23
- package/scripts/josh/josh-commands-ship.ts +169 -0
- package/scripts/josh/josh-commands-split.ts +9 -0
- package/scripts/josh/josh-commands-versioning.ts +49 -2
- package/scripts/josh/josh-commands-workflow.ts +144 -6
- package/scripts/josh/josh-logic.ts +6 -13
- package/scripts/josh/package-directory.ts +2 -4
- package/scripts/josh/process-identity.ts +13 -4
- package/scripts/josh/session-language-cli.ts +4 -8
- package/scripts/josh/session-language-main.ts +13 -0
- package/scripts/josh/session-language.ts +6 -1
- package/scripts/josh/shared-cache.ts +90 -0
- package/scripts/josh/stamp-file.ts +9 -15
- package/scripts/lane/lane-await.ts +51 -12
- package/scripts/lane/lane-cache.ts +5 -30
- package/scripts/lane/lane-capacity.ts +14 -23
- package/scripts/lane/lane-check-stats.ts +62 -21
- package/scripts/lane/lane-child-invocation.ts +8 -0
- package/scripts/lane/lane-cli.ts +15 -5
- package/scripts/lane/lane-close.ts +86 -18
- package/scripts/lane/lane-core-wait-stats.ts +66 -0
- package/scripts/lane/lane-dispatch.ts +24 -3
- package/scripts/lane/lane-gap-stats.ts +156 -0
- package/scripts/lane/lane-handoff.ts +69 -1
- package/scripts/lane/lane-held-stats.ts +129 -0
- package/scripts/lane/lane-install.ts +4 -11
- package/scripts/lane/lane-launch-cli.ts +0 -9
- package/scripts/lane/lane-launch-main.ts +12 -0
- package/scripts/lane/lane-ledger-schema.ts +266 -0
- package/scripts/lane/lane-ledger.ts +92 -123
- package/scripts/lane/lane-limit-cli.ts +12 -1
- package/scripts/lane/lane-measure-cli.ts +57 -11
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-parked.ts +3 -3
- package/scripts/lane/lane-pause-cli.ts +130 -0
- package/scripts/lane/lane-pause.ts +158 -0
- package/scripts/lane/lane-process-table.ts +166 -0
- package/scripts/lane/lane-reap-settle.ts +124 -0
- package/scripts/lane/lane-reap.ts +50 -15
- package/scripts/lane/lane-registry.ts +24 -3
- package/scripts/lane/lane-residents.ts +162 -0
- package/scripts/lane/lane-restart.ts +22 -2
- package/scripts/lane/lane-round-two-stats.ts +49 -0
- package/scripts/lane/lane-sampler.ts +4 -0
- package/scripts/lane/lane-session-action.ts +19 -5
- package/scripts/lane/lane-session-broker.ts +102 -21
- package/scripts/lane/lane-snapshot-trace.ts +84 -0
- package/scripts/lane/lane-spans.ts +51 -0
- package/scripts/lane/lane-spent.ts +6 -3
- package/scripts/lane/lane-stage-stats.ts +7 -2
- package/scripts/lane/lane-state.ts +302 -0
- package/scripts/lane/lane-stats.ts +58 -4
- package/scripts/lane/lane-stop-stats.ts +113 -37
- package/scripts/lane/lane-test-related-stats.ts +127 -0
- package/scripts/lane/lane-transcript-calls.ts +145 -0
- package/scripts/lane/lane-transcript-stats.ts +168 -0
- package/scripts/lane/lane-vacant.ts +13 -3
- package/scripts/lane/lane-withdraw-cli.ts +5 -4
- package/scripts/lane/lane-withdraw.ts +50 -49
- package/scripts/lane/process-directories.ts +70 -0
- package/scripts/lane/process-lane-marks.ts +94 -0
- package/scripts/lib/atomic-write.ts +62 -0
- package/scripts/lib/buffered-process.ts +12 -5
- package/scripts/lib/cli-flags.ts +53 -0
- package/scripts/lib/figure-format.ts +63 -0
- package/scripts/lib/json-value.ts +9 -1
- package/scripts/lib/package-manifest.ts +35 -0
- package/scripts/lib/read-file.ts +3 -1
- package/scripts/lib/stamped-memo.ts +63 -0
- package/scripts/lib/stdout-on-stderr.ts +24 -0
- package/scripts/lib/timeouts.ts +9 -0
- package/scripts/lib/transcript-record.ts +91 -0
- package/scripts/lines/effective-limit.ts +1 -1
- package/scripts/lint/eslint-cache-dependencies.ts +145 -0
- package/scripts/lint/eslint-daemon.ts +265 -0
- package/scripts/lint/format.ts +11 -2
- package/scripts/lint/import-graph.ts +279 -0
- package/scripts/lint/lint-gate-scope.ts +102 -0
- package/scripts/lint/lint-parallel-main.ts +19 -0
- package/scripts/lint/lint-parallel.ts +62 -30
- package/scripts/lint/lint-related-scope.ts +30 -24
- package/scripts/lint/lint-related.ts +38 -6
- package/scripts/notify/confirmation-options.ts +1 -2
- package/scripts/notify/telegram-notify.ts +20 -20
- package/scripts/notify/telegram-test-logic.ts +0 -1
- package/scripts/notify/telegram-test-main.ts +8 -0
- package/scripts/notify/telegram-test.ts +2 -6
- package/scripts/observations/observation-ledger.ts +13 -0
- package/scripts/observations/observations-flush-landing.ts +5 -3
- package/scripts/observations/observations-flush.ts +6 -1
- package/scripts/overrides/overrides-check.ts +2 -5
- package/scripts/overrides/overrides-files.ts +2 -1
- package/scripts/overrides/overrides-logic.ts +3 -6
- package/scripts/package/verify-optional-eslint-install.ts +8 -6
- package/scripts/propagate/propagate-publish.ts +2 -7
- package/scripts/propagate/propagate-targets.ts +4 -12
- package/scripts/refactor/import-graph.ts +9 -7
- package/scripts/release/github-release-runs.ts +17 -6
- package/scripts/release/release-approval-prune.ts +18 -0
- package/scripts/release/release-history.ts +1 -3
- package/scripts/release/release-progress.ts +42 -22
- package/scripts/release/release-publish-run.ts +39 -0
- package/scripts/release/release-publish.ts +6 -6
- package/scripts/release/release-scope-cli.ts +0 -10
- package/scripts/release/release-scope-main.ts +12 -0
- package/scripts/repo/repo-setting.ts +3 -6
- package/scripts/repo/required-checks-logic.ts +6 -15
- package/scripts/report/report-format-reference.ts +2 -0
- package/scripts/report/report-labels.ts +155 -0
- package/scripts/report/report-lint-cli.ts +3 -0
- package/scripts/report/report-lint.ts +85 -110
- package/scripts/report/report-template-cli.ts +59 -0
- package/scripts/report/report-template.ts +112 -0
- package/scripts/review/acceptance-map.ts +1 -0
- package/scripts/review/removed-tests-check.ts +86 -0
- package/scripts/review/removed-tests.ts +149 -0
- package/scripts/review/review-brief-cli.ts +81 -15
- package/scripts/review/review-brief-main.ts +12 -0
- package/scripts/review/review-brief.ts +31 -3
- package/scripts/review/review-code-tree.ts +10 -6
- package/scripts/review/review-comment-round.ts +26 -8
- package/scripts/review/review-finding-ledger.ts +30 -15
- package/scripts/review/review-interaction.ts +94 -0
- package/scripts/review/review-readers-scan.ts +163 -0
- package/scripts/review/review-record-cli.ts +69 -30
- package/scripts/review/review-record-main.ts +12 -0
- package/scripts/review/review-round-one-blobs.ts +221 -0
- package/scripts/review/review-round2-cli.ts +49 -12
- package/scripts/review/review-round2.ts +80 -12
- package/scripts/review/review-stamps.ts +19 -1
- package/scripts/review/review-stop-log.ts +96 -0
- package/scripts/review/review-stops-cli.ts +85 -0
- package/scripts/review/review-tree.ts +6 -17
- package/scripts/review/review-untouched-readers.ts +146 -0
- package/scripts/review/test-titles.ts +118 -0
- package/scripts/rules/agent-model.ts +8 -0
- package/scripts/rules/bash-triggers.ts +46 -3
- package/scripts/rules/decision-oracle-batch.ts +9 -1
- package/scripts/rules/decision-oracle.ts +2 -2
- package/scripts/rules/delivered-rules.ts +80 -2
- package/scripts/rules/destructive-command.ts +8 -1
- package/scripts/rules/direct-filing.ts +10 -2
- package/scripts/rules/direct-issue-comment.ts +117 -0
- package/scripts/rules/direct-pr-create.ts +69 -3
- package/scripts/rules/early-heartbeat.ts +2 -1
- package/scripts/rules/file-body.ts +7 -0
- package/scripts/rules/filing-cap.ts +7 -0
- package/scripts/rules/git-argv.ts +72 -14
- package/scripts/rules/git-force.ts +12 -2
- package/scripts/rules/guard-remedy.ts +4 -0
- package/scripts/rules/implementation-cut-verdict.ts +1 -1
- package/scripts/rules/implementation-cut.ts +11 -3
- package/scripts/rules/index-guard.ts +8 -1
- package/scripts/rules/issue-comments.ts +9 -1
- package/scripts/rules/josh-git-bare.ts +7 -0
- package/scripts/rules/lane-background.ts +67 -25
- package/scripts/rules/lane-carry-conflict.ts +6 -0
- package/scripts/rules/lane-direct-tool.ts +74 -0
- package/scripts/rules/lane-interactive-ask.ts +9 -2
- package/scripts/rules/lane-park.ts +45 -23
- package/scripts/rules/lane-split-park.ts +10 -2
- package/scripts/rules/lane-switch-main.ts +7 -0
- package/scripts/rules/oracle-consulted.ts +10 -0
- package/scripts/rules/oracle-firing.ts +1 -0
- package/scripts/rules/permission-guards.ts +3 -0
- package/scripts/rules/piped-verification.ts +1 -1
- package/scripts/rules/poll-loop.ts +7 -0
- package/scripts/rules/pr-merge.ts +123 -0
- package/scripts/rules/protected-files.ts +32 -5
- package/scripts/rules/raw-field-body.ts +6 -0
- package/scripts/rules/rule-body-guard.ts +8 -0
- package/scripts/rules/rule-list.ts +9 -28
- package/scripts/rules/rule-value-cache.ts +4 -9
- package/scripts/rules/rule-value-cli.ts +51 -32
- package/scripts/rules/rule-value.ts +1 -1
- package/scripts/rules/shell-segments.ts +35 -0
- package/scripts/rules/stop-rule-rows.ts +64 -0
- package/scripts/rules/stop-rules.ts +3 -4
- package/scripts/rules/test-declared-commit.ts +11 -1
- package/scripts/rules/third-party-write.ts +7 -0
- package/scripts/rules/timed-rerun.ts +69 -0
- package/scripts/rules/worktree-guard.ts +13 -3
- package/scripts/run/board/run-board-app-name.ts +23 -0
- package/scripts/run/board/run-board-cli.ts +65 -32
- package/scripts/run/board/run-board-earlier.ts +94 -0
- package/scripts/run/board/run-board-ending-reason.ts +67 -0
- package/scripts/run/board/run-board-estimate-schedule.ts +103 -0
- package/scripts/run/board/run-board-estimate.ts +98 -0
- package/scripts/run/board/run-board-fit.ts +153 -36
- package/scripts/run/board/run-board-fold.ts +123 -0
- package/scripts/run/board/run-board-fresh.ts +2 -2
- package/scripts/run/board/run-board-gather-live.ts +29 -0
- package/scripts/run/board/run-board-gather-thread.ts +189 -0
- package/scripts/run/board/run-board-gather-worker.ts +29 -0
- package/scripts/run/board/run-board-gather.ts +187 -0
- package/scripts/run/board/run-board-github.ts +118 -38
- package/scripts/run/board/run-board-header.ts +82 -20
- package/scripts/run/board/run-board-keys.ts +58 -0
- package/scripts/run/board/run-board-kind.ts +20 -6
- package/scripts/run/board/run-board-labels.ts +116 -71
- package/scripts/run/board/run-board-lane-state.ts +109 -0
- package/scripts/run/board/run-board-lane-time.ts +122 -0
- package/scripts/run/board/run-board-layout.ts +239 -60
- package/scripts/run/board/run-board-length.ts +52 -0
- package/scripts/run/board/run-board-machine.ts +8 -4
- package/scripts/run/board/run-board-main.ts +12 -0
- package/scripts/run/board/run-board-notes.ts +52 -31
- package/scripts/run/board/run-board-now.ts +1 -1
- package/scripts/run/board/run-board-operation.ts +15 -33
- package/scripts/run/board/run-board-phase.ts +1 -1
- package/scripts/run/board/run-board-plan-shared.ts +115 -0
- package/scripts/run/board/run-board-plan.ts +55 -3
- package/scripts/run/board/run-board-planning.ts +45 -0
- package/scripts/run/board/run-board-priority.ts +44 -0
- package/scripts/run/board/run-board-process-groups.ts +195 -0
- package/scripts/run/board/run-board-read.ts +151 -27
- package/scripts/run/board/run-board-reason.ts +235 -51
- package/scripts/run/board/run-board-released.ts +35 -0
- package/scripts/run/board/run-board-render-checks.ts +39 -5
- package/scripts/run/board/run-board-render-condition.ts +96 -0
- package/scripts/run/board/run-board-render-drawn.ts +67 -0
- package/scripts/run/board/run-board-render-foot.ts +59 -0
- package/scripts/run/board/run-board-render-icon.ts +35 -0
- package/scripts/run/board/run-board-render-lanes.ts +145 -0
- package/scripts/run/board/run-board-render-lead.ts +80 -0
- package/scripts/run/board/run-board-render-legend.ts +117 -48
- package/scripts/run/board/run-board-render-notes.ts +156 -29
- package/scripts/run/board/run-board-render-processes.ts +196 -0
- package/scripts/run/board/run-board-render-section.ts +119 -0
- package/scripts/run/board/run-board-render-slot.ts +3 -36
- package/scripts/run/board/run-board-render-tail.ts +115 -48
- package/scripts/run/board/run-board-render-time.ts +56 -0
- package/scripts/run/board/run-board-render-title.ts +117 -24
- package/scripts/run/board/run-board-render-waits.ts +104 -20
- package/scripts/run/board/run-board-render.ts +207 -265
- package/scripts/run/board/run-board-roots.ts +54 -0
- package/scripts/run/board/run-board-row-yield.ts +9 -4
- package/scripts/run/board/run-board-setup.ts +1 -12
- package/scripts/run/board/run-board-snapshot.ts +111 -0
- package/scripts/run/board/run-board-spin.ts +2 -2
- package/scripts/run/board/run-board-state.ts +22 -33
- package/scripts/run/board/run-board-status.ts +147 -71
- package/scripts/run/board/run-board-thanks-read.ts +2 -2
- package/scripts/run/board/run-board-tick.ts +122 -107
- package/scripts/run/board/run-board-usage-board.ts +78 -0
- package/scripts/run/board/run-board-usage-lanes.ts +113 -0
- package/scripts/run/board/run-board-usage-listing.ts +93 -0
- package/scripts/run/board/run-board-usage-read.ts +120 -150
- package/scripts/run/board/run-board-usage-rusage.ts +19 -7
- package/scripts/run/board/run-board-usage-text.ts +139 -51
- package/scripts/run/board/run-board-usage-top.ts +102 -0
- package/scripts/run/board/run-board-usage.ts +89 -72
- package/scripts/run/board/run-board-usual.ts +20 -9
- package/scripts/run/carry/run-carry-args.ts +4 -7
- package/scripts/run/carry/run-carry-conversation.ts +1 -1
- package/scripts/run/carry/run-carry.ts +9 -4
- package/scripts/run/cut/run-cut-handoff.ts +2 -2
- package/scripts/run/event/run-event-cli.ts +1 -5
- package/scripts/run/event/run-event-filed.ts +12 -6
- package/scripts/run/event/run-event-follow.ts +2 -3
- package/scripts/run/event/run-event-render.ts +6 -10
- package/scripts/run/event/run-event-scope.ts +2 -2
- package/scripts/run/event/run-event-stream-emit.ts +82 -22
- package/scripts/run/event/run-event-stream.ts +34 -8
- package/scripts/run/hold/run-hold-adopt.ts +65 -0
- package/scripts/run/hold/run-hold-cli.ts +93 -22
- package/scripts/run/hold/run-hold-main.ts +12 -0
- package/scripts/run/hold/run-hold-member.ts +37 -0
- package/scripts/run/hold/run-stop-notify.ts +183 -0
- package/scripts/run/merge/run-merge-auth.ts +31 -0
- package/scripts/run/merge/run-merge-cli.ts +62 -48
- package/scripts/run/merge/run-merge-close.ts +74 -0
- package/scripts/run/merge/run-merge-collect.ts +6 -13
- package/scripts/run/merge/run-merge-driver.ts +71 -0
- package/scripts/run/merge/run-merge-main.ts +12 -0
- package/scripts/run/merge/run-merge-outcome.ts +23 -0
- package/scripts/run/merge/run-merge-restart.ts +39 -0
- package/scripts/run/merge/run-merge-ship.ts +20 -4
- package/scripts/run/merge/run-merge-signals.ts +33 -9
- package/scripts/run/merge/run-merge-steps.ts +120 -85
- package/scripts/run/merge/run-merge.ts +38 -37
- package/scripts/run/park/run-park-cli.ts +91 -0
- package/scripts/run/park/run-park.ts +88 -0
- package/scripts/run/park/run-stalled.ts +68 -0
- package/scripts/run/pause/run-pause-cli.ts +73 -0
- package/scripts/run/pause/run-pause.ts +251 -0
- package/scripts/run/progress/run-progress-cli.ts +4 -12
- package/scripts/run/progress/run-progress-clock.ts +1 -1
- package/scripts/run/progress/run-progress-config.ts +6 -14
- package/scripts/run/progress/run-progress-main.ts +12 -0
- package/scripts/run/progress/run-progress.ts +1 -4
- package/scripts/run/review/run-review.ts +2 -1
- package/scripts/run/run-ending.ts +1 -0
- package/scripts/run/run-invocation.ts +5 -6
- package/scripts/run/run-liveness-cli.ts +6 -8
- package/scripts/run/run-liveness.ts +60 -29
- package/scripts/run/run-next.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +2 -2
- package/scripts/run/run-preflight.ts +18 -3
- package/scripts/run/run-prep-cli.ts +1 -10
- package/scripts/run/run-prep-locate.ts +7 -50
- package/scripts/run/run-prep-main.ts +12 -0
- package/scripts/run/run-prep.ts +6 -0
- package/scripts/run/run-report-cli.ts +101 -13
- package/scripts/run/run-report-handoff.ts +114 -0
- package/scripts/run/run-step.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +13 -0
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +1 -1
- package/scripts/run/ship/run-ship-args.ts +121 -0
- package/scripts/run/ship/run-ship-body-draft.ts +110 -0
- package/scripts/run/ship/run-ship-cli.ts +63 -140
- package/scripts/run/ship/run-ship-detach.ts +19 -8
- package/scripts/run/ship/run-ship-infra-retry.ts +59 -0
- package/scripts/run/ship/run-ship-next.ts +18 -2
- package/scripts/run/ship/run-ship-overlap.ts +42 -5
- package/scripts/run/ship/run-ship-place.ts +96 -0
- package/scripts/run/ship/run-ship-pre-detach-timed.ts +43 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +81 -24
- package/scripts/run/ship/run-ship-preflight.ts +137 -32
- package/scripts/run/ship/run-ship-probe.ts +11 -3
- package/scripts/run/ship/run-ship-relaunch-cap.ts +70 -0
- package/scripts/run/ship/run-ship-removed-rows.ts +61 -0
- package/scripts/run/ship/run-ship-return.ts +107 -23
- package/scripts/run/ship/run-ship-review-steps.ts +105 -151
- package/scripts/run/ship/run-ship-review.ts +73 -6
- package/scripts/run/ship/run-ship-reviewer.ts +207 -0
- package/scripts/run/ship/run-ship-stage.ts +55 -6
- package/scripts/run/ship/run-ship-steps.ts +46 -15
- package/scripts/run/ship/run-ship-stop-cause.ts +135 -0
- package/scripts/run/ship/run-ship-stop-text.ts +11 -6
- package/scripts/run/ship/run-ship-sync.ts +43 -6
- package/scripts/run/ship/run-ship.ts +30 -2
- package/scripts/run/tidy/run-tidy-cli.ts +0 -4
- package/scripts/run/tidy/run-tidy-lanes.ts +3 -0
- package/scripts/run/tidy/run-tidy-main.ts +8 -0
- package/scripts/run/wake/run-wake-cli.ts +119 -100
- package/scripts/run/wake/run-wake-code.ts +83 -0
- package/scripts/run/wake/run-wake-driver.ts +19 -8
- package/scripts/run/wake/run-wake-loop.ts +34 -3
- package/scripts/run/wake/run-wake-main.ts +13 -0
- package/scripts/run/wake/run-wake-ports.ts +90 -0
- package/scripts/run/wake/run-wake-takeover.ts +66 -0
- package/scripts/run/wake/run-wake.ts +2 -1
- package/scripts/safe-chain/pack-hook.ts +2 -2
- package/scripts/safe-chain/preinstall-command.ts +2 -1
- package/scripts/safe-chain/project-config.ts +3 -9
- package/scripts/security/security-audit-main.ts +8 -0
- package/scripts/security/security-audit-provision-logic.ts +0 -11
- package/scripts/security/security-audit-provision-main.ts +12 -0
- package/scripts/security/security-audit-provision.ts +11 -26
- package/scripts/security/security-audit.ts +6 -7
- package/scripts/self-sync-guard/self-sync-guard-logic.ts +6 -13
- package/scripts/split/split-assess-cli.ts +2 -2
- package/scripts/split/split-assess.ts +15 -0
- package/scripts/split/split-measure.ts +65 -0
- package/scripts/sync/claude-settings-merge.ts +8 -15
- package/scripts/sync/directory-copy-guard.ts +4 -9
- package/scripts/sync/file-content.ts +78 -8
- package/scripts/sync/sync-ai-files.ts +3 -5
- package/scripts/sync/sync-configs.ts +11 -23
- package/scripts/sync/sync-hook-safety.ts +4 -10
- package/scripts/sync/sync-main.ts +11 -0
- package/scripts/sync/sync.ts +25 -37
- package/scripts/test/test-declared-changed.ts +2 -2
- package/scripts/test/test-declared.ts +1 -1
- package/scripts/test/test-e2e-guard.ts +25 -1
- package/scripts/test/test-e2e-stamp.ts +38 -0
- package/scripts/test/test-pinned-logic.ts +145 -0
- package/scripts/test/test-pinned-main.ts +78 -0
- package/scripts/test/test-pinned.ts +100 -0
- package/scripts/test/test-read-map.ts +264 -0
- package/scripts/test/test-read-recording.ts +91 -0
- package/scripts/test/test-read-trace.ts +148 -0
- package/scripts/test/test-red-main.ts +12 -0
- package/scripts/test/test-red.ts +12 -30
- package/scripts/test/test-related-main.ts +18 -0
- package/scripts/test/test-related-record.ts +119 -0
- package/scripts/test/test-related-scope.ts +229 -43
- package/scripts/test/test-related.ts +300 -22
- package/scripts/test/test-unit-guard-main.ts +15 -0
- package/scripts/test/test-unit-guard.ts +42 -24
- package/scripts/test/unit-count-file.ts +37 -0
- package/scripts/test/unit-run-marker.ts +162 -0
- package/scripts/test/unit-setup-files.ts +51 -0
- package/scripts/test/unit-worker-share.ts +61 -149
- package/scripts/test/whole-suite-files.ts +63 -0
- package/scripts/time-runtime/time-background.ts +20 -2
- package/scripts/time-runtime/time-bundles.ts +4 -3
- package/scripts/time-runtime/time-density-hook.ts +8 -6
- package/scripts/time-runtime/time-followup-stage.ts +1 -3
- package/scripts/time-runtime/time-format.ts +5 -28
- package/scripts/time-runtime/time-markers.ts +10 -1
- package/scripts/time-runtime/time-shell.ts +97 -10
- package/scripts/time-runtime/time-transcript-line.ts +49 -62
- package/scripts/uninstall/uninstall.ts +1 -1
- package/scripts/version/bump-version-main.ts +12 -0
- package/scripts/version/bump-version.ts +25 -25
- package/scripts/version/effective-upstream.ts +6 -12
- package/scripts/version/latest-corepack.ts +17 -32
- package/scripts/version/latest-if-required-cli.ts +93 -0
- package/scripts/version/latest-lane-guard-main.ts +8 -0
- package/scripts/version/latest-lane-guard.ts +0 -4
- package/scripts/version/latest-scope-cli.ts +0 -9
- package/scripts/version/latest-scope-main.ts +12 -0
- package/scripts/version/latest-update.ts +5 -5
- package/scripts/version/npm-registry.ts +44 -6
- package/scripts/version/publishable-range-check.ts +5 -16
- package/scripts/version/release-age.ts +1 -1
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/release-window.ts +38 -0
- package/scripts/version/running-binary.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +3 -8
- package/scripts/version/version-targets.ts +6 -28
- package/templates/dependabot.yml +29 -0
- package/templates/gitignore +4 -1
- package/templates/pnpm-workspace.basic.yaml +1 -1
- package/templates/sonar-project.properties +3 -2
- package/templates/workflows/ci.yml +12 -4
- package/templates/workflows/github-release.yml +4 -0
- package/tsconfig.sonar.json +1 -1
- package/.ncurc.json +0 -3
- package/dist/hooks/batch-guard.js +0 -29
- package/dist/hooks/chunk-4KKD2UTK.js +0 -66
- package/dist/hooks/chunk-6TS2JD6D.js +0 -237
- package/dist/hooks/chunk-7DSBJVFH.js +0 -50
- package/dist/hooks/chunk-A65Y4YOR.js +0 -79
- package/dist/hooks/chunk-CDY4NFQG.js +0 -15
- package/dist/hooks/chunk-EFXE6C37.js +0 -81
- package/dist/hooks/chunk-GP4IEC4O.js +0 -20
- package/dist/hooks/chunk-JP45SOCJ.js +0 -56
- package/dist/hooks/chunk-LAOV7NLM.js +0 -45
- package/dist/hooks/chunk-M4HGZV7U.js +0 -83
- package/dist/hooks/chunk-PF2NZL2A.js +0 -131
- package/dist/hooks/chunk-PTICX4OL.js +0 -358
- package/dist/hooks/chunk-RT27QRJW.js +0 -6247
- package/dist/hooks/chunk-SYOTDYCI.js +0 -3533
- package/dist/hooks/chunk-UPUZF3AN.js +0 -279
- package/dist/hooks/chunk-WK6SMBIC.js +0 -3180
- package/dist/hooks/duplicate-read-guard.js +0 -30
- package/dist/hooks/investigation-guard.js +0 -29
- package/scripts/backlog/backlog-stalled-cli.ts +0 -30
- package/scripts/cost-runtime/cost-format.ts +0 -29
- package/scripts/init/basic-path-migration.ts +0 -24
- package/scripts/issue/issue-backlinks-cli.ts +0 -93
- package/scripts/josh/josh-commands-guard.ts +0 -38
- package/scripts/josh/josh-usage-automation.ts +0 -121
- package/scripts/josh/josh-usage-backlog.ts +0 -180
- package/scripts/josh/josh-usage-developer.ts +0 -230
- package/scripts/josh/josh-usage-hooks.ts +0 -38
- package/scripts/josh/josh-usage-issue.ts +0 -205
- package/scripts/josh/josh-usage-review.ts +0 -101
- package/scripts/josh/josh-usage-run.ts +0 -252
- package/scripts/josh/josh-usage-session.ts +0 -238
- package/scripts/josh/josh-usage-workflow.ts +0 -212
- package/scripts/lint/cross-file-recheck.ts +0 -155
- package/scripts/registry-migration/migrate-logic.ts +0 -237
- package/scripts/registry-migration/migrate.ts +0 -292
- package/scripts/registry-migration/user-npmrc.ts +0 -15
- package/scripts/rules/rule-guard.ts +0 -23
- package/scripts/rules/rule-registry-rows.ts +0 -292
- package/scripts/rules/rule-registry.ts +0 -112
- package/scripts/run/run-stranded-cli.ts +0 -27
|
@@ -0,0 +1,4306 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTEXT_CUT_THRESHOLD,
|
|
3
|
+
bash_triggers,
|
|
4
|
+
cost_cli,
|
|
5
|
+
cost_verdict,
|
|
6
|
+
doctor_consumer,
|
|
7
|
+
epic_busy,
|
|
8
|
+
filing_cap,
|
|
9
|
+
git_argv,
|
|
10
|
+
hook_decision,
|
|
11
|
+
interactive_ask,
|
|
12
|
+
lane_background,
|
|
13
|
+
lane_park,
|
|
14
|
+
pre_gate_cut,
|
|
15
|
+
project_checks,
|
|
16
|
+
repo_party,
|
|
17
|
+
run_cut,
|
|
18
|
+
run_cut_handoff,
|
|
19
|
+
run_liveness,
|
|
20
|
+
run_stage,
|
|
21
|
+
run_tail_rule,
|
|
22
|
+
shell_segments,
|
|
23
|
+
tail_commands
|
|
24
|
+
} from "./chunk-CWMBGKYW.js";
|
|
25
|
+
import {
|
|
26
|
+
core_weights,
|
|
27
|
+
cost_blocks,
|
|
28
|
+
time_batch_guard,
|
|
29
|
+
time_bundle_call,
|
|
30
|
+
time_density_hook,
|
|
31
|
+
time_hook_transcript,
|
|
32
|
+
time_shell,
|
|
33
|
+
time_spans
|
|
34
|
+
} from "./chunk-PT5R6UJD.js";
|
|
35
|
+
import {
|
|
36
|
+
agent_role_profile,
|
|
37
|
+
agent_session_role,
|
|
38
|
+
issue_watch,
|
|
39
|
+
issue_watch_gate,
|
|
40
|
+
lane_child_marker,
|
|
41
|
+
lane_registry,
|
|
42
|
+
run_headless,
|
|
43
|
+
run_hold,
|
|
44
|
+
run_preflight
|
|
45
|
+
} from "./chunk-YCDOIR3I.js";
|
|
46
|
+
import {
|
|
47
|
+
PACKAGE_JSON,
|
|
48
|
+
PORCELAIN_FLAG,
|
|
49
|
+
UNTRACKED_FILES_FLAG,
|
|
50
|
+
ancestor_directories,
|
|
51
|
+
cost_transcript,
|
|
52
|
+
figure_format,
|
|
53
|
+
file_reader,
|
|
54
|
+
git_command,
|
|
55
|
+
git_common_directory,
|
|
56
|
+
git_diff_reads,
|
|
57
|
+
git_spawn_sync,
|
|
58
|
+
issue_cite,
|
|
59
|
+
issue_number_shape,
|
|
60
|
+
json_value,
|
|
61
|
+
lane_paths,
|
|
62
|
+
package_manifest,
|
|
63
|
+
run_carry,
|
|
64
|
+
run_event_scope,
|
|
65
|
+
run_event_stream,
|
|
66
|
+
run_event_stream_emit,
|
|
67
|
+
session_cite
|
|
68
|
+
} from "./chunk-WR2KQAYZ.js";
|
|
69
|
+
import {
|
|
70
|
+
MS_PER_MINUTE,
|
|
71
|
+
MS_PER_SECOND,
|
|
72
|
+
error_text,
|
|
73
|
+
platform_temporary,
|
|
74
|
+
stamp_file
|
|
75
|
+
} from "./chunk-AZX4FGQ3.js";
|
|
76
|
+
|
|
77
|
+
// scripts/delegation/duplicate-read-guard.ts
|
|
78
|
+
import { statSync } from "node:fs";
|
|
79
|
+
|
|
80
|
+
// scripts/lane/lane-guard-policy.ts
|
|
81
|
+
var DEFAULT_MODE = "refuse";
|
|
82
|
+
var OFF_MODE = "off";
|
|
83
|
+
var LANE_GUARD_POLICY = [
|
|
84
|
+
{
|
|
85
|
+
id: "investigation",
|
|
86
|
+
mode_in_lane_child: "refuse",
|
|
87
|
+
because: "the notice was measured not to move the reading \u2014 52% of lane cost stayed in main-line exploration \u2014 and the refusal fires once per accumulation, a round trip the child survives as it survives the rule row, so the child is refused like the main line"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "batching",
|
|
91
|
+
mode_in_lane_child: "off",
|
|
92
|
+
because: "a refusal ends a child turn and every advisory notice was measured not to move the call density, so the guard is off and the density stays readable through `josh time:density`"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "duplicate-read",
|
|
96
|
+
mode_in_lane_child: "notice",
|
|
97
|
+
because: "a refusal ends a child turn, yet unchanged re-reads are measured in lane children above all, so the child is nudged to scroll up to the earlier read by a notice rather than killed"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "rule",
|
|
101
|
+
mode_in_lane_child: "refuse",
|
|
102
|
+
because: "it carries the lane-only rules a child depends on (`pre-gate-cut`, `implementation-cut`, `lane-park`) and the safety rules it must still obey, and a notice was rejected despite the collateral a refusal costs a batched sibling, because a cut carried as prose was measured never to fire"
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
var KNOWN_GUARD_IDS = LANE_GUARD_POLICY.map((entry) => entry.id);
|
|
106
|
+
function entry_for(id) {
|
|
107
|
+
return LANE_GUARD_POLICY.find((entry) => entry.id === id.trim());
|
|
108
|
+
}
|
|
109
|
+
function mode_in_lane_child(id) {
|
|
110
|
+
return entry_for(id)?.mode_in_lane_child ?? DEFAULT_MODE;
|
|
111
|
+
}
|
|
112
|
+
function mode_here(id, directory = process.cwd(), source = process.env) {
|
|
113
|
+
const lane_mode = mode_in_lane_child(id);
|
|
114
|
+
if (lane_mode === DEFAULT_MODE) return DEFAULT_MODE;
|
|
115
|
+
return lane_child_marker.is_child_of(directory, source) ? lane_mode : DEFAULT_MODE;
|
|
116
|
+
}
|
|
117
|
+
function is_suppressed_here(id, directory = process.cwd(), source = process.env) {
|
|
118
|
+
return mode_here(id, directory, source) === OFF_MODE;
|
|
119
|
+
}
|
|
120
|
+
var lane_guard_policy = {
|
|
121
|
+
LANE_GUARD_POLICY,
|
|
122
|
+
KNOWN_GUARD_IDS,
|
|
123
|
+
entry_for,
|
|
124
|
+
mode_in_lane_child,
|
|
125
|
+
mode_here,
|
|
126
|
+
is_suppressed_here
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// scripts/delegation/investigation-reads.ts
|
|
130
|
+
import path2 from "node:path";
|
|
131
|
+
|
|
132
|
+
// scripts/document/capped-print-part.ts
|
|
133
|
+
import path from "node:path";
|
|
134
|
+
var DIRECTORY_PREFIX = "josh-capped-print-";
|
|
135
|
+
var PART_PREFIX = "part-";
|
|
136
|
+
var PART_SUFFIX = ".md";
|
|
137
|
+
var PART_PATTERN = /^part-[1-9]\d*\.md$/u;
|
|
138
|
+
function part_name(index) {
|
|
139
|
+
return `${PART_PREFIX}${String(index + 1)}${PART_SUFFIX}`;
|
|
140
|
+
}
|
|
141
|
+
function is_part_file(absolute) {
|
|
142
|
+
const parent = path.basename(path.dirname(absolute));
|
|
143
|
+
return parent.startsWith(DIRECTORY_PREFIX) && PART_PATTERN.test(path.basename(absolute));
|
|
144
|
+
}
|
|
145
|
+
var capped_print_part = { DIRECTORY_PREFIX, part_name, is_part_file };
|
|
146
|
+
|
|
147
|
+
// scripts/delegation/delegation-policy.ts
|
|
148
|
+
var DELEGATE_VERDICT = "delegate";
|
|
149
|
+
var KEEP_VERDICT = "keep";
|
|
150
|
+
var INVESTIGATION_FILE_THRESHOLD = 3;
|
|
151
|
+
var INVESTIGATION_SEARCH_TURN_THRESHOLD = 3;
|
|
152
|
+
var INVESTIGATOR_AGENT = "investigator";
|
|
153
|
+
var INVESTIGATION_LIMIT = `${String(INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit`;
|
|
154
|
+
var INVESTIGATION_STEP = `Dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text.`;
|
|
155
|
+
var DELEGATABLE_STEPS = [
|
|
156
|
+
{
|
|
157
|
+
name: "gate-fix",
|
|
158
|
+
does: "apply the fixes the verification gate already named \u2014 a lint rule, a type error, a spelling the dictionary rejects",
|
|
159
|
+
verifier: "`pnpm josh gate` is re-run; a wrong fix fails it again, and the failure names the file"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "epic-child",
|
|
163
|
+
// One row, two entry points: an epic's child and one named issue of a `backlogrun` are the same
|
|
164
|
+
// unit — same brief, same summary, same verifier. A second row for the batch would be the clone
|
|
165
|
+
// `CLAUDE.md` prohibits.
|
|
166
|
+
does: "run one child of a batch end to end in an isolated unit \u2014 an epic's child or one named issue of a `backlogrun` alike \u2014 plan, verification gate, PR, merge \u2014 and return only its summary to the parent loop",
|
|
167
|
+
verifier: "the parent reads the child's state from GitHub with `pnpm josh issue:state`, not from the summary; a child reported done but not merged is still open, which is the failure showing rather than a run continuing, and its own gate, `/code-review` and CI ran inside the unit before `followup` would touch the PR"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "followup-filing",
|
|
171
|
+
// The late-run follow-up filing chain — `issue:file` (which scouts, lints, labels and runs
|
|
172
|
+
// epic:bundle itself) → epic --add — is a dependency chain the batching guard
|
|
173
|
+
// correctly keeps one call per step, and what makes it expensive is the ~340k context it runs in
|
|
174
|
+
// at the tail of a run, not the step count. A fresh unit runs the same
|
|
175
|
+
// chain at a small context, and the review round cap already composed the finding text, so what
|
|
176
|
+
// the unit is handed is mechanical: run the commands and act on their deterministic answers. This
|
|
177
|
+
// is not the batching guard's business — delegation only moves the chain to a cheaper context.
|
|
178
|
+
does: "run the late-run follow-up filing chain in an isolated unit \u2014 issue:file (scout, lint, labels, epic:bundle), then epic --add \u2014 given the finding text the parent already composed, and return the new Issue number and the epic:bundle answer",
|
|
179
|
+
verifier: "the parent reads the filed Issue from GitHub with `pnpm josh issue:state <new>`, not the unit's summary; a follow-up reported filed but not created is still absent, the way `epic-child`'s reported-done-but-not-merged child is still open. The finding text came from the parent and `issue:file`'s steps are deterministic, so the unit's work is the mechanical execution `issue:state` confirms"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "implementation-unit",
|
|
183
|
+
// The Step 0 change list already decided *what* to change — that design stays in the main line,
|
|
184
|
+
// which is why `design` is rejected below. What is delegated is the *writing* of one unit of that
|
|
185
|
+
// list, and only a unit whose files are disjoint from every other dispatched unit: two subagents
|
|
186
|
+
// editing one file in parallel race on it. `pnpm josh fanout` confirms that disjointness
|
|
187
|
+
// mechanically, so the split is read off the file sets rather than judged.
|
|
188
|
+
does: "implement one file-disjoint unit of the Step 0 change list in an isolated subagent \u2014 the files it edits shared with no other dispatched unit, confirmed by `pnpm josh fanout` \u2014 and return the edited files, never a summary standing in for the diff; the main line keeps integration and the verification gate. Units that share a file, or an Issue that will not split, stay serial",
|
|
189
|
+
verifier: "the parent runs the whole integrated change through `pnpm josh gate` and a `/code-review` subagent it would run anyway \u2014 a unit's mistake fails the same gate and review a serial edit passes through, so delegating writes nothing the backstop does not already catch, and the `pnpm josh fanout` disjointness removes the one failure parallelism adds, two units colliding on a file"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "survey",
|
|
193
|
+
does: "read across many files and report where something appears \u2014 every reference to a symbol, which documents carry a marker",
|
|
194
|
+
verifier: "the reported locations are checked directly; a fabricated or missed location does not survive one `grep` of what it claimed"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "investigation",
|
|
198
|
+
// Not `survey`: that row reports *where* something appears and is checked by one `grep`, while
|
|
199
|
+
// this one reports *how the subject works* and is checked by opening the lines it cited. The two
|
|
200
|
+
// verifiers differ, so widening `survey` would put both in one field and lose the boundary below.
|
|
201
|
+
// **No ordinal.** `${N}rd` would print `1rd` for any other threshold, past a test that only looks
|
|
202
|
+
// for the numeral, so the count is phrased as a plain "N or more".
|
|
203
|
+
does: `read the Issue's subject to find out how it currently works \u2014 once ${String(INVESTIGATION_FILE_THRESHOLD)} or more files the run will not edit have to be read, or ${String(INVESTIGATION_SEARCH_TURN_THRESHOLD)} or more search turns run without an edit \u2014 and return the conclusion plus the \`file:line\` citations that support it, never the file text, which would put the cost back where it was. A throwaway probe script is written, run and deleted inside the unit, which returns its output alone`,
|
|
204
|
+
verifier: "the parent opens the cited lines; a conclusion those lines do not support fails there, and reading a handful of cited regions costs far less than redoing the reading. The unit reports what the code does and never what to change \u2014 deciding that is `diagnosis`, which stays kept, so a unit returning a root cause would be delegating the rejected row under this name"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "lane-failure-investigation",
|
|
208
|
+
// The `backlogrun` parent's counterpart of `investigation`. The parent
|
|
209
|
+
// orchestrates and never implements, yet it read a failed lane's logs, records and sources in its
|
|
210
|
+
// own context — about 150 grep / cat / sed calls on 2026-10-02 — and every later request of the
|
|
211
|
+
// batch re-read them. The verifier is `investigation`'s, and so is the boundary: the unit reports
|
|
212
|
+
// what happened, and what the parent does about it (park, re-dispatch, file) stays its own call.
|
|
213
|
+
does: "find out why a lane's child stopped, failed or conflicted \u2014 its output log, run records, PR checks and the files they name \u2014 and return the conclusion plus the `file:line` or log-line citations that support it, never the text read, so the batch parent's context stays the size its orchestration needs",
|
|
214
|
+
verifier: "the parent opens the cited lines and re-reads the child with `pnpm josh issue:state`; a conclusion those do not support fails there. The unit reports what happened and never what to do \u2014 parking, re-dispatching or filing stays with the parent, so a wrong account is caught before it is acted on"
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
var REJECTED_STEPS = [
|
|
218
|
+
{
|
|
219
|
+
name: "notify-body",
|
|
220
|
+
because: "no verifier. A notification with a wrong body is sent and read as though it were right; nothing in the run disagrees with it afterwards"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "issue-comment",
|
|
224
|
+
because: "no verifier. A decision log or completion comment that says the wrong thing is the record, so there is nothing left to check it against"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: "status-read",
|
|
228
|
+
because: "a misread routes the run to the wrong child, and no later step disagrees. Running the status command is already cheap; what is expensive is acting on it, which is not this step"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "diagnosis",
|
|
232
|
+
because: "a wrong root cause produces a fix that passes the gate and leaves the defect"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "design",
|
|
236
|
+
because: "the cost of a wrong design is paid by every step after it"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "split-assessment",
|
|
240
|
+
because: "a missed split widens one Issue into a batch nobody authorized"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "review",
|
|
244
|
+
because: "the review is the last thing between a defect and a merge; a cheaper one finds less"
|
|
245
|
+
}
|
|
246
|
+
];
|
|
247
|
+
function find_step(name) {
|
|
248
|
+
return DELEGATABLE_STEPS.find((step) => step.name === name.trim());
|
|
249
|
+
}
|
|
250
|
+
function verdict_for(name) {
|
|
251
|
+
return find_step(name) === void 0 ? KEEP_VERDICT : DELEGATE_VERDICT;
|
|
252
|
+
}
|
|
253
|
+
function rejection_for(name) {
|
|
254
|
+
return REJECTED_STEPS.find((step) => step.name === name.trim());
|
|
255
|
+
}
|
|
256
|
+
function reason_for(name) {
|
|
257
|
+
const step = find_step(name);
|
|
258
|
+
if (step !== void 0) return `delegatable: ${step.verifier}`;
|
|
259
|
+
const rejected = rejection_for(name);
|
|
260
|
+
if (rejected !== void 0) return `kept deliberately: ${rejected.because}`;
|
|
261
|
+
return "kept by default: not on the delegatable list, and the list is the whole of it";
|
|
262
|
+
}
|
|
263
|
+
var delegation_policy = {
|
|
264
|
+
DELEGATE_VERDICT,
|
|
265
|
+
INVESTIGATION_FILE_THRESHOLD,
|
|
266
|
+
INVESTIGATION_LIMIT,
|
|
267
|
+
INVESTIGATION_SEARCH_TURN_THRESHOLD,
|
|
268
|
+
INVESTIGATION_STEP,
|
|
269
|
+
INVESTIGATOR_AGENT,
|
|
270
|
+
KEEP_VERDICT,
|
|
271
|
+
DELEGATABLE_STEPS,
|
|
272
|
+
REJECTED_STEPS,
|
|
273
|
+
find_step,
|
|
274
|
+
verdict_for,
|
|
275
|
+
reason_for
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// scripts/delegation/investigation-reads.ts
|
|
279
|
+
var READ_TOOLS = /* @__PURE__ */ new Set(["Read"]);
|
|
280
|
+
var DELEGATION_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
281
|
+
var CONTENT_READ_COMMANDS = [
|
|
282
|
+
"bat",
|
|
283
|
+
"cat",
|
|
284
|
+
"head",
|
|
285
|
+
"less",
|
|
286
|
+
"more",
|
|
287
|
+
"nl",
|
|
288
|
+
"sed",
|
|
289
|
+
"tail"
|
|
290
|
+
];
|
|
291
|
+
var CONTENT_READ_LABELS = new Set(
|
|
292
|
+
CONTENT_READ_COMMANDS.map((command) => time_shell.bash_label(command))
|
|
293
|
+
);
|
|
294
|
+
var SEARCH_COMMANDS = ["fd", "find", "grep", "rg"];
|
|
295
|
+
var SEARCH_LABELS = new Set(
|
|
296
|
+
SEARCH_COMMANDS.map((command) => time_shell.bash_label(command))
|
|
297
|
+
);
|
|
298
|
+
var { INVESTIGATOR_AGENT: INVESTIGATOR_AGENT2 } = delegation_policy;
|
|
299
|
+
var REASON = `\u26D4 investigation: ${delegation_policy.INVESTIGATION_LIMIT}. ${delegation_policy.INVESTIGATION_STEP} Keep main-line reads to files you will edit; every read-only search turn counts. \`.claude/skills/workflow-commands/delegation.md\``;
|
|
300
|
+
var INSTRUCTION_PATHS = ["prompts", ".claude/skills"];
|
|
301
|
+
var INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "AGENTS.md", "GEMINI.md"]);
|
|
302
|
+
var REPOSITORY_ROOT = process.cwd();
|
|
303
|
+
function resolved(target) {
|
|
304
|
+
return path2.resolve(REPOSITORY_ROOT, target);
|
|
305
|
+
}
|
|
306
|
+
function repository_relative(absolute) {
|
|
307
|
+
return path2.relative(REPOSITORY_ROOT, absolute);
|
|
308
|
+
}
|
|
309
|
+
function is_instruction_document(target) {
|
|
310
|
+
const relative = repository_relative(resolved(target));
|
|
311
|
+
const segments = relative.split(path2.sep);
|
|
312
|
+
return INSTRUCTION_FILES.has(segments.at(-1) ?? "") || INSTRUCTION_PATHS.some((prefix) => relative.startsWith(`${prefix}${path2.sep}`));
|
|
313
|
+
}
|
|
314
|
+
var SESSION_TEMP_SEGMENT = /^claude-\d+$/u;
|
|
315
|
+
var SESSION_STATE_SEGMENTS = cost_transcript.TRANSCRIPT_ROOT.split(path2.sep);
|
|
316
|
+
var HARNESS_OUTPUT_DIRECTORY = "tasks";
|
|
317
|
+
var HARNESS_OUTPUT_EXTENSION = ".output";
|
|
318
|
+
var FILE_SEGMENT_INDEX = -1;
|
|
319
|
+
var PARENT_SEGMENT_INDEX = -2;
|
|
320
|
+
function has_state_root(segments, index) {
|
|
321
|
+
return SESSION_STATE_SEGMENTS.every((segment, offset) => segments[index + offset] === segment);
|
|
322
|
+
}
|
|
323
|
+
function is_harness_output(segments) {
|
|
324
|
+
return segments.some((segment) => SESSION_TEMP_SEGMENT.test(segment)) && segments.at(PARENT_SEGMENT_INDEX) === HARNESS_OUTPUT_DIRECTORY && (segments.at(FILE_SEGMENT_INDEX) ?? "").endsWith(HARNESS_OUTPUT_EXTENSION);
|
|
325
|
+
}
|
|
326
|
+
function is_outside_repository(absolute) {
|
|
327
|
+
return repository_relative(absolute).startsWith("..");
|
|
328
|
+
}
|
|
329
|
+
function is_session_artifact(target) {
|
|
330
|
+
const absolute = resolved(target);
|
|
331
|
+
const segments = absolute.split(path2.sep);
|
|
332
|
+
if (!is_outside_repository(absolute)) return false;
|
|
333
|
+
return segments.some((_segment, index) => has_state_root(segments, index)) || is_harness_output(segments) || capped_print_part.is_part_file(absolute);
|
|
334
|
+
}
|
|
335
|
+
function is_subject_file(target) {
|
|
336
|
+
return !is_instruction_document(target) && !is_session_artifact(target);
|
|
337
|
+
}
|
|
338
|
+
function is_nameable_file(target) {
|
|
339
|
+
return !target.includes("*") && !target.includes("?");
|
|
340
|
+
}
|
|
341
|
+
function is_content_read(label) {
|
|
342
|
+
return READ_TOOLS.has(label) || CONTENT_READ_LABELS.has(label);
|
|
343
|
+
}
|
|
344
|
+
function subject_targets(targets) {
|
|
345
|
+
return targets.filter((target) => is_nameable_file(target) && is_subject_file(target)).map((target) => resolved(target));
|
|
346
|
+
}
|
|
347
|
+
function call_targets(call) {
|
|
348
|
+
return subject_targets(time_bundle_call.call_facts(call.name, call.input).targets);
|
|
349
|
+
}
|
|
350
|
+
function delegation_instant(span) {
|
|
351
|
+
return span === void 0 ? hook_decision.NEVER_MS : span.ended_ms;
|
|
352
|
+
}
|
|
353
|
+
function is_failed_outcome(span) {
|
|
354
|
+
return span.outcome === time_spans.FAILED_OUTCOME;
|
|
355
|
+
}
|
|
356
|
+
function edited_files(spans) {
|
|
357
|
+
const written = spans.filter((span) => !is_failed_outcome(span)).flatMap((span) => subject_targets(span.writes));
|
|
358
|
+
return new Set(written);
|
|
359
|
+
}
|
|
360
|
+
function add_all(pending, targets, at_ms) {
|
|
361
|
+
for (const target of targets) if (!pending.has(target)) pending.set(target, at_ms);
|
|
362
|
+
}
|
|
363
|
+
function is_read_only_search(span) {
|
|
364
|
+
return SEARCH_LABELS.has(span.label) && span.is_bundleable && !span.may_write;
|
|
365
|
+
}
|
|
366
|
+
function search_turn_key(searches, span) {
|
|
367
|
+
if (span.message_id !== time_spans.NO_MESSAGE_ID) return span.message_id;
|
|
368
|
+
return `${time_spans.NO_MESSAGE_ID}#${String(searches.size)}`;
|
|
369
|
+
}
|
|
370
|
+
function apply_search(searches, span) {
|
|
371
|
+
if (span.is_writing) {
|
|
372
|
+
searches.clear();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const turn = search_turn_key(searches, span);
|
|
376
|
+
if (is_read_only_search(span) && !searches.has(turn)) searches.set(turn, span.ended_ms);
|
|
377
|
+
}
|
|
378
|
+
function apply_span(accumulation, span, edited) {
|
|
379
|
+
if (DELEGATION_TOOLS.has(span.label)) {
|
|
380
|
+
accumulation.pending.clear();
|
|
381
|
+
accumulation.searches.clear();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (is_failed_outcome(span)) return;
|
|
385
|
+
apply_search(accumulation.searches, span);
|
|
386
|
+
if (!is_content_read(span.label)) return;
|
|
387
|
+
const fresh = subject_targets(span.targets).filter((target) => !edited.has(target));
|
|
388
|
+
add_all(accumulation.pending, fresh, span.ended_ms);
|
|
389
|
+
}
|
|
390
|
+
function last_delegation_ms(spans) {
|
|
391
|
+
return delegation_instant(spans.findLast((span) => DELEGATION_TOOLS.has(span.label)));
|
|
392
|
+
}
|
|
393
|
+
function pending_after(pending, since_ms) {
|
|
394
|
+
return [...pending.values()].filter((at_ms) => at_ms > since_ms).length;
|
|
395
|
+
}
|
|
396
|
+
function tally_of(text, since_ms = hook_decision.NEVER_MS) {
|
|
397
|
+
const { spans, started_ms } = time_spans.parse_timeline(text);
|
|
398
|
+
const edited = edited_files(spans);
|
|
399
|
+
const accumulation = { pending: /* @__PURE__ */ new Map(), searches: /* @__PURE__ */ new Map() };
|
|
400
|
+
for (const span of spans) apply_span(accumulation, span, edited);
|
|
401
|
+
return {
|
|
402
|
+
pending: [...accumulation.pending.keys()],
|
|
403
|
+
pending_since: pending_after(accumulation.pending, since_ms),
|
|
404
|
+
reset_ms: last_delegation_ms(spans),
|
|
405
|
+
window_start_ms: started_ms,
|
|
406
|
+
searches: accumulation.searches.size,
|
|
407
|
+
searches_since: pending_after(accumulation.searches, since_ms),
|
|
408
|
+
edited
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function projected_count(pending, call, edited = /* @__PURE__ */ new Set()) {
|
|
412
|
+
return (/* @__PURE__ */ new Set([...pending, ...call_targets(call).filter((target) => !edited.has(target))])).size;
|
|
413
|
+
}
|
|
414
|
+
function is_at_threshold(pending_count) {
|
|
415
|
+
return pending_count + 1 >= delegation_policy.INVESTIGATION_FILE_THRESHOLD;
|
|
416
|
+
}
|
|
417
|
+
function is_at_search_threshold(search_count) {
|
|
418
|
+
return search_count + 1 >= delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD;
|
|
419
|
+
}
|
|
420
|
+
function has_cleared_the_disarm(tally, refused_at_ms) {
|
|
421
|
+
return tally.reset_ms > refused_at_ms || refused_at_ms < tally.window_start_ms;
|
|
422
|
+
}
|
|
423
|
+
function is_rearmed(tally, refused_at_ms) {
|
|
424
|
+
if (refused_at_ms === hook_decision.NEVER_MS || has_cleared_the_disarm(tally, refused_at_ms)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
return is_at_threshold(tally.pending_since) || is_at_search_threshold(tally.searches_since);
|
|
428
|
+
}
|
|
429
|
+
function is_unit_transcript(transcript) {
|
|
430
|
+
return transcript.split(path2.sep).includes(cost_transcript.UNIT_DIRECTORY);
|
|
431
|
+
}
|
|
432
|
+
function is_unit_run(run) {
|
|
433
|
+
return run !== void 0 && is_unit_transcript(run.transcript);
|
|
434
|
+
}
|
|
435
|
+
function is_read_only_command(call, labels) {
|
|
436
|
+
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
437
|
+
const label = time_shell.bash_label(time_shell.bash_command(call.input));
|
|
438
|
+
return labels.has(label) && time_batch_guard.is_read_only_call(call);
|
|
439
|
+
}
|
|
440
|
+
function is_search_call(call) {
|
|
441
|
+
return is_read_only_command(call, SEARCH_LABELS);
|
|
442
|
+
}
|
|
443
|
+
function is_refusable_call(call) {
|
|
444
|
+
if (is_search_call(call)) return true;
|
|
445
|
+
if (call_targets(call).length === 0) return false;
|
|
446
|
+
if (READ_TOOLS.has(call.name)) return true;
|
|
447
|
+
return is_read_only_command(call, CONTENT_READ_LABELS);
|
|
448
|
+
}
|
|
449
|
+
function is_growing_past_threshold(tally, call) {
|
|
450
|
+
if (is_search_call(call)) return is_at_search_threshold(tally.searches);
|
|
451
|
+
if (!is_at_threshold(tally.pending.length)) return false;
|
|
452
|
+
return projected_count(tally.pending, call, tally.edited) > tally.pending.length;
|
|
453
|
+
}
|
|
454
|
+
function should_block(text, call, refused_at_ms, run) {
|
|
455
|
+
if (!is_refusable_call(call) || is_unit_run(run)) return false;
|
|
456
|
+
const tally = tally_of(text, refused_at_ms);
|
|
457
|
+
return is_growing_past_threshold(tally, call) && is_rearmed(tally, refused_at_ms);
|
|
458
|
+
}
|
|
459
|
+
var investigation_reads = {
|
|
460
|
+
DELEGATION_TOOLS,
|
|
461
|
+
INVESTIGATOR_AGENT: INVESTIGATOR_AGENT2,
|
|
462
|
+
READ_TOOLS,
|
|
463
|
+
REASON,
|
|
464
|
+
is_at_threshold,
|
|
465
|
+
is_instruction_document,
|
|
466
|
+
is_rearmed,
|
|
467
|
+
is_refusable_call,
|
|
468
|
+
is_session_artifact,
|
|
469
|
+
is_subject_file,
|
|
470
|
+
projected_count,
|
|
471
|
+
resolved,
|
|
472
|
+
should_block,
|
|
473
|
+
tally_of
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// scripts/delegation/duplicate-read-guard.ts
|
|
477
|
+
var SWITCH_ENV_KEY = "JOSH_DUPLICATE_READ_GUARD";
|
|
478
|
+
var LANE_GUARD_ID = "duplicate-read";
|
|
479
|
+
var STAMP_PREFIX = "josh-duplicate-read-guard-";
|
|
480
|
+
var NOTICE_STAMP_PREFIX = "josh-duplicate-read-notice-";
|
|
481
|
+
var READ_TOOL = "Read";
|
|
482
|
+
var GLOB_CHARACTERS = /[*?]/u;
|
|
483
|
+
var REASON2 = `\u26D4 duplicate read: this file was already read in this session and has not changed since (its mtime predates that read), so reading it again returns nothing new. Scroll up to the earlier result instead of re-reading it. A read with a new offset/limit, or of a file that has since changed, is allowed; set \`JOSH_DUPLICATE_READ_GUARD=off\` to disable this guard.`;
|
|
484
|
+
var NOTICE = `\u26A0 duplicate read: this file was already read in this session and has not changed since, so re-reading it returns nothing new \u2014 scroll up to the earlier result instead of re-reading it. Set \`JOSH_DUPLICATE_READ_GUARD=off\` to silence this.`;
|
|
485
|
+
function is_paginated(input) {
|
|
486
|
+
return input["offset"] !== void 0 || input["limit"] !== void 0;
|
|
487
|
+
}
|
|
488
|
+
function names_one_file(file_path) {
|
|
489
|
+
return typeof file_path === "string" && file_path !== "" && !GLOB_CHARACTERS.test(file_path);
|
|
490
|
+
}
|
|
491
|
+
function read_target(call) {
|
|
492
|
+
if (call.name !== READ_TOOL || !json_value.is_record(call.input)) return void 0;
|
|
493
|
+
const { input } = call;
|
|
494
|
+
if (is_paginated(input) || !names_one_file(input["file_path"])) return void 0;
|
|
495
|
+
return investigation_reads.resolved(input["file_path"]);
|
|
496
|
+
}
|
|
497
|
+
function is_refusable_call2(call) {
|
|
498
|
+
return read_target(call) !== void 0;
|
|
499
|
+
}
|
|
500
|
+
function is_successful_read(span) {
|
|
501
|
+
const is_read2 = investigation_reads.READ_TOOLS.has(span.label);
|
|
502
|
+
return is_read2 && span.outcome !== time_spans.FAILED_OUTCOME;
|
|
503
|
+
}
|
|
504
|
+
function reads_target(span, target) {
|
|
505
|
+
return span.targets.some((one) => investigation_reads.resolved(one) === target);
|
|
506
|
+
}
|
|
507
|
+
function last_read_ms(spans, target) {
|
|
508
|
+
const times = spans.filter((span) => is_successful_read(span) && reads_target(span, target)).map((span) => span.ended_ms);
|
|
509
|
+
return times.length === 0 ? hook_decision.NEVER_MS : Math.max(...times);
|
|
510
|
+
}
|
|
511
|
+
function file_mtime_ms(target) {
|
|
512
|
+
try {
|
|
513
|
+
return statSync(target).mtimeMs;
|
|
514
|
+
} catch {
|
|
515
|
+
return void 0;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function read_window(tail, target) {
|
|
519
|
+
const { spans, started_ms } = time_spans.parse_timeline(tail);
|
|
520
|
+
return { last_ms: last_read_ms(spans, target), window_start_ms: started_ms };
|
|
521
|
+
}
|
|
522
|
+
function is_unchanged_reread(last_ms, mtime) {
|
|
523
|
+
if (mtime === void 0 || last_ms === hook_decision.NEVER_MS) return false;
|
|
524
|
+
return mtime < last_ms;
|
|
525
|
+
}
|
|
526
|
+
function is_rearmed2(window, fired_at_ms) {
|
|
527
|
+
if (fired_at_ms === hook_decision.NEVER_MS || fired_at_ms < window.window_start_ms) return true;
|
|
528
|
+
return window.last_ms > fired_at_ms;
|
|
529
|
+
}
|
|
530
|
+
function is_redundant_reread(tail, call, fired_at_ms) {
|
|
531
|
+
const target = read_target(call);
|
|
532
|
+
if (target === void 0) return false;
|
|
533
|
+
const window = read_window(tail, target);
|
|
534
|
+
if (!is_unchanged_reread(window.last_ms, file_mtime_ms(target))) return false;
|
|
535
|
+
return is_rearmed2(window, fired_at_ms);
|
|
536
|
+
}
|
|
537
|
+
function duplicate_mode() {
|
|
538
|
+
return lane_guard_policy.mode_here(LANE_GUARD_ID);
|
|
539
|
+
}
|
|
540
|
+
function is_candidate(call) {
|
|
541
|
+
return is_refusable_call2(call) && duplicate_mode() !== "off";
|
|
542
|
+
}
|
|
543
|
+
function should_block_here(tail, call, refused_at_ms) {
|
|
544
|
+
if (duplicate_mode() !== "refuse") return false;
|
|
545
|
+
return is_redundant_reread(tail, call, refused_at_ms);
|
|
546
|
+
}
|
|
547
|
+
function should_notify_here(tail, call, notified_at_ms) {
|
|
548
|
+
if (duplicate_mode() !== "notice") return false;
|
|
549
|
+
return is_redundant_reread(tail, call, notified_at_ms);
|
|
550
|
+
}
|
|
551
|
+
var GUARD = hook_decision.create_transcript_guard({
|
|
552
|
+
prefix: STAMP_PREFIX,
|
|
553
|
+
switch_key: SWITCH_ENV_KEY,
|
|
554
|
+
is_candidate,
|
|
555
|
+
should_block: should_block_here,
|
|
556
|
+
reason: REASON2,
|
|
557
|
+
notify: {
|
|
558
|
+
prefix: NOTICE_STAMP_PREFIX,
|
|
559
|
+
should_notify: should_notify_here,
|
|
560
|
+
text: () => NOTICE
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
var duplicate_read_refusal = GUARD.refusal;
|
|
564
|
+
var duplicate_read_outcome = GUARD.outcome;
|
|
565
|
+
var { is_enabled, refusal_path } = GUARD;
|
|
566
|
+
var notice_refusal_path = hook_decision.create_refusal_stamp(NOTICE_STAMP_PREFIX).path;
|
|
567
|
+
var { deny_envelope, DISABLED_VALUES } = hook_decision;
|
|
568
|
+
|
|
569
|
+
// scripts/delegation/investigation-guard.ts
|
|
570
|
+
var SWITCH_ENV_KEY2 = "JOSH_INVESTIGATION_GUARD";
|
|
571
|
+
var STAMP_PREFIX2 = "josh-investigation-guard-";
|
|
572
|
+
function should_block2(tail, call, refused_at_ms, run) {
|
|
573
|
+
if (agent_session_role.is_reviewer() || lane_guard_policy.mode_here("investigation") !== "refuse") {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
return investigation_reads.should_block(tail, call, refused_at_ms, run);
|
|
577
|
+
}
|
|
578
|
+
var GUARD2 = hook_decision.create_transcript_guard({
|
|
579
|
+
prefix: STAMP_PREFIX2,
|
|
580
|
+
switch_key: SWITCH_ENV_KEY2,
|
|
581
|
+
is_candidate: investigation_reads.is_refusable_call,
|
|
582
|
+
should_block: should_block2,
|
|
583
|
+
reason: investigation_reads.REASON
|
|
584
|
+
});
|
|
585
|
+
var investigation_refusal = GUARD2.refusal;
|
|
586
|
+
var investigation_outcome = GUARD2.outcome;
|
|
587
|
+
var { is_enabled: is_enabled2, refusal_path: refusal_path2 } = GUARD2;
|
|
588
|
+
var { deny_envelope: deny_envelope2, load_environment_file, DISABLED_VALUES: DISABLED_VALUES2 } = hook_decision;
|
|
589
|
+
|
|
590
|
+
// scripts/issue/issue-watch-hook.ts
|
|
591
|
+
var POLL_INTERVAL_MS = 18e4;
|
|
592
|
+
var POLL_TIMEOUT_MS = 5e3;
|
|
593
|
+
function is_new_to_run(record2) {
|
|
594
|
+
return record2?.pending !== void 0 && record2.pending !== record2.shown;
|
|
595
|
+
}
|
|
596
|
+
async function new_update_reason(now) {
|
|
597
|
+
const record2 = await issue_watch.poll({
|
|
598
|
+
interval_ms: POLL_INTERVAL_MS,
|
|
599
|
+
timeout_ms: POLL_TIMEOUT_MS,
|
|
600
|
+
now
|
|
601
|
+
});
|
|
602
|
+
if (!is_new_to_run(record2)) return void 0;
|
|
603
|
+
issue_watch.mark_shown(record2);
|
|
604
|
+
return issue_watch_gate.unread_message(record2);
|
|
605
|
+
}
|
|
606
|
+
async function watch_reason(now = /* @__PURE__ */ new Date()) {
|
|
607
|
+
try {
|
|
608
|
+
return await new_update_reason(now);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
error_text.trace_swallowed("issue_watch_hook.watch_reason", error);
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
var issue_watch_hook = { POLL_INTERVAL_MS, watch_reason };
|
|
615
|
+
|
|
616
|
+
// scripts/run/event/run-event-plan.ts
|
|
617
|
+
var KIND = run_event_stream.EVENT_KIND.PLAN;
|
|
618
|
+
var PLAN_VERB = "planned";
|
|
619
|
+
function plan_text(issue) {
|
|
620
|
+
return `${PLAN_VERB} ${issue_cite.plain(issue)}`;
|
|
621
|
+
}
|
|
622
|
+
function has_plan(events, issue) {
|
|
623
|
+
return run_event_scope.since_own_launch(events, issue).some((event) => event.kind === KIND && run_event_scope.issue_named(event) === issue);
|
|
624
|
+
}
|
|
625
|
+
async function emit_plan(issue) {
|
|
626
|
+
if (has_plan(await run_event_stream_emit.invocation_or_all_events(), issue)) return;
|
|
627
|
+
await run_event_stream_emit.emit(KIND, plan_text(issue));
|
|
628
|
+
}
|
|
629
|
+
var run_event_plan = { emit_plan, plan_text };
|
|
630
|
+
|
|
631
|
+
// scripts/lane/lane-phase.ts
|
|
632
|
+
async function mark_implement(directory = process.cwd(), source = process.env) {
|
|
633
|
+
const issue = lane_child_marker.marked_issue(source);
|
|
634
|
+
if (issue === void 0 || !lane_child_marker.is_child_of(directory, source)) return;
|
|
635
|
+
await run_event_plan.emit_plan(issue);
|
|
636
|
+
await run_event_stream_emit.emit(
|
|
637
|
+
run_event_stream.EVENT_KIND.LANE_PHASE,
|
|
638
|
+
`${issue_cite.plain(issue)} implement`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
var lane_phase = { mark_implement };
|
|
642
|
+
|
|
643
|
+
// scripts/rules/agent-model.ts
|
|
644
|
+
var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
645
|
+
var GENERAL_PURPOSE = "general-purpose";
|
|
646
|
+
function is_general_purpose(call) {
|
|
647
|
+
if (!SUBAGENT_TOOLS.has(call.name) || !json_value.is_record(call.input)) return false;
|
|
648
|
+
const type = call.input["subagent_type"];
|
|
649
|
+
return type === void 0 || type === GENERAL_PURPOSE;
|
|
650
|
+
}
|
|
651
|
+
function names_model(call) {
|
|
652
|
+
const model = json_value.is_record(call.input) ? call.input["model"] : void 0;
|
|
653
|
+
return typeof model === "string" && model.length > 0;
|
|
654
|
+
}
|
|
655
|
+
function is_unnamed_model(call) {
|
|
656
|
+
return is_general_purpose(call) && !names_model(call);
|
|
657
|
+
}
|
|
658
|
+
function keeps_model(call) {
|
|
659
|
+
return is_general_purpose(call) && names_model(call);
|
|
660
|
+
}
|
|
661
|
+
var AGENT_MODEL_REASON = "\u26D4 agent model: a `general-purpose` subagent with no `model` inherits the parent model. Reissue the call with `model` set \u2014 `sonnet` for routine work, `opus` for judgment such as a review. It fires on every occurrence (`.claude/skills/workflow-commands/delegation.md`).";
|
|
662
|
+
var ROW = {
|
|
663
|
+
id: "agent-model",
|
|
664
|
+
is_trigger: is_unnamed_model,
|
|
665
|
+
reason: AGENT_MODEL_REASON,
|
|
666
|
+
decide: () => true,
|
|
667
|
+
keeps: keeps_model,
|
|
668
|
+
reaches: is_general_purpose,
|
|
669
|
+
about: {
|
|
670
|
+
title: "Subagent model named",
|
|
671
|
+
topic: '`.claude/skills/workflow-commands/delegation.md` \u2192 "A `general-purpose` subagent names its model"',
|
|
672
|
+
fires: "an `Agent` / `Task` call of the `general-purpose` type with no `model`. Every occurrence",
|
|
673
|
+
quiet: "a named agent type, or a `model` set"
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
var agent_model = { AGENT_MODEL_REASON, ROW, is_unnamed_model };
|
|
677
|
+
|
|
678
|
+
// scripts/rules/direct-filing.ts
|
|
679
|
+
var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path>`, never with `gh api \u2026/issues` or `gh issue create` \u2014 it runs the scout, the lint, the labels and `epic:bundle` itself. It fires on every occurrence (`docs/josh-commands-backlog.md` \u2192 "`josh issue:file`").';
|
|
680
|
+
function is_any_filing(command) {
|
|
681
|
+
return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
|
|
682
|
+
}
|
|
683
|
+
var ROW2 = {
|
|
684
|
+
id: "direct-filing",
|
|
685
|
+
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
|
|
686
|
+
reason: DIRECT_FILING_REASON,
|
|
687
|
+
decide: () => true,
|
|
688
|
+
keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
689
|
+
reaches: bash_triggers.on_bash_command(is_any_filing),
|
|
690
|
+
about: {
|
|
691
|
+
title: "Direct filing",
|
|
692
|
+
topic: "`docs/josh-commands-backlog.md` \u2192 `josh issue:file`",
|
|
693
|
+
fires: "a `Bash` that creates an Issue outside `josh issue:file` (`gh issue create`, or a POST with `title` to `\u2026/issues`); the refusal hands over the `pnpm josh issue:file` form. Every occurrence",
|
|
694
|
+
quiet: "filing through `pnpm josh issue:file` \u2014 the duplicate scan, the body check and `epic:bundle` all run"
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
var direct_filing = { DIRECT_FILING_REASON, ROW: ROW2 };
|
|
698
|
+
|
|
699
|
+
// scripts/rules/gh-api.ts
|
|
700
|
+
var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
|
|
701
|
+
var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
|
|
702
|
+
var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
|
|
703
|
+
var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
|
|
704
|
+
var READ_METHOD = "GET";
|
|
705
|
+
var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
|
|
706
|
+
var OWNER_GROUP = 1;
|
|
707
|
+
var REPO_GROUP = 2;
|
|
708
|
+
var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
|
|
709
|
+
function is_gh_api(segment) {
|
|
710
|
+
return GH_API_COMMAND.test(segment);
|
|
711
|
+
}
|
|
712
|
+
function method_of(segment) {
|
|
713
|
+
return API_METHOD.exec(segment)?.[1]?.toUpperCase();
|
|
714
|
+
}
|
|
715
|
+
function is_read(segment) {
|
|
716
|
+
const method = method_of(segment);
|
|
717
|
+
if (method !== void 0) return method.toUpperCase() === READ_METHOD;
|
|
718
|
+
return !API_FIELD.test(segment);
|
|
719
|
+
}
|
|
720
|
+
function is_write(segment) {
|
|
721
|
+
return !is_read(segment);
|
|
722
|
+
}
|
|
723
|
+
function repo_target(segment) {
|
|
724
|
+
const match = REPO_PATH.exec(segment);
|
|
725
|
+
if (match === null) return void 0;
|
|
726
|
+
return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
|
|
727
|
+
}
|
|
728
|
+
function is_owner_placeholder(owner) {
|
|
729
|
+
return OWNER_PLACEHOLDERS.has(owner);
|
|
730
|
+
}
|
|
731
|
+
var gh_api = {
|
|
732
|
+
GH_FLAGS,
|
|
733
|
+
is_gh_api,
|
|
734
|
+
is_owner_placeholder,
|
|
735
|
+
is_read,
|
|
736
|
+
is_write,
|
|
737
|
+
method_of,
|
|
738
|
+
repo_target
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// scripts/rules/direct-pr-create.ts
|
|
742
|
+
var GH_COMMAND = "gh";
|
|
743
|
+
var PR_GROUP = "pr";
|
|
744
|
+
var PR_CREATE_ACTIONS = /* @__PURE__ */ new Set(["create", "new"]);
|
|
745
|
+
var FLAG_PREFIX = "-";
|
|
746
|
+
var HELP_FLAGS = /* @__PURE__ */ new Set(["-h", "--help"]);
|
|
747
|
+
var VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set(["-R", "--repo"]);
|
|
748
|
+
var API_VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
|
|
749
|
+
...VALUE_TAKING_FLAGS,
|
|
750
|
+
"-X",
|
|
751
|
+
"--method",
|
|
752
|
+
"-f",
|
|
753
|
+
"--raw-field",
|
|
754
|
+
"-F",
|
|
755
|
+
"--field",
|
|
756
|
+
"-H",
|
|
757
|
+
"--header",
|
|
758
|
+
"--input",
|
|
759
|
+
"-q",
|
|
760
|
+
"--jq",
|
|
761
|
+
"-t",
|
|
762
|
+
"--template",
|
|
763
|
+
"--hostname",
|
|
764
|
+
"-p",
|
|
765
|
+
"--preview",
|
|
766
|
+
"--cache"
|
|
767
|
+
]);
|
|
768
|
+
var API_SUBCOMMAND = "api";
|
|
769
|
+
var PULLS_PATH = /^['"]?(?:https:\/\/api\.github\.com)?\/?repos\/[^/\s'"]+\/[^/\s'"]+\/pulls\/?['"]?$/u;
|
|
770
|
+
function positional_words(args, value_taking_flags) {
|
|
771
|
+
return args.filter((argument, index) => {
|
|
772
|
+
if (argument.startsWith(FLAG_PREFIX)) return false;
|
|
773
|
+
return !value_taking_flags.has(args[index - 1] ?? "");
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
function is_pr_create_api_call(args) {
|
|
777
|
+
const call = [GH_COMMAND, ...args].join(" ");
|
|
778
|
+
if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
|
|
779
|
+
const [subcommand, endpoint = ""] = positional_words(args, API_VALUE_TAKING_FLAGS);
|
|
780
|
+
return subcommand === API_SUBCOMMAND && PULLS_PATH.test(endpoint);
|
|
781
|
+
}
|
|
782
|
+
function is_pr_create_cli_call(args) {
|
|
783
|
+
const [group, action = ""] = positional_words(args, VALUE_TAKING_FLAGS);
|
|
784
|
+
return group === PR_GROUP && PR_CREATE_ACTIONS.has(action);
|
|
785
|
+
}
|
|
786
|
+
function is_help_call(args) {
|
|
787
|
+
return args.some(
|
|
788
|
+
(argument, index) => HELP_FLAGS.has(argument) && !(args[index - 1] ?? "").startsWith(FLAG_PREFIX)
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
function is_pr_create_segment(segment) {
|
|
792
|
+
const args = git_argv.arguments_of(segment, GH_COMMAND);
|
|
793
|
+
if (args === void 0 || is_help_call(args)) return false;
|
|
794
|
+
return is_pr_create_cli_call(args) || is_pr_create_api_call(args);
|
|
795
|
+
}
|
|
796
|
+
var RUNS_QUOTED_TEXT = /\$\(|`/u;
|
|
797
|
+
var QUOTED_TEXT_RUNNERS = /* @__PURE__ */ new Set(["ssh", "eval"]);
|
|
798
|
+
var SHELL_DASH_C = /\b(?:ba|da|k|z)?sh\s+(?:-\S+\s+)*-[A-Za-z]*c\b/u;
|
|
799
|
+
var COMMENT_MARKER = /(?:^|\s)#/mu;
|
|
800
|
+
var LINE_BREAK = "\n";
|
|
801
|
+
function is_run_by_shell(segment) {
|
|
802
|
+
if (RUNS_QUOTED_TEXT.test(segment) || SHELL_DASH_C.test(segment)) return true;
|
|
803
|
+
return QUOTED_TEXT_RUNNERS.has(git_argv.program_of(segment)?.name ?? "");
|
|
804
|
+
}
|
|
805
|
+
function is_runs_quoted_text(segment) {
|
|
806
|
+
if (is_run_by_shell(segment)) return true;
|
|
807
|
+
return segment.includes(LINE_BREAK) && COMMENT_MARKER.test(segment);
|
|
808
|
+
}
|
|
809
|
+
function judged_segments(segment) {
|
|
810
|
+
return is_runs_quoted_text(segment) ? shell_segments.commands_of(segment) : [segment];
|
|
811
|
+
}
|
|
812
|
+
function has_run_segment(command, is_match) {
|
|
813
|
+
return shell_segments.quote_aware_commands_of(command).flatMap((segment) => judged_segments(segment)).some((segment) => is_match(segment));
|
|
814
|
+
}
|
|
815
|
+
function is_direct_pr_create(command) {
|
|
816
|
+
return has_run_segment(command, is_pr_create_segment);
|
|
817
|
+
}
|
|
818
|
+
var DIRECT_PR_CREATE_REASON = "\u26D4 direct PR creation: `gh pr create` (or a `gh api` POST to `repos/<o>/<r>/pulls`) bypasses `build_body`, which writes the `closes #N` line \u2014 the Issue would stay open after the merge (`CLAUDE.md` \u2192 Git Rules). Open the PR with `pnpm josh pr` instead; after a failed push, fix, push, then run `pnpm josh pr`. **This rule fires on every occurrence, not once per run.**";
|
|
819
|
+
var ROW3 = {
|
|
820
|
+
id: "direct-pr-create",
|
|
821
|
+
is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
|
|
822
|
+
reason: DIRECT_PR_CREATE_REASON,
|
|
823
|
+
decide: () => true,
|
|
824
|
+
about: {
|
|
825
|
+
title: "Direct PR creation",
|
|
826
|
+
topic: "`CLAUDE.md` \u2192 Git Rules",
|
|
827
|
+
fires: "`gh pr create`, or a `gh api` write to `repos/<o>/<r>/pulls`; it points at `pnpm josh pr`. Every occurrence",
|
|
828
|
+
quiet: "`pnpm josh pr`, or reading a PR \u2014 the path that writes `closes #N`"
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
var direct_pr_create = {
|
|
832
|
+
API_VALUE_TAKING_FLAGS,
|
|
833
|
+
DIRECT_PR_CREATE_REASON,
|
|
834
|
+
GH_COMMAND,
|
|
835
|
+
ROW: ROW3,
|
|
836
|
+
VALUE_TAKING_FLAGS,
|
|
837
|
+
has_run_segment,
|
|
838
|
+
is_direct_pr_create,
|
|
839
|
+
is_help_call,
|
|
840
|
+
positional_words
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// scripts/rules/direct-issue-comment.ts
|
|
844
|
+
var ISSUE_GROUP = "issue";
|
|
845
|
+
var COMMENT_ACTION = "comment";
|
|
846
|
+
var API_SUBCOMMAND2 = "api";
|
|
847
|
+
var REPO_SELECTOR = /^(?:-R|--repo)(?:=|$)/u;
|
|
848
|
+
var SEGMENT = String.raw`[^/\s'"]+`;
|
|
849
|
+
var QUOTE = `['"]?`;
|
|
850
|
+
var API_ORIGIN = String.raw`(?:https://api\.github\.com)?/?`;
|
|
851
|
+
var COMMENTS_PATH = new RegExp(
|
|
852
|
+
`^${QUOTE}${API_ORIGIN}repos/${SEGMENT}/${SEGMENT}/issues/${SEGMENT}/comments/?${QUOTE}$`,
|
|
853
|
+
"u"
|
|
854
|
+
);
|
|
855
|
+
function is_comment_cli_call(args) {
|
|
856
|
+
if (args.some((argument) => REPO_SELECTOR.test(argument))) return false;
|
|
857
|
+
const [group, action] = direct_pr_create.positional_words(
|
|
858
|
+
args,
|
|
859
|
+
direct_pr_create.VALUE_TAKING_FLAGS
|
|
860
|
+
);
|
|
861
|
+
return group === ISSUE_GROUP && action === COMMENT_ACTION;
|
|
862
|
+
}
|
|
863
|
+
function is_own_comments_endpoint(endpoint) {
|
|
864
|
+
const owner = gh_api.repo_target(endpoint)?.owner ?? "";
|
|
865
|
+
return gh_api.is_owner_placeholder(owner) && COMMENTS_PATH.test(endpoint);
|
|
866
|
+
}
|
|
867
|
+
function is_comment_api_call(args) {
|
|
868
|
+
const call = [direct_pr_create.GH_COMMAND, ...args].join(" ");
|
|
869
|
+
if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
|
|
870
|
+
const [subcommand, endpoint = ""] = direct_pr_create.positional_words(
|
|
871
|
+
args,
|
|
872
|
+
direct_pr_create.API_VALUE_TAKING_FLAGS
|
|
873
|
+
);
|
|
874
|
+
return subcommand === API_SUBCOMMAND2 && is_own_comments_endpoint(endpoint);
|
|
875
|
+
}
|
|
876
|
+
function is_comment_segment(segment) {
|
|
877
|
+
const args = git_argv.arguments_of(segment, direct_pr_create.GH_COMMAND);
|
|
878
|
+
if (args === void 0 || direct_pr_create.is_help_call(args)) return false;
|
|
879
|
+
return is_comment_cli_call(args) || is_comment_api_call(args);
|
|
880
|
+
}
|
|
881
|
+
function is_direct_issue_comment(command) {
|
|
882
|
+
return direct_pr_create.has_run_segment(command, is_comment_segment);
|
|
883
|
+
}
|
|
884
|
+
var COMMENT_NAMES = /* @__PURE__ */ new Set(["issue:comment"]);
|
|
885
|
+
function is_josh_comment(command) {
|
|
886
|
+
return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, COMMENT_NAMES));
|
|
887
|
+
}
|
|
888
|
+
function is_any_comment(command) {
|
|
889
|
+
return is_direct_issue_comment(command) || is_josh_comment(command);
|
|
890
|
+
}
|
|
891
|
+
var DIRECT_ISSUE_COMMENT_REASON = "\u26D4 direct issue comment: `gh issue comment` (or a `gh api` write to `\u2026/issues/<N>/comments`) posts without the run marker, so the run reads its own comment as a person updating the issue \u2014 the review goes stale and `pnpm josh followup` refuses the merge. Post with `pnpm josh issue:comment <N> --body-file <path>` instead (`shell-body.md`). **This rule fires on every occurrence, not once per run.**";
|
|
892
|
+
var ROW4 = {
|
|
893
|
+
id: "direct-issue-comment",
|
|
894
|
+
is_trigger: bash_triggers.on_bash_command(is_direct_issue_comment),
|
|
895
|
+
reason: DIRECT_ISSUE_COMMENT_REASON,
|
|
896
|
+
decide: () => true,
|
|
897
|
+
keeps: bash_triggers.on_bash_command(is_josh_comment),
|
|
898
|
+
reaches: bash_triggers.on_bash_command(is_any_comment),
|
|
899
|
+
about: {
|
|
900
|
+
title: "Direct issue comment",
|
|
901
|
+
topic: "`shell-body.md`",
|
|
902
|
+
fires: "a comment to the checkout's own repository outside `josh issue:comment` (`gh issue comment` with no `--repo`, or a `gh api` write to `repos/{owner}/<r>/issues/<N>/comments`); it points at `pnpm josh issue:comment`. Every occurrence",
|
|
903
|
+
quiet: "`pnpm josh issue:comment`, or reading the comments \u2014 the path that writes the run marker"
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
var direct_issue_comment = { DIRECT_ISSUE_COMMENT_REASON, ROW: ROW4, is_direct_issue_comment };
|
|
907
|
+
|
|
908
|
+
// scripts/run/progress/run-progress.ts
|
|
909
|
+
var DEFAULT_INTERVAL_MINUTES = 20;
|
|
910
|
+
var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
|
|
911
|
+
var DEFAULT_INTERVAL_MS = DEFAULT_INTERVAL_MINUTES * MS_PER_MINUTE;
|
|
912
|
+
var DISABLED_KEY = "JOSH_PROGRESS";
|
|
913
|
+
var DISABLED_VALUE = "0";
|
|
914
|
+
var NO_LANES = "none";
|
|
915
|
+
var NO_CHILD_YET = "no in-progress child yet";
|
|
916
|
+
var NO_NEXT = "none";
|
|
917
|
+
var QUIET_MARKER = "\u23F3";
|
|
918
|
+
var STAMP_END = 16;
|
|
919
|
+
var PAD_WIDTH = 2;
|
|
920
|
+
var MINUTES_PER_HOUR = 60;
|
|
921
|
+
function to_minutes(elapsed_ms) {
|
|
922
|
+
return Math.floor(elapsed_ms / MS_PER_MINUTE);
|
|
923
|
+
}
|
|
924
|
+
function format_minutes(elapsed_ms) {
|
|
925
|
+
return `${String(to_minutes(elapsed_ms))}m`;
|
|
926
|
+
}
|
|
927
|
+
function pad(value) {
|
|
928
|
+
return String(value).padStart(PAD_WIDTH, "0");
|
|
929
|
+
}
|
|
930
|
+
function offset_minutes(date) {
|
|
931
|
+
return Math.round(-date.getTimezoneOffset());
|
|
932
|
+
}
|
|
933
|
+
function format_offset(total_minutes) {
|
|
934
|
+
const sign = total_minutes < 0 ? "-" : "+";
|
|
935
|
+
const absolute = Math.abs(total_minutes);
|
|
936
|
+
return `${sign}${pad(Math.floor(absolute / MINUTES_PER_HOUR))}:${pad(absolute % MINUTES_PER_HOUR)}`;
|
|
937
|
+
}
|
|
938
|
+
function format_observed_at(now_ms) {
|
|
939
|
+
const minutes = offset_minutes(new Date(now_ms));
|
|
940
|
+
const local = new Date(now_ms + minutes * MS_PER_MINUTE);
|
|
941
|
+
const day = `${String(local.getUTCFullYear())}-${pad(local.getUTCMonth() + 1)}-${pad(local.getUTCDate())}`;
|
|
942
|
+
const clock = `${pad(local.getUTCHours())}:${pad(local.getUTCMinutes())}${format_offset(minutes)}`;
|
|
943
|
+
return `${day} ${clock} / ${new Date(now_ms).toISOString().slice(0, STAMP_END)}Z`;
|
|
944
|
+
}
|
|
945
|
+
function observation_key(observations) {
|
|
946
|
+
return JSON.stringify({ children: observations.children, lanes: observations.lanes });
|
|
947
|
+
}
|
|
948
|
+
function next_state(previous, key, now_ms) {
|
|
949
|
+
if (previous?.key === key) return previous;
|
|
950
|
+
return { key, unchanged_since_ms: now_ms };
|
|
951
|
+
}
|
|
952
|
+
function format_child(child, cite) {
|
|
953
|
+
const labels = child.labels.length > 0 ? child.labels.join(",") : "(no labels)";
|
|
954
|
+
return `${cite(child.issue)} ${labels} PR:${child.pr_state}`;
|
|
955
|
+
}
|
|
956
|
+
function format_children(children, repo) {
|
|
957
|
+
if (children.length === 0) return NO_CHILD_YET;
|
|
958
|
+
const cite = issue_cite.citer(repo, new Map(children.map((child) => [child.issue, child.title])));
|
|
959
|
+
return children.map((child) => format_child(child, cite)).join(" \xB7 ");
|
|
960
|
+
}
|
|
961
|
+
function format_lanes(lanes, repo) {
|
|
962
|
+
if (lanes.length === 0) return NO_LANES;
|
|
963
|
+
const cite = issue_cite.citer(repo, /* @__PURE__ */ new Map());
|
|
964
|
+
return lanes.map((lane) => `${cite(lane.issue)}:${lane.state}`).join(" ");
|
|
965
|
+
}
|
|
966
|
+
function format_record(record_age_ms) {
|
|
967
|
+
return record_age_ms === void 0 ? "unread" : `+${format_minutes(record_age_ms)}`;
|
|
968
|
+
}
|
|
969
|
+
function format_next_report(now_ms, interval_ms) {
|
|
970
|
+
return format_observed_at(now_ms + interval_ms);
|
|
971
|
+
}
|
|
972
|
+
function format_next_line(next) {
|
|
973
|
+
return `next ${next ?? NO_NEXT}`;
|
|
974
|
+
}
|
|
975
|
+
function format_report(fields) {
|
|
976
|
+
return [
|
|
977
|
+
`${QUIET_MARKER} at ${fields.observed_at}`,
|
|
978
|
+
`quiet ${fields.quiet} \xB7 unchanged ${fields.unchanged}`,
|
|
979
|
+
`children ${fields.children}`,
|
|
980
|
+
`lanes ${fields.lanes} \xB7 load ${fields.load} \xB7 record ${fields.record}`,
|
|
981
|
+
format_next_line(fields.next)
|
|
982
|
+
].join("\n");
|
|
983
|
+
}
|
|
984
|
+
function format_line(observations, timing) {
|
|
985
|
+
return format_report({
|
|
986
|
+
observed_at: format_observed_at(timing.now_ms),
|
|
987
|
+
quiet: format_minutes(timing.now_ms - timing.quiet_since_ms),
|
|
988
|
+
unchanged: format_minutes(timing.now_ms - timing.unchanged_since_ms),
|
|
989
|
+
children: format_children(observations.children, observations.repo),
|
|
990
|
+
lanes: format_lanes(observations.lanes, observations.repo),
|
|
991
|
+
load: observations.load_average.toFixed(1),
|
|
992
|
+
record: format_record(observations.record_age_ms),
|
|
993
|
+
next: format_next_report(timing.now_ms, timing.interval_ms)
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
function is_due(last_report_ms, now_ms, interval_ms) {
|
|
997
|
+
return now_ms - last_report_ms >= interval_ms;
|
|
998
|
+
}
|
|
999
|
+
function minutes_from(raw) {
|
|
1000
|
+
const minutes = Number(raw);
|
|
1001
|
+
if (raw === void 0 || raw.trim() === "" || !Number.isFinite(minutes) || minutes <= 0) {
|
|
1002
|
+
return void 0;
|
|
1003
|
+
}
|
|
1004
|
+
return minutes;
|
|
1005
|
+
}
|
|
1006
|
+
function is_disabled() {
|
|
1007
|
+
return process.env[DISABLED_KEY] === DISABLED_VALUE;
|
|
1008
|
+
}
|
|
1009
|
+
var run_progress = {
|
|
1010
|
+
DEFAULT_INTERVAL_MINUTES,
|
|
1011
|
+
DEFAULT_INTERVAL_MS,
|
|
1012
|
+
DISABLED_KEY,
|
|
1013
|
+
DISABLED_VALUE,
|
|
1014
|
+
INTERVAL_KEY,
|
|
1015
|
+
NO_CHILD_YET,
|
|
1016
|
+
NO_NEXT,
|
|
1017
|
+
format_child,
|
|
1018
|
+
format_children,
|
|
1019
|
+
format_lanes,
|
|
1020
|
+
format_line,
|
|
1021
|
+
format_next_line,
|
|
1022
|
+
format_next_report,
|
|
1023
|
+
format_report,
|
|
1024
|
+
is_disabled,
|
|
1025
|
+
is_due,
|
|
1026
|
+
minutes_from,
|
|
1027
|
+
next_state,
|
|
1028
|
+
observation_key
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// scripts/run/progress/run-progress-clock.ts
|
|
1032
|
+
import { z } from "zod";
|
|
1033
|
+
var PROGRESS_PREFIX = "josh-run-progress-";
|
|
1034
|
+
var LIFE_PREFIX = "josh-run-progress-life-";
|
|
1035
|
+
var LOG_SUFFIX = ".log";
|
|
1036
|
+
var LOG_KEEP = 12;
|
|
1037
|
+
var LOG_STALE_MINUTES = 90;
|
|
1038
|
+
var LOG_STALE_MS = LOG_STALE_MINUTES * MS_PER_MINUTE;
|
|
1039
|
+
var BLOCK_SEPARATOR = "\n\n";
|
|
1040
|
+
var report_stamp_schema = z.object({ reported_at: z.string(), line: z.string().optional() });
|
|
1041
|
+
function parse_stamp(raw) {
|
|
1042
|
+
const parsed = report_stamp_schema.safeParse(JSON.parse(raw));
|
|
1043
|
+
if (!parsed.success) return void 0;
|
|
1044
|
+
const reported_at = Date.parse(parsed.data.reported_at);
|
|
1045
|
+
return Number.isNaN(reported_at) ? void 0 : reported_at;
|
|
1046
|
+
}
|
|
1047
|
+
function read_last_line(target) {
|
|
1048
|
+
const raw = stamp_file.read_stamp_text(target);
|
|
1049
|
+
if (raw === void 0) return void 0;
|
|
1050
|
+
return json_value.parse_with(raw, report_stamp_schema)?.line;
|
|
1051
|
+
}
|
|
1052
|
+
function read_last_report(target) {
|
|
1053
|
+
const raw = stamp_file.read_stamp_text(target);
|
|
1054
|
+
if (raw === void 0) return void 0;
|
|
1055
|
+
try {
|
|
1056
|
+
return parse_stamp(raw);
|
|
1057
|
+
} catch {
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function log_path_of(target) {
|
|
1062
|
+
return `${target}${LOG_SUFFIX}`;
|
|
1063
|
+
}
|
|
1064
|
+
function is_log_stale(target, now_ms) {
|
|
1065
|
+
const previous_ms = read_last_report(target);
|
|
1066
|
+
return previous_ms === void 0 || now_ms - previous_ms > LOG_STALE_MS;
|
|
1067
|
+
}
|
|
1068
|
+
function recent_blocks(log_target2, is_fresh) {
|
|
1069
|
+
if (is_fresh) return [];
|
|
1070
|
+
const raw = stamp_file.read_stamp_text(log_target2);
|
|
1071
|
+
if (raw === void 0) return [];
|
|
1072
|
+
return raw.split(BLOCK_SEPARATOR).map((block) => block.trimEnd()).filter((block) => block.length > 0);
|
|
1073
|
+
}
|
|
1074
|
+
function append_line(log_target2, reported_at, line, is_fresh) {
|
|
1075
|
+
const block = `${reported_at}
|
|
1076
|
+
${line}`;
|
|
1077
|
+
const blocks = [...recent_blocks(log_target2, is_fresh), block].slice(-LOG_KEEP);
|
|
1078
|
+
stamp_file.write_text_stamp(log_target2, `${blocks.join(BLOCK_SEPARATOR)}
|
|
1079
|
+
`);
|
|
1080
|
+
}
|
|
1081
|
+
function mark(target, now_ms, line) {
|
|
1082
|
+
const reported_at = new Date(now_ms).toISOString();
|
|
1083
|
+
const kept = line ?? read_last_line(target);
|
|
1084
|
+
if (line !== void 0) {
|
|
1085
|
+
append_line(log_path_of(target), reported_at, line, is_log_stale(target, now_ms));
|
|
1086
|
+
}
|
|
1087
|
+
stamp_file.write_stamp(target, kept === void 0 ? { reported_at } : { reported_at, line: kept });
|
|
1088
|
+
}
|
|
1089
|
+
function stamp_target_of(git_directory) {
|
|
1090
|
+
return stamp_file.stamp_path(PROGRESS_PREFIX, git_directory);
|
|
1091
|
+
}
|
|
1092
|
+
function life_target_of(git_directory) {
|
|
1093
|
+
return stamp_file.stamp_path(LIFE_PREFIX, git_directory);
|
|
1094
|
+
}
|
|
1095
|
+
var life_schema = z.object({ alive: z.literal(true), pinged_at: z.string().optional() });
|
|
1096
|
+
function begin_life(target) {
|
|
1097
|
+
stamp_file.write_stamp(target, { alive: true, pinged_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1098
|
+
}
|
|
1099
|
+
function ping_life(target) {
|
|
1100
|
+
begin_life(target);
|
|
1101
|
+
}
|
|
1102
|
+
function parse_pinged_at(raw) {
|
|
1103
|
+
const parsed = life_schema.safeParse(JSON.parse(raw));
|
|
1104
|
+
if (!parsed.success || parsed.data.pinged_at === void 0) return void 0;
|
|
1105
|
+
const pinged_ms = Date.parse(parsed.data.pinged_at);
|
|
1106
|
+
return Number.isNaN(pinged_ms) ? void 0 : pinged_ms;
|
|
1107
|
+
}
|
|
1108
|
+
function is_life_fresh(target, threshold_ms) {
|
|
1109
|
+
const raw = stamp_file.read_stamp_text(target);
|
|
1110
|
+
if (raw === void 0) return false;
|
|
1111
|
+
try {
|
|
1112
|
+
const pinged_ms = parse_pinged_at(raw);
|
|
1113
|
+
return pinged_ms !== void 0 && Date.now() - pinged_ms < threshold_ms;
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
error_text.trace_swallowed("run_progress_clock.is_life_fresh", error);
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function is_life_ended(target) {
|
|
1120
|
+
return stamp_file.read_stamp_text(target) === void 0;
|
|
1121
|
+
}
|
|
1122
|
+
function end_life(target) {
|
|
1123
|
+
stamp_file.remove_stamp(target);
|
|
1124
|
+
}
|
|
1125
|
+
function stamp_target_sync() {
|
|
1126
|
+
const directory = git_common_directory.own();
|
|
1127
|
+
return directory === void 0 ? void 0 : stamp_target_of(directory);
|
|
1128
|
+
}
|
|
1129
|
+
function read_last_report_sync() {
|
|
1130
|
+
const target = stamp_target_sync();
|
|
1131
|
+
return target === void 0 ? void 0 : read_last_report(target);
|
|
1132
|
+
}
|
|
1133
|
+
var run_progress_clock = {
|
|
1134
|
+
LIFE_PREFIX,
|
|
1135
|
+
LOG_KEEP,
|
|
1136
|
+
LOG_STALE_MS,
|
|
1137
|
+
begin_life,
|
|
1138
|
+
end_life,
|
|
1139
|
+
is_life_ended,
|
|
1140
|
+
is_life_fresh,
|
|
1141
|
+
life_target_of,
|
|
1142
|
+
log_path_of,
|
|
1143
|
+
mark,
|
|
1144
|
+
parse_stamp,
|
|
1145
|
+
ping_life,
|
|
1146
|
+
read_last_line,
|
|
1147
|
+
read_last_report,
|
|
1148
|
+
read_last_report_sync,
|
|
1149
|
+
stamp_target_of
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
// scripts/run/progress/run-progress-config.ts
|
|
1153
|
+
import { z as z2 } from "zod";
|
|
1154
|
+
var package_schema = z2.object({
|
|
1155
|
+
josh: z2.object({ progress_interval_minutes: z2.number().positive() }).optional()
|
|
1156
|
+
});
|
|
1157
|
+
function read_declared_minutes(directory) {
|
|
1158
|
+
return package_manifest.read_manifest_if_valid(directory, package_schema)?.josh?.progress_interval_minutes;
|
|
1159
|
+
}
|
|
1160
|
+
function read_package_minutes(start) {
|
|
1161
|
+
for (const directory of ancestor_directories.list(start)) {
|
|
1162
|
+
const minutes = read_declared_minutes(directory);
|
|
1163
|
+
if (minutes !== void 0) return minutes;
|
|
1164
|
+
}
|
|
1165
|
+
return void 0;
|
|
1166
|
+
}
|
|
1167
|
+
function to_ms(minutes) {
|
|
1168
|
+
return minutes === void 0 ? void 0 : minutes * MS_PER_MINUTE;
|
|
1169
|
+
}
|
|
1170
|
+
function resolve_interval_ms(raw, directory = process.cwd()) {
|
|
1171
|
+
return to_ms(run_progress.minutes_from(raw)) ?? to_ms(run_progress.minutes_from(process.env[run_progress.INTERVAL_KEY])) ?? to_ms(read_package_minutes(directory)) ?? run_progress.DEFAULT_INTERVAL_MS;
|
|
1172
|
+
}
|
|
1173
|
+
function configured_interval_ms(directory = process.cwd()) {
|
|
1174
|
+
return resolve_interval_ms(void 0, directory);
|
|
1175
|
+
}
|
|
1176
|
+
var run_progress_config = {
|
|
1177
|
+
PACKAGE_FILE: PACKAGE_JSON,
|
|
1178
|
+
configured_interval_ms,
|
|
1179
|
+
read_package_minutes,
|
|
1180
|
+
resolve_interval_ms
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
// scripts/rules/early-heartbeat.ts
|
|
1184
|
+
var ARM_PREFIX = "josh-heartbeat-arm-";
|
|
1185
|
+
var SEGMENT_SEPARATOR = /&&|\|\||;|&|\n/u;
|
|
1186
|
+
var SLEEP_SEGMENT = /^sleep\s+(\d+(?:\.\d+)?)([smhd]?)$/u;
|
|
1187
|
+
var INERT_SEGMENT = /^(?::|true|(?:echo|printf|date)\b.*)$/u;
|
|
1188
|
+
var LOOP_KEYWORD = /\b(?:while|until|for|do|done)\b/u;
|
|
1189
|
+
var SECONDS_PER_UNIT = { d: 86400, h: 3600, m: 60, s: 1 };
|
|
1190
|
+
var BARE_NUMBER_SECONDS = 1;
|
|
1191
|
+
var AMOUNT_GROUP = 1;
|
|
1192
|
+
var UNIT_GROUP = 2;
|
|
1193
|
+
var NO_SECONDS = 0;
|
|
1194
|
+
var ARM_RECORD = hook_decision.create_refusal_stamp(ARM_PREFIX);
|
|
1195
|
+
function segments_of(command) {
|
|
1196
|
+
return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim()).filter((segment) => segment !== "");
|
|
1197
|
+
}
|
|
1198
|
+
function sleep_seconds(segment) {
|
|
1199
|
+
const matched = SLEEP_SEGMENT.exec(segment);
|
|
1200
|
+
if (matched === null) return void 0;
|
|
1201
|
+
const unit = matched[UNIT_GROUP] ?? "";
|
|
1202
|
+
return Number(matched[AMOUNT_GROUP]) * (SECONDS_PER_UNIT[unit] ?? BARE_NUMBER_SECONDS);
|
|
1203
|
+
}
|
|
1204
|
+
function is_wait_or_inert(segment) {
|
|
1205
|
+
return sleep_seconds(segment) !== void 0 || INERT_SEGMENT.test(segment);
|
|
1206
|
+
}
|
|
1207
|
+
function is_wait_timer(command) {
|
|
1208
|
+
if (LOOP_KEYWORD.test(command)) return false;
|
|
1209
|
+
const segments = segments_of(command);
|
|
1210
|
+
if (segments.every((segment) => sleep_seconds(segment) === void 0)) return false;
|
|
1211
|
+
return segments.every((segment) => is_wait_or_inert(segment));
|
|
1212
|
+
}
|
|
1213
|
+
function wait_duration_ms(command) {
|
|
1214
|
+
const seconds = segments_of(command).reduce(
|
|
1215
|
+
(total, segment) => total + (sleep_seconds(segment) ?? NO_SECONDS),
|
|
1216
|
+
NO_SECONDS
|
|
1217
|
+
);
|
|
1218
|
+
return seconds * MS_PER_SECOND;
|
|
1219
|
+
}
|
|
1220
|
+
function is_armed(target, now_ms) {
|
|
1221
|
+
return now_ms < ARM_RECORD.last_ms(target);
|
|
1222
|
+
}
|
|
1223
|
+
function landing_ms(command, now_ms, last_report_ms) {
|
|
1224
|
+
const interval_ms = run_progress_config.configured_interval_ms();
|
|
1225
|
+
const duration_ms = wait_duration_ms(command);
|
|
1226
|
+
if (duration_ms > interval_ms) return void 0;
|
|
1227
|
+
const lands_ms = now_ms + duration_ms;
|
|
1228
|
+
return run_progress.is_due(last_report_ms, lands_ms, interval_ms) ? lands_ms : void 0;
|
|
1229
|
+
}
|
|
1230
|
+
function decide(call, run, can_record) {
|
|
1231
|
+
const last_report_ms = run_progress_clock.read_last_report_sync();
|
|
1232
|
+
if (last_report_ms === void 0) return false;
|
|
1233
|
+
const target = ARM_RECORD.path(run.transcript);
|
|
1234
|
+
if (is_armed(target, run.now_ms)) return true;
|
|
1235
|
+
const lands_ms = landing_ms(time_shell.bash_command(call.input), run.now_ms, last_report_ms);
|
|
1236
|
+
if (lands_ms === void 0) return true;
|
|
1237
|
+
if (can_record) ARM_RECORD.record(target, lands_ms);
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
var EARLY_HEARTBEAT_REASON = "\u26D4 early heartbeat: this call arms a wait timer of its own. Start `pnpm josh run:progress --wait` once in the background: it reports to the run's event stream on its own \u2014 relay nothing. When asked, `pnpm josh run:progress --once` \u2014 under `backlogrun`, `pnpm josh run:board --chat`; for CI, `pnpm josh followup`. It fires on every occurrence (`.claude/skills/workflow-commands/progress-watcher.md`).";
|
|
1241
|
+
var PROGRESS_NAMES = /* @__PURE__ */ new Set(["run:progress"]);
|
|
1242
|
+
var WATCH_FLAG = /(?:^|\s)--(?:wait|once)(?=\s|$)/u;
|
|
1243
|
+
function is_progress_watch_segment(segment) {
|
|
1244
|
+
return shell_segments.is_josh_command(segment, PROGRESS_NAMES) && WATCH_FLAG.test(segment);
|
|
1245
|
+
}
|
|
1246
|
+
function is_progress_watch(command) {
|
|
1247
|
+
return segments_of(time_shell.unquoted(command)).some(
|
|
1248
|
+
(segment) => is_progress_watch_segment(segment)
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
function waits_for_progress(command) {
|
|
1252
|
+
return is_wait_timer(command) || is_progress_watch(command);
|
|
1253
|
+
}
|
|
1254
|
+
var early_heartbeat = {
|
|
1255
|
+
ARM_PREFIX,
|
|
1256
|
+
EARLY_HEARTBEAT_REASON,
|
|
1257
|
+
decide,
|
|
1258
|
+
is_progress_watch,
|
|
1259
|
+
is_wait_timer,
|
|
1260
|
+
wait_duration_ms,
|
|
1261
|
+
waits_for_progress
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
// scripts/rules/file-body.ts
|
|
1265
|
+
import { existsSync } from "node:fs";
|
|
1266
|
+
var HEREDOC = /(?<!<)<<(?!<)-?\s*['"]?([A-Z_a-z]\w*)/u;
|
|
1267
|
+
var LINE_BREAK2 = "\n";
|
|
1268
|
+
function has_heredoc(command) {
|
|
1269
|
+
return HEREDOC.test(command);
|
|
1270
|
+
}
|
|
1271
|
+
function with_body_line(lines, line) {
|
|
1272
|
+
const opener = lines.at(-1);
|
|
1273
|
+
if (opener === void 0) return [...lines];
|
|
1274
|
+
return [...lines.slice(0, -1), { ...opener, body: `${opener.body}${line}${LINE_BREAK2}` }];
|
|
1275
|
+
}
|
|
1276
|
+
function scan_line(scan, line) {
|
|
1277
|
+
if (scan.delimiter === void 0) {
|
|
1278
|
+
return { lines: [...scan.lines, { text: line, body: "" }], delimiter: HEREDOC.exec(line)?.[1] };
|
|
1279
|
+
}
|
|
1280
|
+
if (line.trim() === scan.delimiter) return { ...scan, delimiter: void 0 };
|
|
1281
|
+
return { ...scan, lines: with_body_line(scan.lines, line) };
|
|
1282
|
+
}
|
|
1283
|
+
function command_lines(command) {
|
|
1284
|
+
let scan = { lines: [], delimiter: void 0 };
|
|
1285
|
+
for (const line of command.split(LINE_BREAK2)) scan = scan_line(scan, line);
|
|
1286
|
+
return scan.lines;
|
|
1287
|
+
}
|
|
1288
|
+
var FILE_REDIRECT = /(?:^|\s)>>?\s*(?!&)("[^"]+"|'[^']+'|\S+)/u;
|
|
1289
|
+
var TEE_TARGET = /\btee\s+(?:-a\s+)?(?!-)("[^"]+"|'[^']+'|\S+)/u;
|
|
1290
|
+
var QUOTES = /^['"]|['"]$/gu;
|
|
1291
|
+
function redirect_target(line) {
|
|
1292
|
+
const target = FILE_REDIRECT.exec(line)?.[1] ?? TEE_TARGET.exec(line)?.[1];
|
|
1293
|
+
return target === void 0 ? void 0 : target.replaceAll(QUOTES, "");
|
|
1294
|
+
}
|
|
1295
|
+
var BODY_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
1296
|
+
"python",
|
|
1297
|
+
"python3",
|
|
1298
|
+
"ruby",
|
|
1299
|
+
"perl",
|
|
1300
|
+
"php",
|
|
1301
|
+
"node"
|
|
1302
|
+
]);
|
|
1303
|
+
var NODE = "node";
|
|
1304
|
+
var NODE_EVAL = /(?:^|\s)(?:-e|--eval)\b/u;
|
|
1305
|
+
var WRITE_CALL = /writeFile|appendFile|createWriteStream|\.write(?:lines|_text|_bytes)?\s*\(/u;
|
|
1306
|
+
var WRITE_MODE = /open\s*\([^)]*['"][rbt+]*[wax]|os\.replace|shutil\.(?:copy|move)/u;
|
|
1307
|
+
function body_writes(body) {
|
|
1308
|
+
return WRITE_CALL.test(body) || WRITE_MODE.test(body);
|
|
1309
|
+
}
|
|
1310
|
+
var SCRIPT_FILE = /\.(?:py|rb|pl|php|js|mjs|cjs)$/u;
|
|
1311
|
+
function is_heredoc_write(line, has_file) {
|
|
1312
|
+
const target = has_heredoc(line.text) ? redirect_target(line.text) : void 0;
|
|
1313
|
+
if (target === void 0) return false;
|
|
1314
|
+
if (has_file(target)) return true;
|
|
1315
|
+
return SCRIPT_FILE.test(target) && body_writes(line.body);
|
|
1316
|
+
}
|
|
1317
|
+
function is_interpreter_write(segment, body) {
|
|
1318
|
+
const leading = time_shell.leading_word(segment);
|
|
1319
|
+
if (!BODY_INTERPRETERS.has(leading)) return false;
|
|
1320
|
+
if (has_heredoc(segment)) return body_writes(body);
|
|
1321
|
+
return leading === NODE && NODE_EVAL.test(segment) && body_writes(segment);
|
|
1322
|
+
}
|
|
1323
|
+
var PERL = "perl";
|
|
1324
|
+
var PERL_IN_PLACE = /^-[A-Za-z0-9]*i/u;
|
|
1325
|
+
var WHITESPACE = /\s+/u;
|
|
1326
|
+
function is_in_place_perl(segment) {
|
|
1327
|
+
if (time_shell.leading_word(segment) !== PERL) return false;
|
|
1328
|
+
return segment.split(WHITESPACE).some((word) => PERL_IN_PLACE.test(word));
|
|
1329
|
+
}
|
|
1330
|
+
function has_body_interpreter(line) {
|
|
1331
|
+
return shell_segments.segments_of(line.text).some((segment) => is_interpreter_write(segment, line.body) || is_in_place_perl(segment));
|
|
1332
|
+
}
|
|
1333
|
+
function carries_a_file_body(command, has_file = existsSync) {
|
|
1334
|
+
return command_lines(command).some(
|
|
1335
|
+
(line) => is_heredoc_write(line, has_file) || has_body_interpreter(line)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
var FILE_BODY_REASON = "\u26D4 file body in a shell command: this carries a file's new text inline, which is re-read on every later request. Edit a region with the Edit tool, rewrite a whole file with the Write tool, and pass a body by path (`--body-file <path>`); a new-file `cat > <new-file> <<EOF` stays allowed. This rule fires on every occurrence (`prompts/collaboration-workflow/file-edits.md`).";
|
|
1339
|
+
var ROW5 = {
|
|
1340
|
+
id: "file-body",
|
|
1341
|
+
is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
|
|
1342
|
+
reason: FILE_BODY_REASON,
|
|
1343
|
+
decide: () => true,
|
|
1344
|
+
about: {
|
|
1345
|
+
title: "No file body on the shell",
|
|
1346
|
+
topic: "`file-edits.md`",
|
|
1347
|
+
fires: "a heredoc write to an existing file, a writing `node -e` / interpreter heredoc, or `perl -0pi -e`. Every occurrence",
|
|
1348
|
+
quiet: "a new file, a read-only heredoc, or a short `sed -i`"
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
var file_body = {
|
|
1352
|
+
FILE_BODY_REASON,
|
|
1353
|
+
ROW: ROW5,
|
|
1354
|
+
carries_a_file_body
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
// scripts/rules/git-force.ts
|
|
1358
|
+
var FORCE_LONG_PREFIX = "--force";
|
|
1359
|
+
var DELETE_LONG = "--delete";
|
|
1360
|
+
var DESTRUCTIVE_PUSH_LONG = /* @__PURE__ */ new Set([DELETE_LONG, "--mirror", "--prune"]);
|
|
1361
|
+
var PUSH_SHORT_LETTERS = "fd";
|
|
1362
|
+
var FORCE_OR_DELETE_REFSPEC = /^[+:]/u;
|
|
1363
|
+
function is_force_or_delete_push(args) {
|
|
1364
|
+
return args.some(
|
|
1365
|
+
(argument) => argument.startsWith(FORCE_LONG_PREFIX) || DESTRUCTIVE_PUSH_LONG.has(argument) || git_argv.short_cluster_has(argument, PUSH_SHORT_LETTERS) || FORCE_OR_DELETE_REFSPEC.test(argument)
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
var BRANCH_DELETE_LETTERS = "dD";
|
|
1369
|
+
function is_branch_delete(args) {
|
|
1370
|
+
return args.some(
|
|
1371
|
+
(argument) => argument === DELETE_LONG || git_argv.short_cluster_has(argument, BRANCH_DELETE_LETTERS)
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
var PUSH_SUBCOMMAND = "push";
|
|
1375
|
+
var BRANCH_SUBCOMMAND = "branch";
|
|
1376
|
+
function is_destructive_segment(segment) {
|
|
1377
|
+
const call = git_argv.parse(segment);
|
|
1378
|
+
if (call === void 0) return false;
|
|
1379
|
+
if (call.subcommand === PUSH_SUBCOMMAND) return is_force_or_delete_push(call.args);
|
|
1380
|
+
return call.subcommand === BRANCH_SUBCOMMAND && is_branch_delete(call.args);
|
|
1381
|
+
}
|
|
1382
|
+
function is_force_or_delete(command) {
|
|
1383
|
+
return shell_segments.commands_of(command).some((segment) => is_destructive_segment(segment));
|
|
1384
|
+
}
|
|
1385
|
+
var GIT_FORCE_REASON = "\u26D4 force push or branch delete: this rewrites or removes shared history, which is Tier C and needs explicit current-turn user instruction. Push with `pnpm josh git`; leave a force push or branch delete to the user's own terminal. This rule fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md`).";
|
|
1386
|
+
var ROW6 = {
|
|
1387
|
+
id: "git-force",
|
|
1388
|
+
is_trigger: bash_triggers.on_bash_command(is_force_or_delete),
|
|
1389
|
+
reason: GIT_FORCE_REASON,
|
|
1390
|
+
decide: () => true,
|
|
1391
|
+
about: {
|
|
1392
|
+
title: "Force push / branch delete",
|
|
1393
|
+
topic: "`operating-rules.md`",
|
|
1394
|
+
fires: "a `git push` / `git branch` read as force (`--force` / `-f` / `-uf` \u2026) or delete (`--delete` / `-d` / `-D` / `:branch`) in any spelling. Every occurrence",
|
|
1395
|
+
quiet: "a plain `git push` / `git branch`"
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
var git_force = {
|
|
1399
|
+
GIT_FORCE_REASON,
|
|
1400
|
+
ROW: ROW6,
|
|
1401
|
+
is_force_or_delete
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
// scripts/run/cut/run-cut-decline.ts
|
|
1405
|
+
import { z as z3 } from "zod";
|
|
1406
|
+
var DECLINE_PREFIX = "josh-run-cut-decline-";
|
|
1407
|
+
var decline_schema = z3.object({ issue: z3.string(), hold_taken_at: z3.string().optional() });
|
|
1408
|
+
function decline_path(git_directory) {
|
|
1409
|
+
return stamp_file.stamp_path(DECLINE_PREFIX, git_directory);
|
|
1410
|
+
}
|
|
1411
|
+
function record(git_directory, decline) {
|
|
1412
|
+
stamp_file.replace_stamp(decline_path(git_directory), decline);
|
|
1413
|
+
}
|
|
1414
|
+
function read(git_directory) {
|
|
1415
|
+
const raw = stamp_file.read_stamp_text(decline_path(git_directory));
|
|
1416
|
+
return raw === void 0 ? void 0 : json_value.parse_with(raw, decline_schema);
|
|
1417
|
+
}
|
|
1418
|
+
function matches(recorded, current) {
|
|
1419
|
+
return recorded?.issue === current.issue && recorded.hold_taken_at === current.hold_taken_at;
|
|
1420
|
+
}
|
|
1421
|
+
function is_declined_sync(issue, directory = run_cut.worktree_git_directory_sync()) {
|
|
1422
|
+
if (directory === void 0) return false;
|
|
1423
|
+
const hold_taken_at = run_cut.hold_of(directory)?.taken_at;
|
|
1424
|
+
return matches(read(directory), { issue, hold_taken_at });
|
|
1425
|
+
}
|
|
1426
|
+
async function declined(request, state, code) {
|
|
1427
|
+
if (request.phase !== run_cut.IMPLEMENTATION_PHASE) return code;
|
|
1428
|
+
const directory = await run_cut.worktree_directory();
|
|
1429
|
+
if (directory !== void 0) {
|
|
1430
|
+
record(directory, { issue: request.issue, hold_taken_at: state.hold_taken_at });
|
|
1431
|
+
}
|
|
1432
|
+
return code;
|
|
1433
|
+
}
|
|
1434
|
+
var run_cut_decline = { decline_path, declined, is_declined_sync, read, record };
|
|
1435
|
+
|
|
1436
|
+
// scripts/run/cut/run-cut-ready.ts
|
|
1437
|
+
function is_ready(tree) {
|
|
1438
|
+
return tree.branch !== tree.default_branch && tree.is_dirty;
|
|
1439
|
+
}
|
|
1440
|
+
async function is_lane_branch(state) {
|
|
1441
|
+
const default_branch = await git_command.get_default_branch();
|
|
1442
|
+
return is_ready({ branch: state.branch, default_branch, is_dirty: state.is_dirty });
|
|
1443
|
+
}
|
|
1444
|
+
var STATUS_ARGUMENTS = ["status", "--porcelain=v2", "--branch", UNTRACKED_FILES_FLAG];
|
|
1445
|
+
var HEADER_PREFIX = "#";
|
|
1446
|
+
var BRANCH_HEADER = "# branch.head ";
|
|
1447
|
+
function tree_of(status, default_branch) {
|
|
1448
|
+
const lines = status.split("\n").filter((line) => line !== "");
|
|
1449
|
+
const header = lines.find((line) => line.startsWith(BRANCH_HEADER));
|
|
1450
|
+
if (header === void 0) return void 0;
|
|
1451
|
+
const branch = header.slice(BRANCH_HEADER.length);
|
|
1452
|
+
const is_dirty = lines.some((line) => !line.startsWith(HEADER_PREFIX));
|
|
1453
|
+
return { branch, default_branch, is_dirty };
|
|
1454
|
+
}
|
|
1455
|
+
function read_tree_sync() {
|
|
1456
|
+
const status = git_spawn_sync.read(STATUS_ARGUMENTS);
|
|
1457
|
+
if (status === void 0) return void 0;
|
|
1458
|
+
const reference = git_spawn_sync.read(git_diff_reads.DEFAULT_BRANCH_ARGUMENTS);
|
|
1459
|
+
return tree_of(status, git_diff_reads.default_branch_of(reference));
|
|
1460
|
+
}
|
|
1461
|
+
function is_ready_sync(read2 = read_tree_sync) {
|
|
1462
|
+
const tree = read2();
|
|
1463
|
+
return tree === void 0 || is_ready(tree);
|
|
1464
|
+
}
|
|
1465
|
+
var run_cut_ready = { is_lane_branch, is_ready, is_ready_sync, tree_of };
|
|
1466
|
+
|
|
1467
|
+
// scripts/rules/implementation-cut-verdict.ts
|
|
1468
|
+
import { z as z4 } from "zod";
|
|
1469
|
+
var VERDICT_CACHE_PREFIX = "josh-impl-cut-verdict-";
|
|
1470
|
+
var VERDICT_REUSE_MS = 5e3;
|
|
1471
|
+
var VERDICT_VALUES = [
|
|
1472
|
+
"over",
|
|
1473
|
+
"under",
|
|
1474
|
+
"unmeasurable"
|
|
1475
|
+
];
|
|
1476
|
+
var cache_schema = z4.object({ read_at_ms: z4.number(), verdict: z4.enum(VERDICT_VALUES) });
|
|
1477
|
+
function cache_path(directory) {
|
|
1478
|
+
return stamp_file.stamp_path(VERDICT_CACHE_PREFIX, directory);
|
|
1479
|
+
}
|
|
1480
|
+
function cached_verdict(target, now_ms) {
|
|
1481
|
+
const raw = stamp_file.read_stamp_text(target);
|
|
1482
|
+
if (raw === void 0) return void 0;
|
|
1483
|
+
const cached = json_value.parse_with(raw, cache_schema);
|
|
1484
|
+
if (cached === void 0) return void 0;
|
|
1485
|
+
return now_ms - cached.read_at_ms < VERDICT_REUSE_MS ? cached.verdict : void 0;
|
|
1486
|
+
}
|
|
1487
|
+
function reused_verdict(read2, directory = process.cwd(), now_ms = Date.now()) {
|
|
1488
|
+
const target = cache_path(directory);
|
|
1489
|
+
const cached = cached_verdict(target, now_ms);
|
|
1490
|
+
if (cached !== void 0) return cached;
|
|
1491
|
+
const value = read2();
|
|
1492
|
+
stamp_file.replace_stamp(target, { read_at_ms: now_ms, verdict: value });
|
|
1493
|
+
return value;
|
|
1494
|
+
}
|
|
1495
|
+
var implementation_cut_verdict = {
|
|
1496
|
+
VERDICT_REUSE_MS,
|
|
1497
|
+
cache_path,
|
|
1498
|
+
reused_verdict
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
// scripts/rules/implementation-cut.ts
|
|
1502
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write"]);
|
|
1503
|
+
function is_edit_tool(name) {
|
|
1504
|
+
return EDIT_TOOLS.has(name);
|
|
1505
|
+
}
|
|
1506
|
+
var CUT_COMMANDS = /* @__PURE__ */ new Set(["run:cut"]);
|
|
1507
|
+
var IMPL_FLAG = /(?:^|\s)--impl(?:[=\s]|$)/u;
|
|
1508
|
+
function is_impl_cut_segment(segment) {
|
|
1509
|
+
return shell_segments.is_josh_command(segment, CUT_COMMANDS) && IMPL_FLAG.test(segment);
|
|
1510
|
+
}
|
|
1511
|
+
function takes_the_impl_cut(command) {
|
|
1512
|
+
return shell_segments.segments_of(command).some((segment) => is_impl_cut_segment(segment));
|
|
1513
|
+
}
|
|
1514
|
+
function can_cut_sync(issue) {
|
|
1515
|
+
return !run_cut_decline.is_declined_sync(issue) && run_cut_ready.is_ready_sync();
|
|
1516
|
+
}
|
|
1517
|
+
function current_state() {
|
|
1518
|
+
return {
|
|
1519
|
+
directory: process.cwd(),
|
|
1520
|
+
source: process.env,
|
|
1521
|
+
carried: run_cut.carried_cut_sync,
|
|
1522
|
+
held_issue: run_cut.held_issue_sync,
|
|
1523
|
+
// **Read through the short reuse window**. The row now fires per
|
|
1524
|
+
// threshold crossing rather than once per run, so this predicate is a candidate on every edit;
|
|
1525
|
+
// the window collapses one turn's burst of edits to a single whole-transcript price.
|
|
1526
|
+
verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict()),
|
|
1527
|
+
can_cut: can_cut_sync
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function lane_child_issue(state) {
|
|
1531
|
+
if (!lane_child_marker.is_child_of(state.directory, state.source)) return void 0;
|
|
1532
|
+
return lane_paths.lane_issue_of(state.directory, { ...state.source });
|
|
1533
|
+
}
|
|
1534
|
+
function held_run_issue(state) {
|
|
1535
|
+
if (lane_paths.lane_issue_of(state.directory, { ...state.source }) !== void 0) return void 0;
|
|
1536
|
+
return state.held_issue();
|
|
1537
|
+
}
|
|
1538
|
+
function implementing_run_issue(state) {
|
|
1539
|
+
if (agent_session_role.is_reviewer(state.source)) return void 0;
|
|
1540
|
+
return lane_child_issue(state) ?? held_run_issue(state);
|
|
1541
|
+
}
|
|
1542
|
+
function uncut_run_issue(state) {
|
|
1543
|
+
const issue = implementing_run_issue(state);
|
|
1544
|
+
if (issue === void 0) return void 0;
|
|
1545
|
+
return state.carried()?.issue === issue ? void 0 : issue;
|
|
1546
|
+
}
|
|
1547
|
+
function warrants_the_cut(state) {
|
|
1548
|
+
const verdict = state.verdict();
|
|
1549
|
+
if (lane_child_issue(state) === void 0) return verdict === cost_verdict.OVER_VERDICT;
|
|
1550
|
+
return verdict !== cost_verdict.UNDER_VERDICT;
|
|
1551
|
+
}
|
|
1552
|
+
function is_over_threshold_edit(call, state = current_state()) {
|
|
1553
|
+
if (!is_edit_tool(call.name)) return false;
|
|
1554
|
+
const issue = uncut_run_issue(state);
|
|
1555
|
+
if (issue === void 0) return false;
|
|
1556
|
+
return warrants_the_cut(state) && state.can_cut(issue);
|
|
1557
|
+
}
|
|
1558
|
+
var THRESHOLD_TEXT = `${figure_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
|
|
1559
|
+
var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: refused on every edit past the ${THRESHOLD_TEXT} line. Write \`${run_cut_handoff.HANDOFF_PATH}\` as ${run_cut_handoff.HANDOFF_FORMAT}, run \`pnpm josh run:cut --impl <N> --handoff <path>\`; \`cut\`: end the turn immediately, no Telegram is owed; else reissue the edit (\`.claude/skills/workflow-commands/pre-gate-cut.md\`).`;
|
|
1560
|
+
var REISSUE_WINDOW_MS = 9e4;
|
|
1561
|
+
function is_reissued_refusal(now_ms, delivered_at_ms) {
|
|
1562
|
+
return now_ms - delivered_at_ms < REISSUE_WINDOW_MS;
|
|
1563
|
+
}
|
|
1564
|
+
function decide2(_call, run, _can_record, delivered_at_ms) {
|
|
1565
|
+
return !is_reissued_refusal(run.now_ms, delivered_at_ms);
|
|
1566
|
+
}
|
|
1567
|
+
var ROW7 = {
|
|
1568
|
+
id: "implementation-cut",
|
|
1569
|
+
is_trigger: is_over_threshold_edit,
|
|
1570
|
+
reason: IMPLEMENTATION_CUT_REASON,
|
|
1571
|
+
decide: decide2,
|
|
1572
|
+
keeps: bash_triggers.on_bash_command(takes_the_impl_cut),
|
|
1573
|
+
// The opening the reason carried before it was cut to the action and the next command.
|
|
1574
|
+
former_reasons: [
|
|
1575
|
+
"\u26D4 implementation-phase cut: this checkout is a lane dispatched",
|
|
1576
|
+
"\u26D4 implementation-phase cut: this run's context crossed the"
|
|
1577
|
+
],
|
|
1578
|
+
about: {
|
|
1579
|
+
title: "The implementation-phase cut",
|
|
1580
|
+
topic: '`.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "It is a guard, fired at the edit that crosses the threshold"',
|
|
1581
|
+
fires: "an `Edit` / `Write` by an uncut lane child whose recent context is over the `pnpm josh cost --cut` threshold; it hands over `pnpm josh run:cut --impl <N> --handoff <path>`. At each threshold crossing",
|
|
1582
|
+
quiet: "not a lane child, a non-editing tool, already cut, or under the threshold"
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
var implementation_cut = {
|
|
1586
|
+
IMPLEMENTATION_CUT_REASON,
|
|
1587
|
+
REISSUE_WINDOW_MS,
|
|
1588
|
+
ROW: ROW7,
|
|
1589
|
+
decide: decide2,
|
|
1590
|
+
is_over_threshold_edit,
|
|
1591
|
+
is_reissued_refusal,
|
|
1592
|
+
takes_the_impl_cut,
|
|
1593
|
+
uncut_run_issue
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
// scripts/rules/prior-comment-read.ts
|
|
1597
|
+
var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
|
|
1598
|
+
var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
|
|
1599
|
+
var ISSUE_READ_ARGUMENTS = /\b(?:issue:read|run:entry|run:prep)\b([\s\d]*)/u;
|
|
1600
|
+
var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
|
|
1601
|
+
var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
|
|
1602
|
+
var ISSUE_READ_COMMAND = /\b(?:issue:read|run:entry|run:prep)\b/u;
|
|
1603
|
+
function add_issue_read_numbers(command, numbers) {
|
|
1604
|
+
const match = ISSUE_READ_ARGUMENTS.exec(command);
|
|
1605
|
+
if (match === null) return;
|
|
1606
|
+
const tokens = (match[1] ?? "").split(/\s+/u);
|
|
1607
|
+
for (const token of tokens) {
|
|
1608
|
+
if (issue_number_shape.ISSUE_NUMBER_PATTERN.test(token)) numbers.add(Number(token));
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function issue_numbers_of(command) {
|
|
1612
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
1613
|
+
for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
|
|
1614
|
+
const view = ISSUE_VIEW_NUMBER.exec(command);
|
|
1615
|
+
if (view !== null) numbers.add(Number(view[1]));
|
|
1616
|
+
add_issue_read_numbers(command, numbers);
|
|
1617
|
+
return numbers;
|
|
1618
|
+
}
|
|
1619
|
+
function fetches_comments(command) {
|
|
1620
|
+
return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
|
|
1621
|
+
}
|
|
1622
|
+
function reads_target_comments(command, target) {
|
|
1623
|
+
if (!fetches_comments(command)) return false;
|
|
1624
|
+
for (const number of issue_numbers_of(command)) {
|
|
1625
|
+
if (target.has(number)) return true;
|
|
1626
|
+
}
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
function already_read_comments_for(tail, command) {
|
|
1630
|
+
const target = issue_numbers_of(command);
|
|
1631
|
+
if (target.size === 0) return false;
|
|
1632
|
+
return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
|
|
1633
|
+
}
|
|
1634
|
+
function already_read_for_call(tail, call) {
|
|
1635
|
+
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
1636
|
+
return already_read_comments_for(tail, time_shell.bash_command(call.input));
|
|
1637
|
+
}
|
|
1638
|
+
var prior_comment_read = {
|
|
1639
|
+
already_read_comments_for,
|
|
1640
|
+
already_read_for_call,
|
|
1641
|
+
fetches_comments,
|
|
1642
|
+
issue_numbers_of
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
// scripts/rules/issue-comments.ts
|
|
1646
|
+
var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
|
|
1647
|
+
var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
|
|
1648
|
+
var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
|
|
1649
|
+
var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
|
|
1650
|
+
var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
|
|
1651
|
+
var NAMES_THE_BODY = /\bbody\b/u;
|
|
1652
|
+
function projects_away_body(segment) {
|
|
1653
|
+
const projection = FIELD_PROJECTION.exec(segment);
|
|
1654
|
+
return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
|
|
1655
|
+
}
|
|
1656
|
+
function is_body_read_segment(segment) {
|
|
1657
|
+
if (projects_away_body(segment)) return false;
|
|
1658
|
+
if (ISSUE_VIEW_COMMAND.test(segment)) return true;
|
|
1659
|
+
return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
|
|
1660
|
+
}
|
|
1661
|
+
function fetches_issue_comments(segment) {
|
|
1662
|
+
if (!FETCHES_COMMENTS.test(segment)) return false;
|
|
1663
|
+
return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
|
|
1664
|
+
}
|
|
1665
|
+
function is_body_only_issue_read(command) {
|
|
1666
|
+
const segments = shell_segments.segments_of(command);
|
|
1667
|
+
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
1668
|
+
return segments.some((segment) => is_body_read_segment(segment));
|
|
1669
|
+
}
|
|
1670
|
+
function reads_issue_comments(command) {
|
|
1671
|
+
return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
|
|
1672
|
+
}
|
|
1673
|
+
var RULE_DOC = "`.claude/skills/workflow-commands/issue-comments.md`";
|
|
1674
|
+
var COMMENTS_FALLBACK = "gh api repos/{owner}/{repo}/issues/<N>/comments";
|
|
1675
|
+
var ISSUE_COMMENTS_REASON = `\u26D4 issue comments: pnpm josh issue:read <N>
|
|
1676
|
+
Run that in place of this read \u2014 an Issue's comments are part of it, and a later comment supersedes the body it contradicts; refused until that command is on the transcript. Without josh, read \`${COMMENTS_FALLBACK}\` beside the body (${RULE_DOC}).`;
|
|
1677
|
+
var COMMENTS_JQ = String.raw`'.[] | "— \(.user.login) \(.created_at)\n\(.body)\n"'`;
|
|
1678
|
+
var COMMENTS_NOTE = `\u21BB issue comments: this read returned only the Issue's body, so the Issue's comments were fetched on the same line \u2014 a later comment supersedes the body it contradicts (${RULE_DOC}).`;
|
|
1679
|
+
function rewritable_path(command) {
|
|
1680
|
+
const read2 = shell_segments.first_of(command);
|
|
1681
|
+
if (read2 === void 0 || ISSUE_VIEW_COMMAND.test(read2)) return void 0;
|
|
1682
|
+
return is_body_read_segment(read2) ? ISSUE_BODY_PATH.exec(read2)?.[0] : void 0;
|
|
1683
|
+
}
|
|
1684
|
+
function comments_command(command) {
|
|
1685
|
+
if (!is_body_only_issue_read(command)) return void 0;
|
|
1686
|
+
const path5 = rewritable_path(command);
|
|
1687
|
+
if (path5 === void 0) return void 0;
|
|
1688
|
+
return `gh api --paginate ${path5}/comments --jq ${COMMENTS_JQ}
|
|
1689
|
+
${command}`;
|
|
1690
|
+
}
|
|
1691
|
+
function comments_input(call, tail) {
|
|
1692
|
+
if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
|
|
1693
|
+
const command = comments_command(time_shell.bash_command(call.input));
|
|
1694
|
+
if (command === void 0 || prior_comment_read.already_read_for_call(tail(), call)) {
|
|
1695
|
+
return void 0;
|
|
1696
|
+
}
|
|
1697
|
+
return { ...call.input, command };
|
|
1698
|
+
}
|
|
1699
|
+
var ROW8 = {
|
|
1700
|
+
id: "issue-comments",
|
|
1701
|
+
is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
|
|
1702
|
+
reason: ISSUE_COMMENTS_REASON,
|
|
1703
|
+
already_satisfied: prior_comment_read.already_read_for_call,
|
|
1704
|
+
keeps: bash_triggers.on_bash_command(reads_issue_comments),
|
|
1705
|
+
rewrite: comments_input,
|
|
1706
|
+
rewrite_note: COMMENTS_NOTE,
|
|
1707
|
+
// The opening the reason carried before it led with the command.
|
|
1708
|
+
former_reasons: ["\u26D4 an Issue's comments are part of the Issue: read them before implementing"],
|
|
1709
|
+
about: {
|
|
1710
|
+
title: "Reading Issue comments",
|
|
1711
|
+
topic: "`issue-comments.md`",
|
|
1712
|
+
fires: "a `Bash` that reads only the Issue body (`gh issue view <N>`, or a GET ending in `\u2026/issues/<N>`). Refused until it is re-read with the comments",
|
|
1713
|
+
quiet: "no Issue read, or a read with `--comments` / `--json comments` / `\u2026/comments` from the start"
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
var issue_comments = {
|
|
1717
|
+
COMMENTS_NOTE,
|
|
1718
|
+
ISSUE_COMMENTS_REASON,
|
|
1719
|
+
ROW: ROW8,
|
|
1720
|
+
comments_input,
|
|
1721
|
+
is_body_only_issue_read
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
// scripts/rules/josh-git-bare.ts
|
|
1725
|
+
var GIT_COMMAND = /* @__PURE__ */ new Set(["git"]);
|
|
1726
|
+
var CONFIRM_FLAGS = /* @__PURE__ */ new Set(["-y", "--yes"]);
|
|
1727
|
+
function has_confirm_flag(args) {
|
|
1728
|
+
return args.some((argument) => CONFIRM_FLAGS.has(argument));
|
|
1729
|
+
}
|
|
1730
|
+
function is_bare_segment(segment) {
|
|
1731
|
+
if (!shell_segments.is_josh_command(segment, GIT_COMMAND)) return false;
|
|
1732
|
+
return !has_confirm_flag(time_shell.josh_arguments(segment));
|
|
1733
|
+
}
|
|
1734
|
+
function is_bare_josh_git(command) {
|
|
1735
|
+
return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
|
|
1736
|
+
}
|
|
1737
|
+
var JOSH_GIT_BARE_REASON = '\u26D4 bare `pnpm josh git`: this prompts to confirm the staging, and with no TTY the prompt falls to its cancel branch. Run `pnpm josh git -y "<title> #<N>"`, or `pnpm josh git -y --skip-commit --skip-push` to open the PR for a push that already landed. It fires on every occurrence.';
|
|
1738
|
+
var ROW9 = {
|
|
1739
|
+
id: "josh-git-bare",
|
|
1740
|
+
is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
|
|
1741
|
+
reason: JOSH_GIT_BARE_REASON,
|
|
1742
|
+
decide: () => true,
|
|
1743
|
+
about: {
|
|
1744
|
+
title: "Bare `pnpm josh git`",
|
|
1745
|
+
topic: "`CLAUDE.md` \u2192 Git Rules",
|
|
1746
|
+
fires: "`pnpm josh git` without `-y`, whose staging prompt cancels with no TTY. Every occurrence",
|
|
1747
|
+
quiet: "`pnpm josh git -y`"
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
var josh_git_bare = {
|
|
1751
|
+
JOSH_GIT_BARE_REASON,
|
|
1752
|
+
ROW: ROW9,
|
|
1753
|
+
is_bare_josh_git
|
|
1754
|
+
};
|
|
1755
|
+
|
|
1756
|
+
// scripts/rules/lane-carry-conflict.ts
|
|
1757
|
+
var RUN_BUDGET_COMMANDS = /* @__PURE__ */ new Set(["run:merge", "run:carry"]);
|
|
1758
|
+
var LEADING_ASSIGNMENT = String.raw`[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\$\((?:[^()]|\([^()]*\))*\)|[^\s"'])*\s+`;
|
|
1759
|
+
var REDIRECTED_PREFIX = new RegExp(
|
|
1760
|
+
String.raw`^(?:${LEADING_ASSIGNMENT})*${platform_temporary.TEMP_ROOT_KEY}=(?!""|''|\s|$|"?\$\{?[A-Za-z_])`,
|
|
1761
|
+
"u"
|
|
1762
|
+
);
|
|
1763
|
+
var SHARED_STATE_COMMANDS = /* @__PURE__ */ new Set(["run:merge"]);
|
|
1764
|
+
var LEADING_ASSIGNMENTS = new RegExp(`^(?:${LEADING_ASSIGNMENT})*`, "u");
|
|
1765
|
+
function is_budget_segment(segment) {
|
|
1766
|
+
const commands = REDIRECTED_PREFIX.test(segment) ? SHARED_STATE_COMMANDS : RUN_BUDGET_COMMANDS;
|
|
1767
|
+
return shell_segments.is_josh_command(segment.replace(LEADING_ASSIGNMENTS, ""), commands);
|
|
1768
|
+
}
|
|
1769
|
+
function invokes_budget_command(command) {
|
|
1770
|
+
return shell_segments.segments_of(command).some((segment) => is_budget_segment(segment));
|
|
1771
|
+
}
|
|
1772
|
+
function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
|
|
1773
|
+
if (!invokes_budget_command(command)) return false;
|
|
1774
|
+
return is_lane_child;
|
|
1775
|
+
}
|
|
1776
|
+
var LANE_CARRY_CONFLICT_REASON = "\u26D4 lane child carry conflict: this session is a dispatched lane child, and `run:merge` / `run:carry` are the parent budget commands; the live record belongs to the parent, not to a competing run. Continue implementing this Issue: no `needs-decision`, no Telegram. Live `run:carry` evidence takes a fresh `JOSH_TEMP_ROOT`; `run:merge` is refused under any root, on every occurrence.";
|
|
1777
|
+
var ROW10 = {
|
|
1778
|
+
id: "lane-carry-conflict",
|
|
1779
|
+
is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
|
|
1780
|
+
reason: LANE_CARRY_CONFLICT_REASON,
|
|
1781
|
+
decide: () => true,
|
|
1782
|
+
about: {
|
|
1783
|
+
title: "Lane child carry commands",
|
|
1784
|
+
topic: "`.claude/skills/workflow-commands/backlogrun-lanes.md`",
|
|
1785
|
+
fires: "a dispatched lane child running `pnpm josh run:merge` / `run:carry`. Every occurrence",
|
|
1786
|
+
quiet: "not a lane child \u2014 the parent owns the run budget"
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
var lane_carry_conflict = {
|
|
1790
|
+
LANE_CARRY_CONFLICT_REASON,
|
|
1791
|
+
ROW: ROW10,
|
|
1792
|
+
invokes_budget_command,
|
|
1793
|
+
is_carry_conflict
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
// scripts/gate/command-memory.ts
|
|
1797
|
+
import os from "node:os";
|
|
1798
|
+
var JOSH_PROGRAM = "josh";
|
|
1799
|
+
var RESERVED_RUN = "reserved-run";
|
|
1800
|
+
var COMMAND_SEPARATOR = "--";
|
|
1801
|
+
var ESLINT = "eslint";
|
|
1802
|
+
var VITEST = "vitest";
|
|
1803
|
+
var AUTO_WORKERS = "auto";
|
|
1804
|
+
var LINE_BREAK3 = "\n";
|
|
1805
|
+
var SHELL_HEREDOC = /\b(?:ba|z|da|k)?sh(?:\s+-\w+)*\s*<<-?\s*(['"]?)(\w+)\1/u;
|
|
1806
|
+
var HEREDOC_DELIMITER_GROUP = 2;
|
|
1807
|
+
var AUTO_CORE_SHARE = 2;
|
|
1808
|
+
var SINGLE_WORKER = 1;
|
|
1809
|
+
var WORKER_FLAGS = {
|
|
1810
|
+
[ESLINT]: "--concurrency",
|
|
1811
|
+
[VITEST]: "--maxWorkers"
|
|
1812
|
+
};
|
|
1813
|
+
function wrapped_command_of(call) {
|
|
1814
|
+
if (call.name !== JOSH_PROGRAM || call.args[0] !== RESERVED_RUN) return void 0;
|
|
1815
|
+
const separator = call.args.indexOf(COMMAND_SEPARATOR);
|
|
1816
|
+
return separator === -1 ? void 0 : call.args.slice(separator + 1).join(" ");
|
|
1817
|
+
}
|
|
1818
|
+
function reading_of(segment, is_reserved) {
|
|
1819
|
+
const call = git_argv.program_of(segment);
|
|
1820
|
+
const wrapped2 = call === void 0 ? void 0 : wrapped_command_of(call);
|
|
1821
|
+
if (wrapped2 !== void 0) return { line: wrapped2, is_reserved: true };
|
|
1822
|
+
const script = bash_triggers.shell_script_of(segment);
|
|
1823
|
+
if (script !== void 0) return { line: script, is_reserved };
|
|
1824
|
+
return call === void 0 ? void 0 : { ...call, is_reserved };
|
|
1825
|
+
}
|
|
1826
|
+
function body_until(lines, delimiter) {
|
|
1827
|
+
const end = lines.findIndex((body_line) => body_line.trim() === delimiter);
|
|
1828
|
+
return (end === -1 ? lines : lines.slice(0, end)).join(LINE_BREAK3);
|
|
1829
|
+
}
|
|
1830
|
+
function shell_heredoc_bodies(line) {
|
|
1831
|
+
const lines = line.split(LINE_BREAK3);
|
|
1832
|
+
return lines.flatMap((opening, index) => {
|
|
1833
|
+
const delimiter = SHELL_HEREDOC.exec(opening)?.[HEREDOC_DELIMITER_GROUP];
|
|
1834
|
+
return delimiter === void 0 ? [] : body_until(lines.slice(index + 1), delimiter);
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
function is_tool_call(reading) {
|
|
1838
|
+
return "name" in reading;
|
|
1839
|
+
}
|
|
1840
|
+
function is_pending_line(reading) {
|
|
1841
|
+
return !is_tool_call(reading);
|
|
1842
|
+
}
|
|
1843
|
+
function tool_calls_of(line) {
|
|
1844
|
+
const pending = [{ line, is_reserved: false }];
|
|
1845
|
+
const calls = [];
|
|
1846
|
+
for (let next = pending.shift(); next !== void 0; next = pending.shift()) {
|
|
1847
|
+
const { is_reserved } = next;
|
|
1848
|
+
const readings = shell_segments.quote_aware_commands_of(time_shell.without_heredoc_bodies(next.line)).flatMap((segment) => reading_of(segment, is_reserved) ?? []);
|
|
1849
|
+
const bodies = shell_heredoc_bodies(next.line).map((body) => ({ line: body, is_reserved }));
|
|
1850
|
+
calls.push(...readings.filter((reading) => is_tool_call(reading)));
|
|
1851
|
+
pending.push(...readings.filter((reading) => is_pending_line(reading)), ...bodies);
|
|
1852
|
+
}
|
|
1853
|
+
return calls;
|
|
1854
|
+
}
|
|
1855
|
+
function flag_value(args, flag) {
|
|
1856
|
+
const prefix = `${flag}=`;
|
|
1857
|
+
const index = args.findIndex((argument2) => argument2 === flag || argument2.startsWith(prefix));
|
|
1858
|
+
const argument = args[index];
|
|
1859
|
+
if (argument === void 0) return void 0;
|
|
1860
|
+
return argument === flag ? args[index + 1] : argument.slice(prefix.length);
|
|
1861
|
+
}
|
|
1862
|
+
function default_workers(name, cores) {
|
|
1863
|
+
return name === VITEST ? Math.max(SINGLE_WORKER, cores - 1) : SINGLE_WORKER;
|
|
1864
|
+
}
|
|
1865
|
+
function count_of(value, cores) {
|
|
1866
|
+
if (value === AUTO_WORKERS) return Math.max(SINGLE_WORKER, Math.floor(cores / AUTO_CORE_SHARE));
|
|
1867
|
+
const count = Number(value);
|
|
1868
|
+
return Number.isSafeInteger(count) && count > SINGLE_WORKER ? count : SINGLE_WORKER;
|
|
1869
|
+
}
|
|
1870
|
+
function workers_of(call, cores = os.availableParallelism()) {
|
|
1871
|
+
const flag = WORKER_FLAGS[call.name];
|
|
1872
|
+
const value = flag === void 0 ? void 0 : flag_value(call.args, flag);
|
|
1873
|
+
return value === void 0 ? default_workers(call.name, cores) : count_of(value, cores);
|
|
1874
|
+
}
|
|
1875
|
+
function memory_of(call, cores) {
|
|
1876
|
+
const row = core_weights.TOOL_MEMORY[call.name];
|
|
1877
|
+
return row === void 0 ? void 0 : core_weights.MEMORY_MB[row] * workers_of(call, cores);
|
|
1878
|
+
}
|
|
1879
|
+
function estimated_memory_mb(line, cores = os.availableParallelism()) {
|
|
1880
|
+
const amounts = tool_calls_of(line).flatMap((call) => memory_of(call, cores) ?? []);
|
|
1881
|
+
return amounts.length === 0 ? void 0 : Math.max(...amounts);
|
|
1882
|
+
}
|
|
1883
|
+
function runs_multi_worker_eslint(line, cores = os.availableParallelism()) {
|
|
1884
|
+
return tool_calls_of(line).some(
|
|
1885
|
+
(call) => call.name === ESLINT && workers_of(call, cores) > SINGLE_WORKER
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
var command_memory = { estimated_memory_mb, runs_multi_worker_eslint, tool_calls_of };
|
|
1889
|
+
|
|
1890
|
+
// scripts/rules/lane-direct-tool.ts
|
|
1891
|
+
var HEAVY_TOOLS = /* @__PURE__ */ new Set(["eslint", "vitest", "cspell", "tsc"]);
|
|
1892
|
+
function runs_heavy_tool(command) {
|
|
1893
|
+
return command_memory.tool_calls_of(command).some((call) => !call.is_reserved && HEAVY_TOOLS.has(call.name));
|
|
1894
|
+
}
|
|
1895
|
+
function is_lane_direct_tool(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
|
|
1896
|
+
if (!is_lane_child) return false;
|
|
1897
|
+
return runs_heavy_tool(command) || command_memory.runs_multi_worker_eslint(command);
|
|
1898
|
+
}
|
|
1899
|
+
var LANE_DIRECT_TOOL_REASON = "\u26D4 lane child running `eslint` / `vitest` / `cspell` / `tsc` directly, or multi-worker eslint: it overlaps the other lanes. Run `pnpm josh lint:related`, `pnpm josh test:related`, `pnpm josh cspell:dot` or `pnpm josh reserved-run <weight> -- <command>`, eslint at `--concurrency off`; measure concurrency outside a lane. Refused on every occurrence.";
|
|
1900
|
+
var ROW11 = {
|
|
1901
|
+
id: "lane-direct-tool",
|
|
1902
|
+
is_trigger: bash_triggers.on_bash_command(is_lane_direct_tool),
|
|
1903
|
+
reason: LANE_DIRECT_TOOL_REASON,
|
|
1904
|
+
decide: () => true,
|
|
1905
|
+
about: {
|
|
1906
|
+
title: "Lane child running a heavy tool directly",
|
|
1907
|
+
topic: "`docs/josh-commands-automation.md` \u2192 `josh reserved-run`",
|
|
1908
|
+
fires: "a dispatched lane child launching `eslint` / `vitest` / `cspell` / `tsc` (bare, `pnpm exec`, `npx`, inside `sh -c`), or eslint with `--concurrency` other than `off` / `1` by any path, `reserved-run` included. Every occurrence",
|
|
1909
|
+
quiet: "not a lane child, or the run goes through `pnpm josh` with single-worker eslint"
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
var lane_direct_tool = {
|
|
1913
|
+
LANE_DIRECT_TOOL_REASON,
|
|
1914
|
+
ROW: ROW11,
|
|
1915
|
+
is_lane_direct_tool,
|
|
1916
|
+
runs_heavy_tool
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
// scripts/rules/lane-interactive-ask.ts
|
|
1920
|
+
function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
|
|
1921
|
+
if (!interactive_ask.is_interactive_tool(call.name)) return false;
|
|
1922
|
+
return is_lane_child;
|
|
1923
|
+
}
|
|
1924
|
+
var LANE_INTERACTIVE_ASK_REASON = "\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so park instead: `pnpm josh run:park <N> --body-file <path>` (it applies `needs-decision`), the `confirmation` Telegram, stop. It fires on every occurrence (`.claude/skills/workflow-commands/pre-gate-cut.md`, `backlogrun-park.md`).";
|
|
1925
|
+
var ROW12 = {
|
|
1926
|
+
id: "lane-interactive-ask",
|
|
1927
|
+
is_trigger: is_interactive_ask,
|
|
1928
|
+
reason: LANE_INTERACTIVE_ASK_REASON,
|
|
1929
|
+
decide: () => true,
|
|
1930
|
+
about: {
|
|
1931
|
+
title: "Lane child interactive ask",
|
|
1932
|
+
topic: '`.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "The interactive ask is refused one call earlier"',
|
|
1933
|
+
fires: "a dispatched lane child's `AskUserQuestion`; it hands over the park procedure. Every occurrence",
|
|
1934
|
+
quiet: "not a lane child, or not an interactive tool"
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW12, is_interactive_ask };
|
|
1938
|
+
|
|
1939
|
+
// scripts/rules/lane-split-park.ts
|
|
1940
|
+
var EPIC_COMMANDS = /* @__PURE__ */ new Set(["epic"]);
|
|
1941
|
+
var PROMOTED_ISSUE = /--promote[=\s]+["']?(?<issue>\d+)\b/u;
|
|
1942
|
+
var LABEL_TARGETS = [
|
|
1943
|
+
/\/issues\/(?<issue>\d+)\/labels\b/u,
|
|
1944
|
+
/\bissue\s+edit\s+["']?(?<issue>\d+)\b/u,
|
|
1945
|
+
/\brun:park\s+["']?(?<issue>\d+)\b/u
|
|
1946
|
+
];
|
|
1947
|
+
var ISSUE_URL = /--issue-url[=\s]+["']?([^\s"']+)/u;
|
|
1948
|
+
var URL_ISSUE = /\/issues\/(\d+)\b/u;
|
|
1949
|
+
function promoted_in(segment) {
|
|
1950
|
+
if (!shell_segments.is_josh_command(segment, EPIC_COMMANDS)) return void 0;
|
|
1951
|
+
return PROMOTED_ISSUE.exec(segment)?.groups?.["issue"];
|
|
1952
|
+
}
|
|
1953
|
+
function promoted_issues(commands) {
|
|
1954
|
+
const segments = commands.flatMap((command) => shell_segments.segments_of(command));
|
|
1955
|
+
const issues = segments.map((segment) => promoted_in(segment));
|
|
1956
|
+
return new Set(issues.filter((issue) => issue !== void 0));
|
|
1957
|
+
}
|
|
1958
|
+
function label_target(command) {
|
|
1959
|
+
for (const pattern of LABEL_TARGETS) {
|
|
1960
|
+
const issue = pattern.exec(command)?.groups?.["issue"];
|
|
1961
|
+
if (issue !== void 0) return issue;
|
|
1962
|
+
}
|
|
1963
|
+
return void 0;
|
|
1964
|
+
}
|
|
1965
|
+
function is_park_of(command, issue) {
|
|
1966
|
+
return lane_park.records_the_park(command) && label_target(command) === issue;
|
|
1967
|
+
}
|
|
1968
|
+
function notified_issue(command) {
|
|
1969
|
+
const [, url = ""] = ISSUE_URL.exec(command) ?? [];
|
|
1970
|
+
const [, issue] = URL_ISSUE.exec(url) ?? [];
|
|
1971
|
+
return issue;
|
|
1972
|
+
}
|
|
1973
|
+
function is_notify_of(command, issue) {
|
|
1974
|
+
return lane_park.is_confirmation_notify(command) && notified_issue(command) === issue;
|
|
1975
|
+
}
|
|
1976
|
+
function is_candidate2(command) {
|
|
1977
|
+
const issue = lane_child_marker.marked_issue();
|
|
1978
|
+
if (issue === void 0) return false;
|
|
1979
|
+
if (!is_park_of(command, issue) && !is_notify_of(command, issue)) return false;
|
|
1980
|
+
return lane_child_marker.is_child_of(process.cwd());
|
|
1981
|
+
}
|
|
1982
|
+
function decide3(_call, run) {
|
|
1983
|
+
const issue = lane_child_marker.marked_issue();
|
|
1984
|
+
const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run.transcript));
|
|
1985
|
+
return issue !== void 0 && promoted_issues(commands).has(issue);
|
|
1986
|
+
}
|
|
1987
|
+
var LANE_SPLIT_PARK_REASON = '\u26D4 lane split park: this session is a dispatched lane child that promoted its own Issue to an epic this run, and a split is not a park: no `needs-decision` on the epic, no `confirmation` Telegram, on every occurrence. Park a child issue if the rest needs a person, else end the turn (`.claude/skills/workflow-commands/backlogrun-park.md` \u2192 "Splitting a child mid-run").';
|
|
1988
|
+
var ROW13 = {
|
|
1989
|
+
id: "lane-split-park",
|
|
1990
|
+
is_trigger: bash_triggers.on_bash_command(is_candidate2),
|
|
1991
|
+
reason: LANE_SPLIT_PARK_REASON,
|
|
1992
|
+
decide: decide3,
|
|
1993
|
+
about: {
|
|
1994
|
+
title: "A split is not a park",
|
|
1995
|
+
topic: '`.claude/skills/workflow-commands/backlogrun-park.md` \u2192 "Splitting a child mid-run"',
|
|
1996
|
+
fires: "a lane child that promoted its Issue to an epic this run parking it (`needs-decision`, `confirmation` notify). Every occurrence",
|
|
1997
|
+
quiet: "no promotion this run"
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
var lane_split_park = {
|
|
2001
|
+
LANE_SPLIT_PARK_REASON,
|
|
2002
|
+
ROW: ROW13,
|
|
2003
|
+
promoted_issues
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// scripts/rules/lane-switch-main.ts
|
|
2007
|
+
var SWITCH_SUBCOMMAND = "switch";
|
|
2008
|
+
var NON_COLLIDING_FLAGS = /* @__PURE__ */ new Set([
|
|
2009
|
+
"-c",
|
|
2010
|
+
"-C",
|
|
2011
|
+
"--create",
|
|
2012
|
+
"--force-create",
|
|
2013
|
+
"-d",
|
|
2014
|
+
"--detach"
|
|
2015
|
+
]);
|
|
2016
|
+
var FLAG_PREFIX2 = "-";
|
|
2017
|
+
function switch_target(segment) {
|
|
2018
|
+
const call = git_argv.parse(segment);
|
|
2019
|
+
if (call?.subcommand !== SWITCH_SUBCOMMAND || call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))) {
|
|
2020
|
+
return void 0;
|
|
2021
|
+
}
|
|
2022
|
+
return call.args.find((argument) => !argument.startsWith(FLAG_PREFIX2));
|
|
2023
|
+
}
|
|
2024
|
+
function switch_targets(command) {
|
|
2025
|
+
return shell_segments.segments_of(command).map((segment) => switch_target(segment)).filter((target) => target !== void 0);
|
|
2026
|
+
}
|
|
2027
|
+
function own_lane_branch(directory) {
|
|
2028
|
+
const issue = lane_paths.lane_issue_of(directory);
|
|
2029
|
+
return issue === void 0 ? void 0 : lane_paths.lane_branch(issue);
|
|
2030
|
+
}
|
|
2031
|
+
function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_child_of(process.cwd()), own_branch = own_lane_branch(process.cwd())) {
|
|
2032
|
+
if (!is_lane_child || own_branch === void 0) return false;
|
|
2033
|
+
return switch_targets(command).some((target) => target !== own_branch);
|
|
2034
|
+
}
|
|
2035
|
+
var LANE_SWITCH_MAIN_REASON = "\u26D4 lane child on `git switch main`: this session is a dispatched lane child, and switching to the default branch here always fails (`already used by worktree`: the primary checkout holds it). Skip this step and keep implementing on the current branch; `pnpm josh main:merge` brings the default in during the gate. Refused on every occurrence (`backlogrun-lanes.md`, `backlogrun-child.md`).";
|
|
2036
|
+
var ROW14 = {
|
|
2037
|
+
id: "lane-switch-main",
|
|
2038
|
+
is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
|
|
2039
|
+
reason: LANE_SWITCH_MAIN_REASON,
|
|
2040
|
+
decide: () => true,
|
|
2041
|
+
about: {
|
|
2042
|
+
title: "Lane child on `git switch main`",
|
|
2043
|
+
topic: "`.claude/skills/workflow-commands/backlogrun-lanes.md`",
|
|
2044
|
+
fires: "a dispatched lane child switching to the default branch or running `josh main:sync`. Every occurrence",
|
|
2045
|
+
quiet: "not a lane child \u2014 `pnpm josh main:merge` brings the default in at the gate"
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
var lane_switch_main = {
|
|
2049
|
+
LANE_SWITCH_MAIN_REASON,
|
|
2050
|
+
ROW: ROW14,
|
|
2051
|
+
is_switch_away_from_lane,
|
|
2052
|
+
switch_target,
|
|
2053
|
+
switch_targets
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
// scripts/rules/decision-oracle-tokens.ts
|
|
2057
|
+
var oracle_tokens = {
|
|
2058
|
+
REQUIRED: "required",
|
|
2059
|
+
SKIP: "skip",
|
|
2060
|
+
STOP: "stop",
|
|
2061
|
+
WAIT: "wait",
|
|
2062
|
+
RETRY: "retry",
|
|
2063
|
+
OVER: "over",
|
|
2064
|
+
UNKNOWN: "unknown",
|
|
2065
|
+
NONE: "none",
|
|
2066
|
+
NOT_A_LANE: "not-a-lane",
|
|
2067
|
+
BUSY: "busy",
|
|
2068
|
+
HUMAN_REVIEW: "human-review",
|
|
2069
|
+
ISSUE_N_ARG: "<N>",
|
|
2070
|
+
EXCLUDE_ARG: "[--exclude <n>...]"
|
|
2071
|
+
};
|
|
2072
|
+
|
|
2073
|
+
// scripts/rules/decision-oracle-batch.ts
|
|
2074
|
+
var {
|
|
2075
|
+
BUSY,
|
|
2076
|
+
EXCLUDE_ARG,
|
|
2077
|
+
HUMAN_REVIEW,
|
|
2078
|
+
ISSUE_N_ARG,
|
|
2079
|
+
NONE,
|
|
2080
|
+
NOT_A_LANE,
|
|
2081
|
+
OVER,
|
|
2082
|
+
RETRY,
|
|
2083
|
+
STOP,
|
|
2084
|
+
UNKNOWN,
|
|
2085
|
+
WAIT
|
|
2086
|
+
} = oracle_tokens;
|
|
2087
|
+
var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
|
|
2088
|
+
var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
|
|
2089
|
+
var RUN_MERGE_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:merge`";
|
|
2090
|
+
var EPIC_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh epic:next`";
|
|
2091
|
+
var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh backlog:budget`";
|
|
2092
|
+
var RUN_LIVENESS_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:liveness`";
|
|
2093
|
+
var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh auto-ok:next`";
|
|
2094
|
+
var STASH_PUSH_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh stash:push`";
|
|
2095
|
+
var RUN_CUT_CMD = "run:cut";
|
|
2096
|
+
var BATCH_ORACLES = [
|
|
2097
|
+
{
|
|
2098
|
+
name: "run:cut:resume",
|
|
2099
|
+
command: RUN_CUT_CMD,
|
|
2100
|
+
decision: "Whether a lane child is resuming prior work or starting fresh",
|
|
2101
|
+
args: `--resume ${ISSUE_N_ARG}`,
|
|
2102
|
+
vocabulary: ["fresh", "resume", NOT_A_LANE],
|
|
2103
|
+
single_source: PRE_GATE_CUT_MD
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
name: "run:cut:gate",
|
|
2107
|
+
command: RUN_CUT_CMD,
|
|
2108
|
+
decision: "Whether a lane child should take its pre-gate cut now",
|
|
2109
|
+
args: ISSUE_N_ARG,
|
|
2110
|
+
vocabulary: ["cut", NOT_A_LANE],
|
|
2111
|
+
single_source: PRE_GATE_CUT_MD
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
name: "backlog:budget",
|
|
2115
|
+
decision: "Whether a backlogrun may start more work, keep watching, or finish",
|
|
2116
|
+
args: "",
|
|
2117
|
+
vocabulary: ["run", "watch", STOP],
|
|
2118
|
+
single_source: BACKLOG_BUDGET_REFERENCE
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
name: "run:liveness",
|
|
2122
|
+
decision: "Whether a delegated unit is still working or has stopped without reporting",
|
|
2123
|
+
args: `${ISSUE_N_ARG} --output <path>`,
|
|
2124
|
+
vocabulary: ["alive", "paused", "stopped", "settled", "undetermined"],
|
|
2125
|
+
single_source: RUN_LIVENESS_REFERENCE
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: "stash:push",
|
|
2129
|
+
decision: "Whether the working tree had anything outside the observation ledger to set aside",
|
|
2130
|
+
args: "<message>",
|
|
2131
|
+
vocabulary: ["stashed", "clean"],
|
|
2132
|
+
single_source: STASH_PUSH_REFERENCE
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
name: "stash:pop",
|
|
2136
|
+
decision: "Whether a named stash entry exists and can be applied",
|
|
2137
|
+
args: "<message>",
|
|
2138
|
+
vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
|
|
2139
|
+
single_source: ".claude/skills/workflow-commands/prerequisite.md"
|
|
2140
|
+
},
|
|
2141
|
+
{
|
|
2142
|
+
name: "backlog:next",
|
|
2143
|
+
decision: "Which issue the backlog offers next, or whether to wait or stop",
|
|
2144
|
+
args: EXCLUDE_ARG,
|
|
2145
|
+
vocabulary: [WAIT, STOP, RETRY, "error", NONE],
|
|
2146
|
+
single_source: BACKLOGRUN_MD
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
name: "auto-ok:next",
|
|
2150
|
+
decision: "The next opted-in issue outside any epic, or none",
|
|
2151
|
+
args: EXCLUDE_ARG,
|
|
2152
|
+
vocabulary: [NONE],
|
|
2153
|
+
single_source: AUTO_OK_NEXT_REFERENCE
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
name: "epic:next",
|
|
2157
|
+
decision: "The next runnable child of an epic, or the epic's status",
|
|
2158
|
+
args: "<epic>",
|
|
2159
|
+
vocabulary: [WAIT, STOP, "complete"],
|
|
2160
|
+
single_source: EPIC_NEXT_REFERENCE
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
name: "run:merge",
|
|
2164
|
+
decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
|
|
2165
|
+
args: ISSUE_N_ARG,
|
|
2166
|
+
vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
|
|
2167
|
+
single_source: RUN_MERGE_REFERENCE
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
name: "run:step",
|
|
2171
|
+
decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
|
|
2172
|
+
args: `[${ISSUE_N_ARG}]`,
|
|
2173
|
+
// The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
|
|
2174
|
+
// one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
|
|
2175
|
+
// number is not one for `backlog:next`.
|
|
2176
|
+
vocabulary: [
|
|
2177
|
+
"implement",
|
|
2178
|
+
HUMAN_REVIEW,
|
|
2179
|
+
"update-deps",
|
|
2180
|
+
"already-done",
|
|
2181
|
+
"keep-work",
|
|
2182
|
+
WAIT,
|
|
2183
|
+
STOP,
|
|
2184
|
+
UNKNOWN
|
|
2185
|
+
],
|
|
2186
|
+
single_source: BACKLOGRUN_MD
|
|
2187
|
+
}
|
|
2188
|
+
];
|
|
2189
|
+
|
|
2190
|
+
// scripts/rules/decision-oracle-stage.ts
|
|
2191
|
+
var RUN_ENTRY_ORACLE = {
|
|
2192
|
+
name: "run:entry",
|
|
2193
|
+
decision: "Where a run on an issue starts, and whether the typed command has already been reached",
|
|
2194
|
+
args: "<N> [--to kickoff|halfrun|prrun|fullrun]",
|
|
2195
|
+
vocabulary: run_stage.START_TOKENS,
|
|
2196
|
+
single_source: "docs/how-to/run-issues.md"
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
// scripts/rules/decision-oracle.ts
|
|
2200
|
+
var { BUSY: BUSY2, ISSUE_N_ARG: ISSUE_N_ARG2, NONE: NONE2, OVER: OVER2, REQUIRED, SKIP, UNKNOWN: UNKNOWN2 } = oracle_tokens;
|
|
2201
|
+
var CHAIN_RULE_MD = ".claude/skills/workflow-commands/chain-rule.md";
|
|
2202
|
+
var BACKLOGRUN_PROGRESS_MD = ".claude/skills/workflow-commands/backlogrun-progress.md";
|
|
2203
|
+
var SKILL_2E = ".claude/skills/workflow-commands/issue-scout.md";
|
|
2204
|
+
var SPLIT_ASSESSMENT_QUESTION = ".claude/skills/workflow-commands/split-assessment.md \u2192 The question";
|
|
2205
|
+
var LIVE = "live";
|
|
2206
|
+
var STOPPED = "stopped";
|
|
2207
|
+
var RECONCILED = "reconciled";
|
|
2208
|
+
var NOTHING_TO_RECONCILE = "nothing to reconcile";
|
|
2209
|
+
var DECISION_ORACLES = [
|
|
2210
|
+
{
|
|
2211
|
+
name: "delegate",
|
|
2212
|
+
decision: "Whether a run step may be delegated to a cheaper execution tier",
|
|
2213
|
+
args: "<step>",
|
|
2214
|
+
vocabulary: ["delegate", "keep"],
|
|
2215
|
+
single_source: ".claude/skills/workflow-commands/delegation.md"
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
name: "review:level",
|
|
2219
|
+
decision: "The code-review level for the changed paths",
|
|
2220
|
+
command: "review:brief",
|
|
2221
|
+
args: "--level-only",
|
|
2222
|
+
vocabulary: ["low", "medium", "high", "max"],
|
|
2223
|
+
single_source: "prompts/review.md"
|
|
2224
|
+
},
|
|
2225
|
+
{
|
|
2226
|
+
name: "review:round2",
|
|
2227
|
+
decision: "Whether the second /code-review round is due",
|
|
2228
|
+
args: "[--round-1-closed]",
|
|
2229
|
+
vocabulary: [REQUIRED, SKIP],
|
|
2230
|
+
single_source: CHAIN_RULE_MD
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
name: "disposition",
|
|
2234
|
+
decision: "Whether a review finding reaches a runtime path, so it may be filed",
|
|
2235
|
+
args: "<path...>",
|
|
2236
|
+
vocabulary: ["runtime", "non-runtime"],
|
|
2237
|
+
single_source: "prompts/review.md \u2192 Three-way disposition after the cap"
|
|
2238
|
+
},
|
|
2239
|
+
{
|
|
2240
|
+
name: "review:attest",
|
|
2241
|
+
decision: "Whether a /code-review attested the briefed checkout",
|
|
2242
|
+
args: "--check",
|
|
2243
|
+
vocabulary: ["ok", "missing", "mismatch", "not-required"],
|
|
2244
|
+
single_source: CHAIN_RULE_MD
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
name: "latest:scope",
|
|
2248
|
+
decision: "Whether this run has to update dependencies first",
|
|
2249
|
+
args: "",
|
|
2250
|
+
vocabulary: [REQUIRED, SKIP],
|
|
2251
|
+
single_source: ".claude/skills/workflow-commands/latest-gate.md"
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
name: "run:hold",
|
|
2255
|
+
decision: "Whether this working tree is free for a run to claim",
|
|
2256
|
+
args: `[${ISSUE_N_ARG2}]`,
|
|
2257
|
+
vocabulary: ["hold", BUSY2, UNKNOWN2],
|
|
2258
|
+
single_source: ".claude/skills/workflow-commands/working-tree-hold.md"
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
name: "cost:cut",
|
|
2262
|
+
command: "cost",
|
|
2263
|
+
decision: "Whether the session cost exceeds the hand-off threshold",
|
|
2264
|
+
args: "--cut",
|
|
2265
|
+
vocabulary: [OVER2, "under"],
|
|
2266
|
+
single_source: BACKLOGRUN_PROGRESS_MD
|
|
2267
|
+
},
|
|
2268
|
+
{
|
|
2269
|
+
name: "release:scope",
|
|
2270
|
+
decision: "Whether a release is owed after this merge",
|
|
2271
|
+
args: "",
|
|
2272
|
+
vocabulary: [REQUIRED, SKIP, UNKNOWN2],
|
|
2273
|
+
single_source: ".claude/skills/workflow-commands/followup.md"
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
name: "epic:bundle",
|
|
2277
|
+
decision: "Which epic a newly filed issue belongs to",
|
|
2278
|
+
args: ISSUE_N_ARG2,
|
|
2279
|
+
vocabulary: ["add_to_epic", "create_epic", "ask", "nothing_to_bundle"],
|
|
2280
|
+
single_source: SKILL_2E
|
|
2281
|
+
},
|
|
2282
|
+
{
|
|
2283
|
+
name: "issue:scout",
|
|
2284
|
+
decision: "Whether a matching open issue already exists before filing",
|
|
2285
|
+
args: "<title>",
|
|
2286
|
+
vocabulary: ["Duplicates:", "Epic:"],
|
|
2287
|
+
single_source: SKILL_2E
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
name: "issue:fold",
|
|
2291
|
+
decision: "Whether findings filed from one session fold into one issue or stay separate",
|
|
2292
|
+
args: "<title...>",
|
|
2293
|
+
vocabulary: ["fold", "separate", "no-fold-needed", "undetermined"],
|
|
2294
|
+
single_source: SPLIT_ASSESSMENT_QUESTION
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
name: "pkg:scout",
|
|
2298
|
+
decision: "Whether the top package candidate is clearly best (Tier A) or a near-tie (Tier B)",
|
|
2299
|
+
args: "<keywords>",
|
|
2300
|
+
vocabulary: ["clear", "close"],
|
|
2301
|
+
single_source: "CLAUDE.md \u2192 Package-First Development"
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
name: "issue:lint",
|
|
2305
|
+
decision: "Whether a behavior-change issue declares a deliverable firing point and a re-runnable baseline",
|
|
2306
|
+
args: "<path>",
|
|
2307
|
+
vocabulary: ["ok", "\u2716 missing heading", "\u2716 firing point", "\u2716 baseline"],
|
|
2308
|
+
single_source: "prompts/collaboration-workflow/issue-template.md"
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
name: "cases",
|
|
2312
|
+
decision: "Each boundary a change crosses (network, process, fs, time, shared state, environment) and the abnormal cases it owes",
|
|
2313
|
+
args: "<path...>",
|
|
2314
|
+
vocabulary: ["network", "process", "fs", "time", "state", "env", NONE2],
|
|
2315
|
+
single_source: "prompts/collaboration-workflow/report-format.md \u2192 \u5909\u66F4\u3068\u30C6\u30B9\u30C8"
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
name: "issue:state",
|
|
2319
|
+
decision: "An issue's state, labels, and human-review flag",
|
|
2320
|
+
args: ISSUE_N_ARG2,
|
|
2321
|
+
vocabulary: ["human_review: yes", "human_review: no"],
|
|
2322
|
+
single_source: ".claude/skills/workflow-commands/needs-human-review.md"
|
|
2323
|
+
},
|
|
2324
|
+
...BATCH_ORACLES,
|
|
2325
|
+
{
|
|
2326
|
+
name: "refactor:scan",
|
|
2327
|
+
decision: "Whether the refactoring scope still holds high- or medium-priority candidates",
|
|
2328
|
+
args: "",
|
|
2329
|
+
vocabulary: ["clear", "candidates", "error"],
|
|
2330
|
+
single_source: "prompts/refactoring.md"
|
|
2331
|
+
},
|
|
2332
|
+
{
|
|
2333
|
+
name: "split:assess",
|
|
2334
|
+
decision: "Whether a change size clears the split guide (the split assessment size question)",
|
|
2335
|
+
args: "[--json]",
|
|
2336
|
+
vocabulary: ["split", "single"],
|
|
2337
|
+
single_source: SPLIT_ASSESSMENT_QUESTION
|
|
2338
|
+
},
|
|
2339
|
+
{
|
|
2340
|
+
name: "sonar:hotspots",
|
|
2341
|
+
decision: "The Step B disposition for each SonarCloud hotspot on a pull request",
|
|
2342
|
+
args: "<PR>",
|
|
2343
|
+
vocabulary: ["excluded", "local", "fix", "defer", "unreadable"],
|
|
2344
|
+
single_source: "prompts/sonar-hotspot-handling.md"
|
|
2345
|
+
},
|
|
2346
|
+
{
|
|
2347
|
+
name: "clone:scan",
|
|
2348
|
+
decision: "Whether cross-file or cross-repository code duplication exists",
|
|
2349
|
+
args: "",
|
|
2350
|
+
vocabulary: ["clean", "clones:"],
|
|
2351
|
+
single_source: "prompts/collaboration-workflow/principles.md \u2192 no-clones"
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
name: "epic:reconcile",
|
|
2355
|
+
command: "epic",
|
|
2356
|
+
decision: "Whether an epic's declaration matches its recorded relations, and the repair when it does not",
|
|
2357
|
+
args: "--reconcile <E>",
|
|
2358
|
+
vocabulary: [RECONCILED, NOTHING_TO_RECONCILE],
|
|
2359
|
+
single_source: "docs/josh-commands-backlog.md \u2192 `josh epic --reconcile`"
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
name: "lane:list",
|
|
2363
|
+
decision: "Whether each in-progress issue holds a live lane, and the two-directional difference",
|
|
2364
|
+
args: "",
|
|
2365
|
+
vocabulary: [LIVE, STOPPED, UNKNOWN2],
|
|
2366
|
+
single_source: "docs/josh-commands-run.md \u2192 The `in-progress` / lane difference"
|
|
2367
|
+
},
|
|
2368
|
+
RUN_ENTRY_ORACLE
|
|
2369
|
+
];
|
|
2370
|
+
function get_command(oracle) {
|
|
2371
|
+
return oracle.command ?? oracle.name;
|
|
2372
|
+
}
|
|
2373
|
+
function find_oracle(name) {
|
|
2374
|
+
return DECISION_ORACLES.find((oracle) => oracle.name === name.trim());
|
|
2375
|
+
}
|
|
2376
|
+
var decision_oracle = {
|
|
2377
|
+
DECISION_ORACLES,
|
|
2378
|
+
find_oracle,
|
|
2379
|
+
get_command
|
|
2380
|
+
};
|
|
2381
|
+
|
|
2382
|
+
// scripts/rules/oracle-firing.ts
|
|
2383
|
+
var PACKAGE_ADD = /^(?:pnpm|yarn|bun)\s+add\s+\S|^npm\s+(?:install|i)\s+[^-\s]/u;
|
|
2384
|
+
function is_package_add(command) {
|
|
2385
|
+
return shell_segments.segments_of(command).some((segment) => PACKAGE_ADD.test(segment));
|
|
2386
|
+
}
|
|
2387
|
+
var FIRING = /* @__PURE__ */ new Map([
|
|
2388
|
+
[
|
|
2389
|
+
"pkg:scout",
|
|
2390
|
+
{
|
|
2391
|
+
describes: "a package-add command (`pnpm add \u2026`, `npm install <pkg>`)",
|
|
2392
|
+
governs: is_package_add
|
|
2393
|
+
}
|
|
2394
|
+
]
|
|
2395
|
+
]);
|
|
2396
|
+
var PHASE = "the act it governs is a phase of a run, not a single shell call a trigger can anchor on";
|
|
2397
|
+
var READ = "it answers a status question and governs no action of its own to gate before";
|
|
2398
|
+
var SELF = "the command is itself the governed act; there is no earlier call to gate before it";
|
|
2399
|
+
var LATEST_SCOPE_REASON = "the run it governs (`pnpm josh latest`) is already gated at its point of use by the latest-gate; a firing here would double-gate it";
|
|
2400
|
+
var FILING_STEP_REASON = "`pnpm josh issue:file` runs it as one of its filing steps, and a direct filing is refused by the `direct-filing` delivered rule; there is no separate call left to gate";
|
|
2401
|
+
var RELEASE_SCOPE_REASON = "it reads the release owed *after* `pnpm josh followup` has merged (`git_followup_pending`), so it trails the merge rather than gating it; a firing on `followup` would gate a check that must run once the merge is done, reading a pre-merge state that has not yet arrived";
|
|
2402
|
+
var NOT_NAMED = /* @__PURE__ */ new Map([
|
|
2403
|
+
["release:scope", RELEASE_SCOPE_REASON],
|
|
2404
|
+
["delegate", PHASE],
|
|
2405
|
+
["review:level", READ],
|
|
2406
|
+
["review:round2", READ],
|
|
2407
|
+
["disposition", READ],
|
|
2408
|
+
["review:attest", READ],
|
|
2409
|
+
["latest:scope", LATEST_SCOPE_REASON],
|
|
2410
|
+
["run:hold", SELF],
|
|
2411
|
+
["cost:cut", READ],
|
|
2412
|
+
["epic:bundle", FILING_STEP_REASON],
|
|
2413
|
+
["issue:scout", FILING_STEP_REASON],
|
|
2414
|
+
["issue:lint", FILING_STEP_REASON],
|
|
2415
|
+
["issue:fold", "`issue:file` asks the fold question itself on the second filing it governs"],
|
|
2416
|
+
["cases", READ],
|
|
2417
|
+
["issue:state", READ],
|
|
2418
|
+
["run:cut:resume", SELF],
|
|
2419
|
+
["run:cut:gate", SELF],
|
|
2420
|
+
["backlog:budget", READ],
|
|
2421
|
+
["run:liveness", READ],
|
|
2422
|
+
["stash:push", SELF],
|
|
2423
|
+
["stash:pop", SELF],
|
|
2424
|
+
["backlog:next", READ],
|
|
2425
|
+
["auto-ok:next", READ],
|
|
2426
|
+
["epic:next", READ],
|
|
2427
|
+
["run:merge", PHASE],
|
|
2428
|
+
["run:step", SELF],
|
|
2429
|
+
["run:entry", SELF],
|
|
2430
|
+
["refactor:scan", PHASE],
|
|
2431
|
+
["split:assess", PHASE],
|
|
2432
|
+
["sonar:hotspots", READ],
|
|
2433
|
+
["clone:scan", PHASE],
|
|
2434
|
+
["epic:reconcile", SELF],
|
|
2435
|
+
["lane:list", READ]
|
|
2436
|
+
]);
|
|
2437
|
+
function declaration_for(name) {
|
|
2438
|
+
const firing_point = FIRING.get(name);
|
|
2439
|
+
if (firing_point !== void 0) return { firing_point };
|
|
2440
|
+
const not_firing_reason = NOT_NAMED.get(name);
|
|
2441
|
+
return not_firing_reason === void 0 ? {} : { not_firing_reason };
|
|
2442
|
+
}
|
|
2443
|
+
function firing_oracles() {
|
|
2444
|
+
return decision_oracle.DECISION_ORACLES.flatMap((oracle) => {
|
|
2445
|
+
const firing_point = FIRING.get(oracle.name);
|
|
2446
|
+
return firing_point === void 0 ? [] : { oracle, firing_point };
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
var oracle_firing = {
|
|
2450
|
+
FIRING,
|
|
2451
|
+
NOT_NAMED,
|
|
2452
|
+
declaration_for,
|
|
2453
|
+
firing_oracles,
|
|
2454
|
+
is_package_add
|
|
2455
|
+
};
|
|
2456
|
+
|
|
2457
|
+
// scripts/rules/oracle-consulted.ts
|
|
2458
|
+
function command_names(oracle) {
|
|
2459
|
+
return /* @__PURE__ */ new Set([decision_oracle.get_command(oracle)]);
|
|
2460
|
+
}
|
|
2461
|
+
function runs_the_oracle(command, names) {
|
|
2462
|
+
return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names));
|
|
2463
|
+
}
|
|
2464
|
+
function command_line(oracle) {
|
|
2465
|
+
const command = decision_oracle.get_command(oracle);
|
|
2466
|
+
return oracle.args ? `pnpm josh ${command} ${oracle.args}` : `pnpm josh ${command}`;
|
|
2467
|
+
}
|
|
2468
|
+
function reason_for2(oracle, firing_point) {
|
|
2469
|
+
return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}. Run \`${command_line(oracle)}\` first \u2014 it decides ${oracle.decision.toLowerCase()} and answers ${oracle.vocabulary.join(" | ")} (${oracle.single_source}). Every such call is refused until that command is on the transcript.`;
|
|
2470
|
+
}
|
|
2471
|
+
var ABOUT = {
|
|
2472
|
+
title: "An act its oracle was not asked about",
|
|
2473
|
+
topic: "`decision-oracle.ts`",
|
|
2474
|
+
fires: "a `Bash` governed by an oracle that declared a firing point (`pkg:scout` governs `pnpm add`), issued without that `pnpm josh <command>`. Refused until it ran",
|
|
2475
|
+
quiet: "no governed act, or the oracle already ran"
|
|
2476
|
+
};
|
|
2477
|
+
function row_for(oracle, firing_point) {
|
|
2478
|
+
const names = command_names(oracle);
|
|
2479
|
+
return {
|
|
2480
|
+
id: `oracle-consulted:${oracle.name}`,
|
|
2481
|
+
is_trigger: bash_triggers.on_bash_command(firing_point.governs),
|
|
2482
|
+
reason: reason_for2(oracle, firing_point),
|
|
2483
|
+
already_satisfied: (tail, _call) => tail_commands.prior_bash_commands(tail).some((command) => runs_the_oracle(command, names)),
|
|
2484
|
+
keeps: bash_triggers.on_bash_command((command) => runs_the_oracle(command, names)),
|
|
2485
|
+
about: ABOUT
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
var ROWS = oracle_firing.firing_oracles().map(({ oracle, firing_point }) => row_for(oracle, firing_point));
|
|
2489
|
+
var oracle_consulted = { ROWS, reason_for: reason_for2 };
|
|
2490
|
+
|
|
2491
|
+
// scripts/rules/destructive-command.ts
|
|
2492
|
+
var RM_COMMAND = "rm";
|
|
2493
|
+
var RECURSIVE_LONG = "--recursive";
|
|
2494
|
+
var RECURSIVE_LETTERS = "rR";
|
|
2495
|
+
var FORCE_LONG = "--force";
|
|
2496
|
+
var FORCE_LETTER = "f";
|
|
2497
|
+
function has_flag(args, long, letters) {
|
|
2498
|
+
return args.some((argument) => argument === long || git_argv.short_cluster_has(argument, letters));
|
|
2499
|
+
}
|
|
2500
|
+
function is_recursive_forced_rm(segment) {
|
|
2501
|
+
const args = git_argv.arguments_of(segment, RM_COMMAND);
|
|
2502
|
+
if (args === void 0) return false;
|
|
2503
|
+
return has_flag(args, RECURSIVE_LONG, RECURSIVE_LETTERS) && has_flag(args, FORCE_LONG, FORCE_LETTER);
|
|
2504
|
+
}
|
|
2505
|
+
var GH_COMMAND2 = "gh";
|
|
2506
|
+
var REPO_GROUP2 = "repo";
|
|
2507
|
+
var REPO_REMOVALS = /* @__PURE__ */ new Set(["delete", "archive"]);
|
|
2508
|
+
var REPO_EDIT = "edit";
|
|
2509
|
+
var VISIBILITY_FLAG = "--visibility";
|
|
2510
|
+
var PR_GROUP2 = "pr";
|
|
2511
|
+
var PR_CLOSE = "close";
|
|
2512
|
+
function is_destructive_repo_action(action, args) {
|
|
2513
|
+
if (REPO_REMOVALS.has(action)) return true;
|
|
2514
|
+
return action === REPO_EDIT && args.some((argument) => argument.startsWith(VISIBILITY_FLAG));
|
|
2515
|
+
}
|
|
2516
|
+
function is_destructive_gh_command(args) {
|
|
2517
|
+
const [group, action = ""] = args;
|
|
2518
|
+
if (group === PR_GROUP2) return action === PR_CLOSE;
|
|
2519
|
+
return group === REPO_GROUP2 && is_destructive_repo_action(action, args);
|
|
2520
|
+
}
|
|
2521
|
+
var DELETE_METHOD = "DELETE";
|
|
2522
|
+
var REPOSITORY_PATH = "repos/";
|
|
2523
|
+
var ISSUE_LABEL_PATH = /^['"]?\/?repos\/[^/\s'"]+\/[^/\s'"]+\/issues\/\d+\/labels\/[^/\s'"]+['"]?$/u;
|
|
2524
|
+
function is_destructive_api_call(args) {
|
|
2525
|
+
const call = [GH_COMMAND2, ...args].join(" ");
|
|
2526
|
+
if (!gh_api.is_gh_api(call) || gh_api.method_of(call) !== DELETE_METHOD) return false;
|
|
2527
|
+
return args.some(
|
|
2528
|
+
(argument) => argument.includes(REPOSITORY_PATH) && !ISSUE_LABEL_PATH.test(argument)
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
function is_destructive_gh(segment) {
|
|
2532
|
+
const args = git_argv.arguments_of(segment, GH_COMMAND2);
|
|
2533
|
+
if (args === void 0) return false;
|
|
2534
|
+
return is_destructive_gh_command(args) || is_destructive_api_call(args);
|
|
2535
|
+
}
|
|
2536
|
+
function is_destructive_segment2(segment) {
|
|
2537
|
+
return is_recursive_forced_rm(segment) || is_destructive_gh(segment);
|
|
2538
|
+
}
|
|
2539
|
+
function is_destructive_command(command) {
|
|
2540
|
+
return shell_segments.commands_of(command).some((segment) => is_destructive_segment2(segment));
|
|
2541
|
+
}
|
|
2542
|
+
var DESTRUCTIVE_COMMAND_REASON = "\u26D4 destructive command: a recursive forced `rm`, a `gh repo delete` / `archive` / visibility edit, `gh pr close` or a `gh api -X DELETE` cannot be undone, which is Tier C. Remove one path at a time without `-f`, or ask the user to run it. It fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md`).";
|
|
2543
|
+
var ROW15 = {
|
|
2544
|
+
id: "destructive-command",
|
|
2545
|
+
is_trigger: bash_triggers.on_bash_command(is_destructive_command),
|
|
2546
|
+
reason: DESTRUCTIVE_COMMAND_REASON,
|
|
2547
|
+
decide: () => true,
|
|
2548
|
+
// The opening the reason carried before it was cut to the action and the next command.
|
|
2549
|
+
former_reasons: ["\u26D4 destructive command: a recursive forced `rm` (`rm -rf`"],
|
|
2550
|
+
about: {
|
|
2551
|
+
title: "Destructive command",
|
|
2552
|
+
topic: "`operating-rules.md`",
|
|
2553
|
+
fires: "`rm -rf` (any spelling), `gh repo delete` / `archive`, `gh pr close`, `gh api -X DELETE`. Every occurrence",
|
|
2554
|
+
quiet: "`gh issue close` or removing an Issue label \u2014 the workflow's own steps"
|
|
2555
|
+
}
|
|
2556
|
+
};
|
|
2557
|
+
var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW15, is_destructive_command };
|
|
2558
|
+
|
|
2559
|
+
// scripts/rules/worktree-guard.ts
|
|
2560
|
+
var CHECKOUT_SUBCOMMAND = "checkout";
|
|
2561
|
+
var RESTORE_SUBCOMMAND = "restore";
|
|
2562
|
+
var STASH_SUBCOMMAND = "stash";
|
|
2563
|
+
var PATHSPEC_SEPARATOR = "--";
|
|
2564
|
+
function is_worktree_checkout(args) {
|
|
2565
|
+
return args.includes(PATHSPEC_SEPARATOR);
|
|
2566
|
+
}
|
|
2567
|
+
var STAGED_LONG = "--staged";
|
|
2568
|
+
var STAGED_LETTER = "S";
|
|
2569
|
+
var WORKTREE_LONG = "--worktree";
|
|
2570
|
+
var WORKTREE_LETTER = "W";
|
|
2571
|
+
function touches_worktree(args) {
|
|
2572
|
+
const is_staged = args.some(
|
|
2573
|
+
(argument) => argument === STAGED_LONG || git_argv.short_cluster_has(argument, STAGED_LETTER)
|
|
2574
|
+
);
|
|
2575
|
+
const is_worktree = args.some(
|
|
2576
|
+
(argument) => argument === WORKTREE_LONG || git_argv.short_cluster_has(argument, WORKTREE_LETTER)
|
|
2577
|
+
);
|
|
2578
|
+
return !is_staged || is_worktree;
|
|
2579
|
+
}
|
|
2580
|
+
var PUSH_STASH = "push";
|
|
2581
|
+
var MESSAGE_LONG = "--message";
|
|
2582
|
+
var MESSAGE_SHORT_LETTER = "m";
|
|
2583
|
+
var READ_ONLY_STASH = /* @__PURE__ */ new Set(["list", "show"]);
|
|
2584
|
+
function has_message(args) {
|
|
2585
|
+
return args.some(
|
|
2586
|
+
(argument) => argument === MESSAGE_LONG || argument.startsWith(`${MESSAGE_LONG}=`) || git_argv.short_cluster_has(argument, MESSAGE_SHORT_LETTER)
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
function is_unauthorized_stash(args) {
|
|
2590
|
+
const [stash_subcommand, ...rest] = args;
|
|
2591
|
+
if (stash_subcommand === void 0) return true;
|
|
2592
|
+
if (READ_ONLY_STASH.has(stash_subcommand)) return false;
|
|
2593
|
+
if (stash_subcommand === PUSH_STASH) return !has_message(rest);
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
var CLEAN_SUBCOMMAND = "clean";
|
|
2597
|
+
var FORCE_LONG2 = "--force";
|
|
2598
|
+
var FORCE_LETTER2 = "f";
|
|
2599
|
+
function is_forced_clean(args) {
|
|
2600
|
+
return args.some(
|
|
2601
|
+
(argument) => argument === FORCE_LONG2 || git_argv.short_cluster_has(argument, FORCE_LETTER2)
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
var JUDGED_SUBCOMMANDS = /* @__PURE__ */ new Map([
|
|
2605
|
+
[CHECKOUT_SUBCOMMAND, is_worktree_checkout],
|
|
2606
|
+
[RESTORE_SUBCOMMAND, touches_worktree],
|
|
2607
|
+
[CLEAN_SUBCOMMAND, is_forced_clean],
|
|
2608
|
+
[STASH_SUBCOMMAND, is_unauthorized_stash]
|
|
2609
|
+
]);
|
|
2610
|
+
function is_unauthorized_segment(segment) {
|
|
2611
|
+
const call = git_argv.parse(segment);
|
|
2612
|
+
if (call === void 0) return false;
|
|
2613
|
+
return JUDGED_SUBCOMMANDS.get(call.subcommand ?? "")?.(call.args) === true;
|
|
2614
|
+
}
|
|
2615
|
+
function is_unauthorized_worktree_change(command) {
|
|
2616
|
+
return shell_segments.commands_of(command).some((segment) => is_unauthorized_segment(segment));
|
|
2617
|
+
}
|
|
2618
|
+
var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout --`, `git restore`, `git clean -f`, `git stash`. A documented flow stashes with `pnpm josh stash:push "<message>"`; a handed-over stash is `pnpm josh stash:pop "<message>"`; record the substitution on the Issue (Tier A) unless the plan says why the owner keeps the stash: then stop and confirm. It fires on every occurrence (`operating-rules.md`).';
|
|
2619
|
+
var ROW16 = {
|
|
2620
|
+
id: "worktree-mutation",
|
|
2621
|
+
is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
|
|
2622
|
+
reason: WORKTREE_MUTATION_REASON,
|
|
2623
|
+
decide: () => true,
|
|
2624
|
+
about: {
|
|
2625
|
+
title: "Unauthorized work-tree mutation",
|
|
2626
|
+
topic: "`operating-rules.md`",
|
|
2627
|
+
fires: "`git checkout -- <path>` / `git restore <path>` / forced `git clean -f` / an unauthorized `git stash` (bare, push without a message, positional pop). Every occurrence",
|
|
2628
|
+
quiet: "`git stash push -m`, `git stash list`, `pnpm josh git`, `pnpm josh stash:push`, `pnpm josh stash:pop`"
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
var worktree_guard = {
|
|
2632
|
+
ROW: ROW16,
|
|
2633
|
+
WORKTREE_MUTATION_REASON,
|
|
2634
|
+
is_unauthorized_worktree_change,
|
|
2635
|
+
touches_worktree
|
|
2636
|
+
};
|
|
2637
|
+
|
|
2638
|
+
// scripts/rules/index-guard.ts
|
|
2639
|
+
var INDEX_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
2640
|
+
"add",
|
|
2641
|
+
"commit",
|
|
2642
|
+
"mv",
|
|
2643
|
+
"reset",
|
|
2644
|
+
"rm",
|
|
2645
|
+
"stage"
|
|
2646
|
+
]);
|
|
2647
|
+
var RESTORE_SUBCOMMAND2 = "restore";
|
|
2648
|
+
function is_index_restore(args) {
|
|
2649
|
+
return !worktree_guard.touches_worktree(args);
|
|
2650
|
+
}
|
|
2651
|
+
function is_index_segment(segment) {
|
|
2652
|
+
const call = git_argv.parse(segment);
|
|
2653
|
+
if (call === void 0) return false;
|
|
2654
|
+
if (INDEX_SUBCOMMANDS.has(call.subcommand ?? "")) return true;
|
|
2655
|
+
return call.subcommand === RESTORE_SUBCOMMAND2 && is_index_restore(call.args);
|
|
2656
|
+
}
|
|
2657
|
+
function is_index_mutation(command) {
|
|
2658
|
+
return shell_segments.commands_of(command).some((segment) => is_index_segment(segment));
|
|
2659
|
+
}
|
|
2660
|
+
var INDEX_MUTATION_REASON = "\u26D4 git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore --staged` overwrite the user's staged snapshot. Commit with `pnpm josh git -y`; any other staging is the user's, on explicit instruction. It fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md` \u2192 \"no-self-staging\").";
|
|
2661
|
+
var ROW17 = {
|
|
2662
|
+
id: "index-mutation",
|
|
2663
|
+
is_trigger: bash_triggers.on_bash_command(is_index_mutation),
|
|
2664
|
+
reason: INDEX_MUTATION_REASON,
|
|
2665
|
+
decide: () => true,
|
|
2666
|
+
about: {
|
|
2667
|
+
title: "Index mutation",
|
|
2668
|
+
topic: "`operating-rules.md`",
|
|
2669
|
+
fires: "`git add` / `commit` / `reset` / `rm` / `mv` / `restore --staged` in any spelling. Every occurrence",
|
|
2670
|
+
quiet: "`pnpm josh git` \u2014 the approved commit flow"
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
var index_guard = { INDEX_MUTATION_REASON, ROW: ROW17, is_index_mutation };
|
|
2674
|
+
|
|
2675
|
+
// scripts/rules/pr-merge.ts
|
|
2676
|
+
var GH_COMMAND3 = "gh";
|
|
2677
|
+
var CURL_COMMAND = "curl";
|
|
2678
|
+
var PR_GROUP3 = "pr";
|
|
2679
|
+
var MERGE_ACTION = "merge";
|
|
2680
|
+
var API_SUBCOMMAND3 = "api";
|
|
2681
|
+
var GRAPHQL_ENDPOINT = "graphql";
|
|
2682
|
+
var MERGE_PATH = /pulls\/[^/\s'"]+\/merge(?![\w-])/u;
|
|
2683
|
+
var MERGE_MUTATIONS = /\b(?:mergePullRequest|enablePullRequestAutoMerge)\b/u;
|
|
2684
|
+
var GRAPHQL_URL = /\/graphql\b/u;
|
|
2685
|
+
var FILE_BODY_VALUE = /^(?:-[A-Za-z]|[^=\s]*=)?['"]?@/u;
|
|
2686
|
+
var FILE_BODY_FLAGS = /* @__PURE__ */ new Set(["--input", "-T", "--upload-file"]);
|
|
2687
|
+
var SUBSTITUTED_BODY = /\$\(|`/u;
|
|
2688
|
+
function positional_words2(args) {
|
|
2689
|
+
return direct_pr_create.positional_words(args, direct_pr_create.API_VALUE_TAKING_FLAGS);
|
|
2690
|
+
}
|
|
2691
|
+
function is_pr_merge_cli_call(args) {
|
|
2692
|
+
const [group, action] = positional_words2(args);
|
|
2693
|
+
return group === PR_GROUP3 && action === MERGE_ACTION;
|
|
2694
|
+
}
|
|
2695
|
+
function is_github_api_call(name, args) {
|
|
2696
|
+
if (name === CURL_COMMAND) return true;
|
|
2697
|
+
return name === GH_COMMAND3 && positional_words2(args)[0] === API_SUBCOMMAND3;
|
|
2698
|
+
}
|
|
2699
|
+
function is_graphql_call(name, args) {
|
|
2700
|
+
if (args.some((argument) => GRAPHQL_URL.test(argument))) return true;
|
|
2701
|
+
return name === GH_COMMAND3 && positional_words2(args)[1] === GRAPHQL_ENDPOINT;
|
|
2702
|
+
}
|
|
2703
|
+
function is_unreadable_body(argument) {
|
|
2704
|
+
if (FILE_BODY_FLAGS.has(argument)) return true;
|
|
2705
|
+
return FILE_BODY_VALUE.test(argument) || SUBSTITUTED_BODY.test(argument);
|
|
2706
|
+
}
|
|
2707
|
+
function is_graphql_merge(command, name, args) {
|
|
2708
|
+
if (!is_graphql_call(name, args)) return false;
|
|
2709
|
+
return MERGE_MUTATIONS.test(command) || args.some((argument) => is_unreadable_body(argument));
|
|
2710
|
+
}
|
|
2711
|
+
function is_api_merge(command, segment, call) {
|
|
2712
|
+
if (MERGE_PATH.test(segment) || MERGE_MUTATIONS.test(segment)) return true;
|
|
2713
|
+
return is_graphql_merge(command, call.name, call.args);
|
|
2714
|
+
}
|
|
2715
|
+
function is_pr_merge_segment(command, segment) {
|
|
2716
|
+
const call = git_argv.program_of(segment);
|
|
2717
|
+
if (call === void 0) return false;
|
|
2718
|
+
if (call.name === GH_COMMAND3 && is_pr_merge_cli_call(call.args)) return true;
|
|
2719
|
+
return is_github_api_call(call.name, call.args) && is_api_merge(command, segment, call);
|
|
2720
|
+
}
|
|
2721
|
+
function is_pr_merge(command) {
|
|
2722
|
+
return shell_segments.commands_of(command).some((segment) => is_pr_merge_segment(command, segment));
|
|
2723
|
+
}
|
|
2724
|
+
var PR_MERGE_REASON = "\u26D4 direct PR merge: `gh pr merge`, a `gh api` / `curl` call to `\u2026/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` when the run authorizes it; otherwise leave it to the user (`CLAUDE.md` \u2192 Git Rules). This rule fires on every occurrence.";
|
|
2725
|
+
var ROW18 = {
|
|
2726
|
+
id: "pr-merge",
|
|
2727
|
+
is_trigger: bash_triggers.on_bash_command(is_pr_merge),
|
|
2728
|
+
reason: PR_MERGE_REASON,
|
|
2729
|
+
decide: () => true,
|
|
2730
|
+
about: {
|
|
2731
|
+
title: "Direct PR merge",
|
|
2732
|
+
topic: "`CLAUDE.md` \u2192 Git Rules",
|
|
2733
|
+
fires: "`gh pr merge`, a `gh api` / `curl` call to `\u2026/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file. Every occurrence",
|
|
2734
|
+
quiet: "`pnpm josh followup` and `pnpm josh observations:flush`, which merge in their own process"
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
var pr_merge = { PR_MERGE_REASON, ROW: ROW18, is_pr_merge };
|
|
2738
|
+
|
|
2739
|
+
// scripts/rules/protected-files.ts
|
|
2740
|
+
import path3 from "node:path";
|
|
2741
|
+
var READ_TOOL2 = "Read";
|
|
2742
|
+
var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
|
|
2743
|
+
var SECRET_FILES = /* @__PURE__ */ new Set([".env", ".dev.vars"]);
|
|
2744
|
+
var ENVIRONMENT_VARIANT_PREFIX = ".env.";
|
|
2745
|
+
var TRACKED_VARIANTS = /* @__PURE__ */ new Set([".env.example", ".env.test"]);
|
|
2746
|
+
var SETTINGS_FILE = path3.join(".claude", "settings.json");
|
|
2747
|
+
var WHITESPACE2 = /\s+/u;
|
|
2748
|
+
var TOKEN_QUOTES = /["']/gu;
|
|
2749
|
+
var INPUT_REDIRECT = "<";
|
|
2750
|
+
var SHELL_READERS = /* @__PURE__ */ new Set([
|
|
2751
|
+
".",
|
|
2752
|
+
"awk",
|
|
2753
|
+
"base64",
|
|
2754
|
+
"bat",
|
|
2755
|
+
"cat",
|
|
2756
|
+
"cmp",
|
|
2757
|
+
"column",
|
|
2758
|
+
"comm",
|
|
2759
|
+
"cut",
|
|
2760
|
+
"diff",
|
|
2761
|
+
"fold",
|
|
2762
|
+
"grep",
|
|
2763
|
+
"head",
|
|
2764
|
+
"hexdump",
|
|
2765
|
+
"jq",
|
|
2766
|
+
"less",
|
|
2767
|
+
"more",
|
|
2768
|
+
"nl",
|
|
2769
|
+
"od",
|
|
2770
|
+
"paste",
|
|
2771
|
+
"perl",
|
|
2772
|
+
"python",
|
|
2773
|
+
"python3",
|
|
2774
|
+
"rev",
|
|
2775
|
+
"rg",
|
|
2776
|
+
"ruby",
|
|
2777
|
+
"sed",
|
|
2778
|
+
"sort",
|
|
2779
|
+
"source",
|
|
2780
|
+
"strings",
|
|
2781
|
+
"tac",
|
|
2782
|
+
"tail",
|
|
2783
|
+
"uniq",
|
|
2784
|
+
"xxd",
|
|
2785
|
+
"yq"
|
|
2786
|
+
]);
|
|
2787
|
+
function file_path_of(call) {
|
|
2788
|
+
if (!json_value.is_record(call.input)) return "";
|
|
2789
|
+
const value = call.input["file_path"];
|
|
2790
|
+
return typeof value === "string" ? value : "";
|
|
2791
|
+
}
|
|
2792
|
+
function is_environment_path(token) {
|
|
2793
|
+
const name = path3.basename(token.replaceAll(TOKEN_QUOTES, ""));
|
|
2794
|
+
if (TRACKED_VARIANTS.has(name)) return false;
|
|
2795
|
+
return SECRET_FILES.has(name) || name.startsWith(ENVIRONMENT_VARIANT_PREFIX);
|
|
2796
|
+
}
|
|
2797
|
+
function is_environment_read_segment(segment) {
|
|
2798
|
+
const words = segment.replaceAll(INPUT_REDIRECT, " < ").split(WHITESPACE2).filter((word) => word !== "");
|
|
2799
|
+
const is_redirected = words.some(
|
|
2800
|
+
(word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? "")
|
|
2801
|
+
);
|
|
2802
|
+
const call = git_argv.program_of(segment);
|
|
2803
|
+
if (is_redirected || call === void 0) return is_redirected;
|
|
2804
|
+
return SHELL_READERS.has(call.name) && call.args.some((word) => is_environment_path(word));
|
|
2805
|
+
}
|
|
2806
|
+
function is_environment_shell_read(command) {
|
|
2807
|
+
return shell_segments.commands_of(command).some((segment) => is_environment_read_segment(segment));
|
|
2808
|
+
}
|
|
2809
|
+
function is_environment_read(call) {
|
|
2810
|
+
if (call.name === READ_TOOL2) return is_environment_path(file_path_of(call));
|
|
2811
|
+
return bash_triggers.on_bash_command(is_environment_shell_read)(call);
|
|
2812
|
+
}
|
|
2813
|
+
function is_consumer_settings_edit(call, is_kit_consumer) {
|
|
2814
|
+
if (!EDIT_TOOLS2.has(call.name)) return false;
|
|
2815
|
+
const file_path = path3.resolve(file_path_of(call));
|
|
2816
|
+
if (!file_path.endsWith(`${path3.sep}${SETTINGS_FILE}`)) return false;
|
|
2817
|
+
return is_kit_consumer(path3.dirname(path3.dirname(file_path)));
|
|
2818
|
+
}
|
|
2819
|
+
function is_protected_file_call(call, is_kit_consumer = doctor_consumer.is_kit_consumer) {
|
|
2820
|
+
return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer);
|
|
2821
|
+
}
|
|
2822
|
+
var PROTECTED_FILE_REASON = "\u26D4 protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets, and a consumer `.claude/settings.json` is owned by `josh sync` (a change belongs upstream in kit). Leave these files alone, or ask the user. It fires on every occurrence.";
|
|
2823
|
+
var ROW19 = {
|
|
2824
|
+
id: "protected-file",
|
|
2825
|
+
is_trigger: is_protected_file_call,
|
|
2826
|
+
reason: PROTECTED_FILE_REASON,
|
|
2827
|
+
decide: () => true,
|
|
2828
|
+
// The opening the reason carried before it was cut to the action and the next command.
|
|
2829
|
+
former_reasons: ["\u26D4 protected file: `.env` holds the Telegram bot token"],
|
|
2830
|
+
about: {
|
|
2831
|
+
title: "Protected file",
|
|
2832
|
+
topic: "`operating-rules.md`",
|
|
2833
|
+
fires: "a `Read` of `.env`, or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence",
|
|
2834
|
+
quiet: "kit itself, or the user's `~/.claude/settings.json`"
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
var protected_files = { ROW: ROW19, is_protected_file_call };
|
|
2838
|
+
|
|
2839
|
+
// scripts/rules/permission-guards.ts
|
|
2840
|
+
var ROWS2 = [
|
|
2841
|
+
index_guard.ROW,
|
|
2842
|
+
destructive_command.ROW,
|
|
2843
|
+
protected_files.ROW,
|
|
2844
|
+
direct_pr_create.ROW,
|
|
2845
|
+
pr_merge.ROW
|
|
2846
|
+
];
|
|
2847
|
+
var permission_guards = { ROWS: ROWS2 };
|
|
2848
|
+
|
|
2849
|
+
// scripts/rules/piped-verification.ts
|
|
2850
|
+
var VERIFICATION_COMMANDS = /* @__PURE__ */ new Set([
|
|
2851
|
+
"check",
|
|
2852
|
+
"cspell",
|
|
2853
|
+
"cspell:dot",
|
|
2854
|
+
"eval",
|
|
2855
|
+
"gate",
|
|
2856
|
+
"lint",
|
|
2857
|
+
"lint:eslint",
|
|
2858
|
+
"lint:prettier",
|
|
2859
|
+
"lint:related",
|
|
2860
|
+
"overrides",
|
|
2861
|
+
"ranges",
|
|
2862
|
+
"test",
|
|
2863
|
+
"test:e2e",
|
|
2864
|
+
"test:related",
|
|
2865
|
+
"test:unit"
|
|
2866
|
+
]);
|
|
2867
|
+
function is_verification_command(segment) {
|
|
2868
|
+
return shell_segments.is_josh_command(segment, VERIFICATION_COMMANDS);
|
|
2869
|
+
}
|
|
2870
|
+
function is_masked_verification(command) {
|
|
2871
|
+
return time_shell.discarded_commands(command).some((segment) => is_verification_command(segment));
|
|
2872
|
+
}
|
|
2873
|
+
var PIPED_VERIFICATION_REASON = "\u26D4 piped verification: a pipeline exits with its last command's status, so `pnpm josh gate | tail` reports success on a gate that failed. Reissue this call with no pipe (josh prints its verdict last), or redirect it to a file and read ranges from that file, or prefix `set -o pipefail`; read the verdict line, never the exit code alone (`prompts/collaboration-workflow/output-bounds.md`).";
|
|
2874
|
+
function pipelines_of(command) {
|
|
2875
|
+
return shell_segments.segments_of(time_shell.unquoted(command)).map((segment) => segment.split("|").map((piece) => piece.trim()));
|
|
2876
|
+
}
|
|
2877
|
+
function command_pieces(command) {
|
|
2878
|
+
return pipelines_of(command).flat();
|
|
2879
|
+
}
|
|
2880
|
+
function runs_verification(command) {
|
|
2881
|
+
return command_pieces(command).some((piece) => is_verification_command(piece));
|
|
2882
|
+
}
|
|
2883
|
+
function keeps_verdict_intact(command) {
|
|
2884
|
+
return runs_verification(command) && !is_masked_verification(command);
|
|
2885
|
+
}
|
|
2886
|
+
var PIPEFAIL_OPEN = "(set -o pipefail; ";
|
|
2887
|
+
var PIPEFAIL_CLOSE = ")";
|
|
2888
|
+
var COMMENT_START = /(?:^|\s)#/u;
|
|
2889
|
+
var FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc)\b/u;
|
|
2890
|
+
var EARLY_EXIT_GREP = /^grep\b.*\s(?:-[a-zA-Z]*[lLmq]|--(?:quiet|silent|max-count|files-with(?:out)?-match))/u;
|
|
2891
|
+
var ALLOWED_OPERATORS = /2>&1|&&/gu;
|
|
2892
|
+
var SIDE_EFFECT_SYNTAX = /[&<>`]|\$\(/u;
|
|
2893
|
+
var GROUP_SYNTAX = /[()]/u;
|
|
2894
|
+
var PIPEFAIL_NOTE = "\u21BB piped verification: the josh check was piped, so its pipeline was wrapped in `(set -o pipefail; \u2026)` to carry the check's status, and the rest of the line ran as typed. Read the printed verdict, never the exit code alone \u2014 `prompts/collaboration-workflow/output-bounds.md`.";
|
|
2895
|
+
function reads_to_the_end(piece) {
|
|
2896
|
+
return FULL_READING_FILTER.test(piece) && !EARLY_EXIT_GREP.test(piece);
|
|
2897
|
+
}
|
|
2898
|
+
function is_filtered_check(pipeline) {
|
|
2899
|
+
const [head = "", ...filters] = pipeline;
|
|
2900
|
+
return is_verification_command(head) && filters.every((piece) => reads_to_the_end(piece));
|
|
2901
|
+
}
|
|
2902
|
+
function has_side_effect_syntax(command) {
|
|
2903
|
+
return SIDE_EFFECT_SYNTAX.test(command.replaceAll(ALLOWED_OPERATORS, " "));
|
|
2904
|
+
}
|
|
2905
|
+
function is_rewritable_part(raw, judged) {
|
|
2906
|
+
const pipeline = judged.split("|").map((piece) => piece.trim());
|
|
2907
|
+
return !has_side_effect_syntax(raw) && !GROUP_SYNTAX.test(judged) && is_filtered_check(pipeline);
|
|
2908
|
+
}
|
|
2909
|
+
function code_end(judged) {
|
|
2910
|
+
const comment = COMMENT_START.exec(judged);
|
|
2911
|
+
return comment === null ? judged.length : comment.index;
|
|
2912
|
+
}
|
|
2913
|
+
function wrapped(raw, judged) {
|
|
2914
|
+
const code = raw.slice(0, code_end(judged));
|
|
2915
|
+
const core = code.trim();
|
|
2916
|
+
const lead = code.slice(0, code.indexOf(core));
|
|
2917
|
+
return `${lead}${PIPEFAIL_OPEN}${core}${PIPEFAIL_CLOSE}${raw.slice(lead.length + core.length)}`;
|
|
2918
|
+
}
|
|
2919
|
+
function rewritten_part(raw, judged) {
|
|
2920
|
+
if (!is_masked_verification(judged)) return raw;
|
|
2921
|
+
return is_rewritable_part(raw, judged) ? wrapped(raw, judged) : void 0;
|
|
2922
|
+
}
|
|
2923
|
+
function pipefail_command(command) {
|
|
2924
|
+
const judged = time_shell.blanked(command);
|
|
2925
|
+
if (judged === void 0 || !is_masked_verification(command)) return void 0;
|
|
2926
|
+
const offset = { at: 0 };
|
|
2927
|
+
const parts = shell_segments.parts_of(judged).map((part) => {
|
|
2928
|
+
const raw = command.slice(offset.at, offset.at + part.length);
|
|
2929
|
+
offset.at += part.length;
|
|
2930
|
+
return rewritten_part(raw, part);
|
|
2931
|
+
});
|
|
2932
|
+
const rewritten = parts.includes(void 0) ? void 0 : parts.join("");
|
|
2933
|
+
return rewritten === command ? void 0 : rewritten;
|
|
2934
|
+
}
|
|
2935
|
+
function pipefail_input(call) {
|
|
2936
|
+
if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
|
|
2937
|
+
const command = pipefail_command(time_shell.bash_command(call.input));
|
|
2938
|
+
return command === void 0 ? void 0 : { ...call.input, command };
|
|
2939
|
+
}
|
|
2940
|
+
var piped_verification = {
|
|
2941
|
+
PIPED_VERIFICATION_REASON,
|
|
2942
|
+
PIPEFAIL_NOTE,
|
|
2943
|
+
VERIFICATION_COMMANDS,
|
|
2944
|
+
is_masked_verification,
|
|
2945
|
+
keeps_verdict_intact,
|
|
2946
|
+
pipefail_input,
|
|
2947
|
+
runs_verification
|
|
2948
|
+
};
|
|
2949
|
+
|
|
2950
|
+
// scripts/rules/poll-loop.ts
|
|
2951
|
+
var LOOP_KEYWORD2 = /\b(?:while|until)\s/u;
|
|
2952
|
+
var SLEEP_COMMAND = /\bsleep\s+\S/u;
|
|
2953
|
+
var COMMAND_POSITION = String.raw`(?:^|[;&|({\n]|\b(?:do|then|else)\s)\s*`;
|
|
2954
|
+
var UNTIL_HEADER = new RegExp(String.raw`${COMMAND_POSITION}until\s`, "u");
|
|
2955
|
+
var WHILE_HEADER = String.raw`${COMMAND_POSITION}while\s+(?:!\s*)?`;
|
|
2956
|
+
var PROBE_HEADERS = [
|
|
2957
|
+
String.raw`\[\[?\s+(?:!\s*)?-[defs]\s`,
|
|
2958
|
+
String.raw`test\s+(?:!\s*)?-[defs]\s`,
|
|
2959
|
+
String.raw`(?:kill\s+-0|pgrep)\b`,
|
|
2960
|
+
String.raw`grep\s+-[A-Za-z]*q`
|
|
2961
|
+
].map((probe) => new RegExp(`${WHILE_HEADER}${probe}`, "u"));
|
|
2962
|
+
function is_state_wait(plain) {
|
|
2963
|
+
return UNTIL_HEADER.test(plain) || PROBE_HEADERS.some((header) => header.test(plain));
|
|
2964
|
+
}
|
|
2965
|
+
var STREAM_READER = /\bwhile\s+read\b/u;
|
|
2966
|
+
function is_stream_reader(plain) {
|
|
2967
|
+
return STREAM_READER.test(plain);
|
|
2968
|
+
}
|
|
2969
|
+
function is_poll_loop(command) {
|
|
2970
|
+
const plain = time_shell.unquoted(command);
|
|
2971
|
+
if (is_stream_reader(plain)) return false;
|
|
2972
|
+
if (is_state_wait(plain)) return true;
|
|
2973
|
+
return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
|
|
2974
|
+
}
|
|
2975
|
+
var POLL_LOOP_REASON = "\u26D4 output poll loop: this hand-writes a `while`/`until` wait loop over a command's output, a marker file or a process, and the loop does not know when the command finished. Issue the long command with `run_in_background` set and end the turn \u2014 its exit re-invokes you; for CI, `pnpm josh followup` (`.claude/skills/workflow-commands/background-commands.md`). It fires on every occurrence.";
|
|
2976
|
+
var ROW20 = {
|
|
2977
|
+
id: "poll-loop",
|
|
2978
|
+
is_trigger: bash_triggers.on_bash_command(is_poll_loop),
|
|
2979
|
+
reason: POLL_LOOP_REASON,
|
|
2980
|
+
decide: () => true,
|
|
2981
|
+
about: {
|
|
2982
|
+
title: "Output poll loop",
|
|
2983
|
+
topic: "`.claude/skills/workflow-commands/background-commands.md`",
|
|
2984
|
+
fires: "a hand-written `while` / `until` wait loop over a command's output, a marker file or a process. Every occurrence",
|
|
2985
|
+
quiet: "the command started with `run_in_background` \u2014 its exit ends the wait"
|
|
2986
|
+
}
|
|
2987
|
+
};
|
|
2988
|
+
var poll_loop = {
|
|
2989
|
+
POLL_LOOP_REASON,
|
|
2990
|
+
ROW: ROW20,
|
|
2991
|
+
is_poll_loop
|
|
2992
|
+
};
|
|
2993
|
+
|
|
2994
|
+
// scripts/rules/raw-field-body.ts
|
|
2995
|
+
var RAW_FIELD_BODY_AT = /(?:^|\s)(?:-f|--raw-field)[\s=]+['"]?body=['"]?@/u;
|
|
2996
|
+
function posts_at_path_literally(command) {
|
|
2997
|
+
return RAW_FIELD_BODY_AT.test(command);
|
|
2998
|
+
}
|
|
2999
|
+
var RAW_FIELD_BODY_REASON = "\u26D4 literal @path body: this passes `body=@<path>` to `gh api` with a raw field flag (`-f` / `--raw-field`), which posts the string `@<path>` rather than the file. Post the comment with `pnpm josh issue:comment <N> --body-file <path>`; this rule fires on every occurrence (`prompts/collaboration-workflow/shell-body.md`).";
|
|
3000
|
+
var ROW21 = {
|
|
3001
|
+
id: "raw-field-body",
|
|
3002
|
+
is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
|
|
3003
|
+
reason: RAW_FIELD_BODY_REASON,
|
|
3004
|
+
decide: () => true,
|
|
3005
|
+
about: {
|
|
3006
|
+
title: "Posting `@path` literally",
|
|
3007
|
+
topic: "`shell-body.md`",
|
|
3008
|
+
fires: "a raw field (`-f` / `--raw-field`) passing `body=@<path>`. Every occurrence",
|
|
3009
|
+
quiet: "`-F` / `--field body=@`, a body without `@`, or `pnpm josh issue:comment`"
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW21, posts_at_path_literally };
|
|
3013
|
+
|
|
3014
|
+
// scripts/rules/rule-body-guard.ts
|
|
3015
|
+
var EDIT_TOOL = "Edit";
|
|
3016
|
+
var WRITE_TOOL = "Write";
|
|
3017
|
+
var CLAUDE_DOCUMENT = /(?:^|\/)CLAUDE\.md$/u;
|
|
3018
|
+
var PROMPT_DOCUMENT = /(?:^|\/)prompts\/.+\.md$/u;
|
|
3019
|
+
var SKILL_DOCUMENT = /(?:^|\/)\.claude\/skills\/.+\.md$/u;
|
|
3020
|
+
function is_rule_document(file_path) {
|
|
3021
|
+
return CLAUDE_DOCUMENT.test(file_path) || PROMPT_DOCUMENT.test(file_path) || SKILL_DOCUMENT.test(file_path);
|
|
3022
|
+
}
|
|
3023
|
+
var LINK_TARGET = /\]\([^)]*\)/gu;
|
|
3024
|
+
var BARE_URL = /https?:\/\/\S+/gu;
|
|
3025
|
+
function prose_of(text) {
|
|
3026
|
+
return text.replaceAll(LINK_TARGET, "]()").replaceAll(BARE_URL, "");
|
|
3027
|
+
}
|
|
3028
|
+
var MIN_APPENDED_PROSE = 120;
|
|
3029
|
+
function adds_rule_prose(old_text, new_text) {
|
|
3030
|
+
return prose_of(new_text).length - prose_of(old_text).length >= MIN_APPENDED_PROSE;
|
|
3031
|
+
}
|
|
3032
|
+
function string_field(input, key) {
|
|
3033
|
+
const value = input[key];
|
|
3034
|
+
return typeof value === "string" ? value : "";
|
|
3035
|
+
}
|
|
3036
|
+
function read_existing(file_path) {
|
|
3037
|
+
return file_reader.read_file_or_empty(file_path);
|
|
3038
|
+
}
|
|
3039
|
+
function texts_of(name, input, read2) {
|
|
3040
|
+
if (name === WRITE_TOOL) {
|
|
3041
|
+
return {
|
|
3042
|
+
old_text: read2(string_field(input, "file_path")),
|
|
3043
|
+
new_text: string_field(input, "content")
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
return {
|
|
3047
|
+
old_text: string_field(input, "old_string"),
|
|
3048
|
+
new_text: string_field(input, "new_string")
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
function is_edit_tool2(name) {
|
|
3052
|
+
return name === EDIT_TOOL || name === WRITE_TOOL;
|
|
3053
|
+
}
|
|
3054
|
+
function rule_document_input(call) {
|
|
3055
|
+
if (!is_edit_tool2(call.name) || !json_value.is_record(call.input)) return void 0;
|
|
3056
|
+
return is_rule_document(string_field(call.input, "file_path")) ? call.input : void 0;
|
|
3057
|
+
}
|
|
3058
|
+
function writes_rule_prose(call, read2 = read_existing) {
|
|
3059
|
+
const input = rule_document_input(call);
|
|
3060
|
+
if (input === void 0) return false;
|
|
3061
|
+
const { old_text, new_text } = texts_of(call.name, input, read2);
|
|
3062
|
+
return adds_rule_prose(old_text, new_text);
|
|
3063
|
+
}
|
|
3064
|
+
var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Answer question 0 (`pnpm josh oracle:list`) and the ordering question (`pnpm josh run:step`) of `prompts/collaboration-workflow/residency.md`; refused until both commands are on the transcript, then reissue this as a trigger-plus-pointer line (`prompts/collaboration-workflow/rule-delivery.md`).";
|
|
3065
|
+
var PLACEMENT_ORACLES = [
|
|
3066
|
+
/* @__PURE__ */ new Set(["oracle:list"]),
|
|
3067
|
+
/* @__PURE__ */ new Set(["run:step", "run:next"])
|
|
3068
|
+
];
|
|
3069
|
+
function ran_oracle(tail_commands_run, names) {
|
|
3070
|
+
return tail_commands_run.some(
|
|
3071
|
+
(command) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
function answered_placement(tail, _call) {
|
|
3075
|
+
const commands = tail_commands.prior_bash_commands(tail);
|
|
3076
|
+
return PLACEMENT_ORACLES.every((names) => ran_oracle(commands, names));
|
|
3077
|
+
}
|
|
3078
|
+
function runs_placement_oracle(command) {
|
|
3079
|
+
return PLACEMENT_ORACLES.some(
|
|
3080
|
+
(names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
var ROW22 = {
|
|
3084
|
+
id: "rule-body",
|
|
3085
|
+
is_trigger: writes_rule_prose,
|
|
3086
|
+
reason: RULE_BODY_REASON,
|
|
3087
|
+
already_satisfied: answered_placement,
|
|
3088
|
+
keeps: bash_triggers.on_bash_command(runs_placement_oracle),
|
|
3089
|
+
about: {
|
|
3090
|
+
title: "Question 0 and the ordering question before writing a rule into prose",
|
|
3091
|
+
topic: "`residency.md`",
|
|
3092
|
+
fires: "an `Edit` / `Write` to `CLAUDE.md` / `prompts/**/*.md` / `.claude/skills/**/*.md` appending at least the threshold. Refused until both `pnpm josh oracle:list` and `pnpm josh run:step` ran",
|
|
3093
|
+
quiet: "not a rule document, an append under the threshold (typo, link swap, deletion), or both commands ran"
|
|
3094
|
+
}
|
|
3095
|
+
};
|
|
3096
|
+
var rule_body_guard = {
|
|
3097
|
+
RULE_BODY_REASON,
|
|
3098
|
+
ROW: ROW22,
|
|
3099
|
+
answered_placement,
|
|
3100
|
+
is_rule_document,
|
|
3101
|
+
writes_rule_prose
|
|
3102
|
+
};
|
|
3103
|
+
|
|
3104
|
+
// scripts/rules/shell-body-trigger.ts
|
|
3105
|
+
var FIELD_FLAGS = "(?:-f|-F|--field|--raw-field)";
|
|
3106
|
+
var MESSAGE_FLAGS = "(?:--body|--notify-message|--recommended|--alternative)";
|
|
3107
|
+
var FIELD_OPENING = String.raw`${FIELD_FLAGS}[\s=]*(?:'?body="|"body=)`;
|
|
3108
|
+
var MESSAGE_OPENING = String.raw`${MESSAGE_FLAGS}[\s=]+"`;
|
|
3109
|
+
var NESTED_SUBSTITUTION = String.raw`\$\([^()]*\)`;
|
|
3110
|
+
var SUBSTITUTION = String.raw`\$\((?:[^()$]|\$(?!\()|${NESTED_SUBSTITUTION})*\)`;
|
|
3111
|
+
var QUOTED_VALUE = String.raw`(?:[^"\\$]|\\.|\$(?!\()|${SUBSTITUTION})*`;
|
|
3112
|
+
var INLINE_BODY_VALUE = new RegExp(
|
|
3113
|
+
`(?:${FIELD_OPENING}|${MESSAGE_OPENING})(${QUOTED_VALUE})"`,
|
|
3114
|
+
"gu"
|
|
3115
|
+
);
|
|
3116
|
+
var SHELL_EVALUATED = /[`$]/u;
|
|
3117
|
+
var WHOLE_VALUE_SUBSTITUTION = new RegExp(`^${SUBSTITUTION}$`, "u");
|
|
3118
|
+
var BACKTICK = "`";
|
|
3119
|
+
var WHOLE_VALUE_FILE_REFERENCE = /^@\S+$/u;
|
|
3120
|
+
var ESCAPED_PAIR = /\\./gu;
|
|
3121
|
+
function is_whole_value_substitution(literal) {
|
|
3122
|
+
return WHOLE_VALUE_SUBSTITUTION.test(literal) && !literal.includes(BACKTICK);
|
|
3123
|
+
}
|
|
3124
|
+
function is_body_already_in_a_file(literal) {
|
|
3125
|
+
return WHOLE_VALUE_FILE_REFERENCE.test(literal) || is_whole_value_substitution(literal);
|
|
3126
|
+
}
|
|
3127
|
+
function is_evaluated_value(raw_value) {
|
|
3128
|
+
const literal = raw_value.replaceAll(ESCAPED_PAIR, "");
|
|
3129
|
+
if (is_body_already_in_a_file(literal)) return false;
|
|
3130
|
+
return SHELL_EVALUATED.test(literal);
|
|
3131
|
+
}
|
|
3132
|
+
function is_shell_evaluated_body(command) {
|
|
3133
|
+
for (const match of command.matchAll(INLINE_BODY_VALUE)) {
|
|
3134
|
+
if (is_evaluated_value(match[1] ?? "")) return true;
|
|
3135
|
+
}
|
|
3136
|
+
return false;
|
|
3137
|
+
}
|
|
3138
|
+
var FILE_FLAG = /(?:--body-file|--notify-message-file)(?:[\s=]|$)/u;
|
|
3139
|
+
var FIELD_FILE_REFERENCE = new RegExp(String.raw`${FIELD_FLAGS}[\s=]*body=@\S`, "u");
|
|
3140
|
+
var ANSI_C_BODY = /(?:(?:--body|--notify-message)[\s=]+|body=)\$'/u;
|
|
3141
|
+
var DOUBLE_QUOTED_SPAN = /"[^"]*"/gu;
|
|
3142
|
+
var BODY_FLAG = new RegExp(
|
|
3143
|
+
String.raw`${FIELD_FLAGS}[\s=]*'?"?body=|${MESSAGE_FLAGS}[\s=]|--body-file[\s=]|--notify-message-file[\s=]`,
|
|
3144
|
+
"u"
|
|
3145
|
+
);
|
|
3146
|
+
var QUOTED_SPAN = /"[^"]*"|'[^']*'/gu;
|
|
3147
|
+
function inline_body_literals(command) {
|
|
3148
|
+
return Array.from(
|
|
3149
|
+
command.matchAll(INLINE_BODY_VALUE),
|
|
3150
|
+
(match) => (match[1] ?? "").replaceAll(ESCAPED_PAIR, "")
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
function is_safe_body_form(command) {
|
|
3154
|
+
if (ANSI_C_BODY.test(command.replaceAll(DOUBLE_QUOTED_SPAN, " "))) return true;
|
|
3155
|
+
const flags = command.replaceAll(QUOTED_SPAN, " ");
|
|
3156
|
+
if (FILE_FLAG.test(flags) || FIELD_FILE_REFERENCE.test(flags)) return true;
|
|
3157
|
+
return inline_body_literals(command).some((literal) => is_body_already_in_a_file(literal));
|
|
3158
|
+
}
|
|
3159
|
+
function keeps_body_safe(command) {
|
|
3160
|
+
return is_safe_body_form(command) && !is_shell_evaluated_body(command);
|
|
3161
|
+
}
|
|
3162
|
+
function carries_a_body(command) {
|
|
3163
|
+
if (BODY_FLAG.test(command.replaceAll(QUOTED_SPAN, " "))) return true;
|
|
3164
|
+
return inline_body_literals(command).length > 0;
|
|
3165
|
+
}
|
|
3166
|
+
var SHELL_BODY_REASON = "\u26D4 shell-evaluated body: this command carries a body inline in double quotes, and that body contains a backtick or a `$`, so the shell executes it. Reissue this call once the body is in a file, passed by path (`pnpm josh issue:comment <N> --body-file <path>`, `--notify-message-file <path>`), or use `$'\u2026'` quoting (`prompts/collaboration-workflow/shell-body.md`).";
|
|
3167
|
+
var shell_body_trigger = {
|
|
3168
|
+
SHELL_BODY_REASON,
|
|
3169
|
+
carries_a_body,
|
|
3170
|
+
is_shell_evaluated_body,
|
|
3171
|
+
keeps_body_safe
|
|
3172
|
+
};
|
|
3173
|
+
|
|
3174
|
+
// scripts/test/test-declared-logic.ts
|
|
3175
|
+
var TEST_SUFFIXES = [".e2e.ts", ".test.ts"];
|
|
3176
|
+
var EXEMPT_PATHS = [".editorconfig"];
|
|
3177
|
+
var EXEMPT_PREFIXES = [".idea/", ".vscode/", "prompts/"];
|
|
3178
|
+
var EXEMPT_SUFFIXES = [".md"];
|
|
3179
|
+
var BASIC_VISUAL_SUFFIXES = [".html", ".css"];
|
|
3180
|
+
var BASIC_REQUIRED_SUFFIXES = [
|
|
3181
|
+
".ts",
|
|
3182
|
+
".tsx",
|
|
3183
|
+
".mts",
|
|
3184
|
+
".cts",
|
|
3185
|
+
".js",
|
|
3186
|
+
".jsx",
|
|
3187
|
+
".mjs",
|
|
3188
|
+
".cjs",
|
|
3189
|
+
".svelte",
|
|
3190
|
+
".json",
|
|
3191
|
+
".jsonc",
|
|
3192
|
+
".json5",
|
|
3193
|
+
".yaml",
|
|
3194
|
+
".yml",
|
|
3195
|
+
".toml"
|
|
3196
|
+
];
|
|
3197
|
+
function normalize(paths) {
|
|
3198
|
+
return paths.map((path5) => path5.trim()).filter((path5) => path5 !== "");
|
|
3199
|
+
}
|
|
3200
|
+
function is_test_file(path5) {
|
|
3201
|
+
return TEST_SUFFIXES.some((suffix) => path5.endsWith(suffix));
|
|
3202
|
+
}
|
|
3203
|
+
function is_basic_manual(path5, is_basic) {
|
|
3204
|
+
const lower = path5.toLowerCase();
|
|
3205
|
+
return is_basic && BASIC_REQUIRED_SUFFIXES.every((suffix) => !lower.endsWith(suffix));
|
|
3206
|
+
}
|
|
3207
|
+
function is_basic_visual(path5) {
|
|
3208
|
+
const lower = path5.toLowerCase();
|
|
3209
|
+
return BASIC_VISUAL_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
3210
|
+
}
|
|
3211
|
+
function is_exempt(path5, is_basic = false) {
|
|
3212
|
+
if (EXEMPT_PATHS.includes(path5) || is_basic_manual(path5, is_basic)) return true;
|
|
3213
|
+
return EXEMPT_SUFFIXES.some((suffix) => path5.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path5.startsWith(prefix));
|
|
3214
|
+
}
|
|
3215
|
+
function is_runtime(path5, is_basic = false) {
|
|
3216
|
+
return !is_test_file(path5) && !is_exempt(path5, is_basic);
|
|
3217
|
+
}
|
|
3218
|
+
function verdict_for2(paths, is_basic = false) {
|
|
3219
|
+
const changed = normalize(paths);
|
|
3220
|
+
if (changed.some((path5) => is_test_file(path5))) return "satisfied";
|
|
3221
|
+
if (changed.some((path5) => is_runtime(path5, is_basic))) return "required";
|
|
3222
|
+
return "exempt";
|
|
3223
|
+
}
|
|
3224
|
+
function runtime_files(paths, is_basic = false) {
|
|
3225
|
+
return normalize(paths).filter((path5) => is_runtime(path5, is_basic));
|
|
3226
|
+
}
|
|
3227
|
+
function exempt_files(paths, is_basic = false) {
|
|
3228
|
+
return normalize(paths).filter((path5) => is_exempt(path5, is_basic));
|
|
3229
|
+
}
|
|
3230
|
+
function manual_check_files(paths, is_basic) {
|
|
3231
|
+
return normalize(paths).filter((path5) => is_exempt(path5, is_basic) && !is_exempt(path5));
|
|
3232
|
+
}
|
|
3233
|
+
var test_declared_logic = {
|
|
3234
|
+
EXEMPT_PATHS,
|
|
3235
|
+
EXEMPT_PREFIXES,
|
|
3236
|
+
EXEMPT_SUFFIXES,
|
|
3237
|
+
exempt_files,
|
|
3238
|
+
is_basic_visual,
|
|
3239
|
+
is_exempt,
|
|
3240
|
+
is_runtime,
|
|
3241
|
+
is_test_file,
|
|
3242
|
+
manual_check_files,
|
|
3243
|
+
runtime_files,
|
|
3244
|
+
verdict_for: verdict_for2
|
|
3245
|
+
};
|
|
3246
|
+
|
|
3247
|
+
// scripts/test/test-declared-changed.ts
|
|
3248
|
+
var RENAME_ARROW = " -> ";
|
|
3249
|
+
var STATUS_PREFIX_LENGTH = 3;
|
|
3250
|
+
function path_of(line) {
|
|
3251
|
+
const body = line.slice(STATUS_PREFIX_LENGTH);
|
|
3252
|
+
const arrow = body.indexOf(RENAME_ARROW);
|
|
3253
|
+
return arrow === -1 ? body : body.slice(arrow + RENAME_ARROW.length);
|
|
3254
|
+
}
|
|
3255
|
+
function read_changed_paths_sync() {
|
|
3256
|
+
const output = git_spawn_sync.run(["status", PORCELAIN_FLAG], { timeout: 0 });
|
|
3257
|
+
if (output.exit_code !== 0) return [];
|
|
3258
|
+
return output.stdout.split("\n").filter((line) => line.length > STATUS_PREFIX_LENGTH).map((line) => path_of(line));
|
|
3259
|
+
}
|
|
3260
|
+
var injected = {};
|
|
3261
|
+
function source_paths() {
|
|
3262
|
+
return injected.paths ?? read_changed_paths_sync();
|
|
3263
|
+
}
|
|
3264
|
+
function current_verdict(paths = source_paths()) {
|
|
3265
|
+
return test_declared_logic.verdict_for(paths, project_checks.is_basic(process.cwd()));
|
|
3266
|
+
}
|
|
3267
|
+
function restore_injection(previous) {
|
|
3268
|
+
if (previous === void 0) {
|
|
3269
|
+
delete injected.paths;
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
injected.paths = previous;
|
|
3273
|
+
}
|
|
3274
|
+
function with_paths(paths, body) {
|
|
3275
|
+
const previous = injected.paths;
|
|
3276
|
+
injected.paths = paths;
|
|
3277
|
+
try {
|
|
3278
|
+
return body();
|
|
3279
|
+
} finally {
|
|
3280
|
+
restore_injection(previous);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
var test_declared_changed = { current_verdict, path_of, read_changed_paths_sync, with_paths };
|
|
3284
|
+
|
|
3285
|
+
// scripts/rules/test-declared-commit.ts
|
|
3286
|
+
function is_untested_commit(call) {
|
|
3287
|
+
if (!run_tail_rule.is_push_step_call(call)) return false;
|
|
3288
|
+
return test_declared_changed.current_verdict() === "required";
|
|
3289
|
+
}
|
|
3290
|
+
var REASON3 = "\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads it `required` and names the files. Tests are required for ALL code changes: add the test and reissue, or, for a change that touches no runtime path, declare the non-runtime exception in your Step 0 work summary and reissue. It fires once per run (`CLAUDE.md` \u2192 Code Change Rules).";
|
|
3291
|
+
var ROW23 = {
|
|
3292
|
+
id: "test-declared",
|
|
3293
|
+
is_trigger: is_untested_commit,
|
|
3294
|
+
reason: REASON3,
|
|
3295
|
+
about: {
|
|
3296
|
+
title: "Test declaration",
|
|
3297
|
+
topic: "`CLAUDE.md` \u2192 Code Change Rules",
|
|
3298
|
+
fires: "`pnpm josh git -y` (`josh g`) while `pnpm josh test:declared` answers `required`",
|
|
3299
|
+
quiet: "`exempt` (non-runtime only) or `satisfied` (tests included)"
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
var test_declared_commit = { REASON: REASON3, ROW: ROW23 };
|
|
3303
|
+
|
|
3304
|
+
// scripts/rules/third-party-write.ts
|
|
3305
|
+
function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
|
|
3306
|
+
if (!gh_api.is_gh_api(segment) || !gh_api.is_write(segment)) return false;
|
|
3307
|
+
const target = gh_api.repo_target(segment);
|
|
3308
|
+
if (target === void 0) return false;
|
|
3309
|
+
if (!gh_api.is_owner_placeholder(target.owner)) {
|
|
3310
|
+
return repo_party.classify(session_owner, target.owner) === repo_party.THIRD_PARTY;
|
|
3311
|
+
}
|
|
3312
|
+
return resolve_placeholder_owners().some(
|
|
3313
|
+
(owner) => owner === void 0 || repo_party.classify(session_owner, owner) === repo_party.THIRD_PARTY
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
var GH_CONTEXT_CHANGE = /(?:^|\s)(?:cd|pushd)(?:\s|$)|(?:^|\s)(?:export\s+)?GH_REPO=/u;
|
|
3317
|
+
function unreadable_candidates() {
|
|
3318
|
+
return [void 0];
|
|
3319
|
+
}
|
|
3320
|
+
function candidates_at(segments, index, resolve_placeholder_owners) {
|
|
3321
|
+
const has_moved = segments.slice(0, index).some((segment) => GH_CONTEXT_CHANGE.test(segment));
|
|
3322
|
+
return has_moved ? unreadable_candidates : resolve_placeholder_owners;
|
|
3323
|
+
}
|
|
3324
|
+
function writes_third_party(command, resolve_owner = repo_party.current_owner, resolve_placeholder_owners = repo_party.placeholder_owners) {
|
|
3325
|
+
const segments = shell_segments.segments_of(command);
|
|
3326
|
+
const has_write = segments.some(
|
|
3327
|
+
(segment) => gh_api.is_gh_api(segment) && gh_api.is_write(segment)
|
|
3328
|
+
);
|
|
3329
|
+
if (!has_write) return false;
|
|
3330
|
+
const session_owner = resolve_owner();
|
|
3331
|
+
return segments.some(
|
|
3332
|
+
(segment, index) => is_third_party_write_segment(
|
|
3333
|
+
segment,
|
|
3334
|
+
session_owner,
|
|
3335
|
+
candidates_at(segments, index, resolve_placeholder_owners)
|
|
3336
|
+
)
|
|
3337
|
+
);
|
|
3338
|
+
}
|
|
3339
|
+
var THIRD_PARTY_WRITE_REASON = "\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session, which is Tier C and needs explicit current-turn user instruction. Check with `pnpm josh repo:party <owner/repo>`, then record the finding on our own Issue and draft the artifact for a person to post. This rule fires on every occurrence (`prompts/collaboration-workflow/upstream-interrupt.md`).";
|
|
3340
|
+
var ROW24 = {
|
|
3341
|
+
id: "third-party-write",
|
|
3342
|
+
is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
|
|
3343
|
+
reason: THIRD_PARTY_WRITE_REASON,
|
|
3344
|
+
decide: () => true,
|
|
3345
|
+
about: {
|
|
3346
|
+
title: "Third-party write",
|
|
3347
|
+
topic: "`upstream-interrupt.md`",
|
|
3348
|
+
fires: "a `gh api` write (field flag or a write method) to a repository whose owner is not this session. Every occurrence",
|
|
3349
|
+
quiet: "a read, or a first-party owner (`pnpm josh repo:party`)"
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
var third_party_write = {
|
|
3353
|
+
ROW: ROW24,
|
|
3354
|
+
THIRD_PARTY_WRITE_REASON,
|
|
3355
|
+
writes_third_party
|
|
3356
|
+
};
|
|
3357
|
+
|
|
3358
|
+
// scripts/rules/timed-rerun.ts
|
|
3359
|
+
var TIMED_SEGMENT = /^\s*(?:[A-Za-z_]\w*=\S*\s+)*(?:\S*\/)?(?:time|hyperfine)\s/u;
|
|
3360
|
+
function is_timed_run(command) {
|
|
3361
|
+
return shell_segments.quote_aware_commands_of(command).some((segment) => TIMED_SEGMENT.test(segment));
|
|
3362
|
+
}
|
|
3363
|
+
var RECORD_READ = /\bgh\s+api\b.*\/actions\/runs\b|\bjosh\s+(?:lane:stats|lst)\b/u;
|
|
3364
|
+
function reads_the_record(command) {
|
|
3365
|
+
return RECORD_READ.test(command);
|
|
3366
|
+
}
|
|
3367
|
+
function measures_duration(command) {
|
|
3368
|
+
return reads_the_record(command) || is_timed_run(command);
|
|
3369
|
+
}
|
|
3370
|
+
var TIMED_RERUN_REASON = "\u26D4 timed re-run: a CI or gate duration is already on record \u2014 read the Actions jobs with `gh api \u2026/actions/runs/<run_id>/jobs` or the gate timings with `pnpm josh lane:stats`. Re-run only when the record cannot answer: reissue the same command once, and post the result on the Issue (`prompts/collaboration-workflow/measurement.md`).";
|
|
3371
|
+
var ROW25 = {
|
|
3372
|
+
id: "timed-rerun",
|
|
3373
|
+
is_trigger: bash_triggers.on_bash_command(is_timed_run),
|
|
3374
|
+
reason: TIMED_RERUN_REASON,
|
|
3375
|
+
keeps: bash_triggers.on_bash_command(reads_the_record),
|
|
3376
|
+
reaches: bash_triggers.on_bash_command(measures_duration),
|
|
3377
|
+
about: {
|
|
3378
|
+
title: "Timed re-run",
|
|
3379
|
+
topic: "`measurement.md`",
|
|
3380
|
+
fires: "a command timed with `time` / `/usr/bin/time` / `hyperfine`",
|
|
3381
|
+
quiet: "the duration is read from the Actions jobs or `pnpm josh lane:stats`"
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
var timed_rerun = {
|
|
3385
|
+
ROW: ROW25,
|
|
3386
|
+
TIMED_RERUN_REASON,
|
|
3387
|
+
is_timed_run,
|
|
3388
|
+
reads_the_record
|
|
3389
|
+
};
|
|
3390
|
+
|
|
3391
|
+
// scripts/rules/delivered-rules.ts
|
|
3392
|
+
var SWITCH_ENV_KEY3 = "JOSH_RULE_GUARD";
|
|
3393
|
+
var PRETOOL_GUARD = "pnpm josh pretool:guard";
|
|
3394
|
+
var STAMP_PREFIX3 = "josh-rule-guard-";
|
|
3395
|
+
var { is_issue_filing, on_bash_command } = bash_triggers;
|
|
3396
|
+
var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
|
|
3397
|
+
var DELIVERED_RULES = [
|
|
3398
|
+
// First: a third-party write is stopped before the first-party filing rows count anything.
|
|
3399
|
+
third_party_write.ROW,
|
|
3400
|
+
// The direct call only; `filing-cap` below triggers on `issue:file`. The WIP cap and the fold
|
|
3401
|
+
// question are `issue:file`'s own steps, so no row gates them.
|
|
3402
|
+
direct_filing.ROW,
|
|
3403
|
+
// Before the body rows: a comment posted by hand is refused whatever its body, and the command it
|
|
3404
|
+
// points at takes the body by path, so the body rows have nothing left to say.
|
|
3405
|
+
direct_issue_comment.ROW,
|
|
3406
|
+
filing_cap.ROW,
|
|
3407
|
+
issue_comments.ROW,
|
|
3408
|
+
{
|
|
3409
|
+
id: "shell-body",
|
|
3410
|
+
is_trigger: on_bash_command(is_shell_evaluated_body2),
|
|
3411
|
+
reason: SHELL_BODY_REASON2,
|
|
3412
|
+
keeps: on_bash_command(keeps_body_safe2),
|
|
3413
|
+
reaches: on_bash_command(carries_a_body2),
|
|
3414
|
+
about: {
|
|
3415
|
+
title: "No body on the shell",
|
|
3416
|
+
topic: "`shell-body.md`",
|
|
3417
|
+
fires: 'a double-quoted body value (`-f body="\u2026"` / `-f "body=\u2026"` / `--body "\u2026"` / `--notify-message "\u2026"`) carrying a backtick or `$`',
|
|
3418
|
+
quiet: "the shell does not evaluate the body"
|
|
3419
|
+
}
|
|
3420
|
+
},
|
|
3421
|
+
raw_field_body.ROW,
|
|
3422
|
+
{
|
|
3423
|
+
id: "piped-verification",
|
|
3424
|
+
is_trigger: on_bash_command(piped_verification.is_masked_verification),
|
|
3425
|
+
reason: piped_verification.PIPED_VERIFICATION_REASON,
|
|
3426
|
+
keeps: on_bash_command(piped_verification.keeps_verdict_intact),
|
|
3427
|
+
reaches: on_bash_command(piped_verification.runs_verification),
|
|
3428
|
+
// A masking whose filter reads to the end is rewritten under `pipefail`, never refused.
|
|
3429
|
+
rewrite: piped_verification.pipefail_input,
|
|
3430
|
+
rewrite_note: piped_verification.PIPEFAIL_NOTE,
|
|
3431
|
+
about: {
|
|
3432
|
+
title: "Piped verification",
|
|
3433
|
+
topic: "`output-bounds.md`",
|
|
3434
|
+
fires: "a pass/fail josh check (`gate` / `check` / `lint*` / `cspell*` / `test*` / `eval` / `overrides` / `ranges`) in front of a pipe \u2014 rewritten under `set -o pipefail` when every filter is `tail` or a full-reading `grep`, refused otherwise",
|
|
3435
|
+
quiet: "the exit code reaches the caller \u2014 a failure reads as a failure"
|
|
3436
|
+
}
|
|
3437
|
+
},
|
|
3438
|
+
{
|
|
3439
|
+
id: "early-heartbeat",
|
|
3440
|
+
is_trigger: on_bash_command(early_heartbeat.is_wait_timer),
|
|
3441
|
+
reason: early_heartbeat.EARLY_HEARTBEAT_REASON,
|
|
3442
|
+
decide: early_heartbeat.decide,
|
|
3443
|
+
keeps: on_bash_command(early_heartbeat.is_progress_watch),
|
|
3444
|
+
reaches: on_bash_command(early_heartbeat.waits_for_progress),
|
|
3445
|
+
about: {
|
|
3446
|
+
title: "Early progress report",
|
|
3447
|
+
topic: '`.claude/skills/workflow-commands/progress-watcher.md` \u2192 "Progress while the run is quiet"',
|
|
3448
|
+
fires: "a `Bash` whose only purpose is waiting (`sleep` alone, or `echo` / `:` / `date`) while a timer is live or before the interval. Every occurrence",
|
|
3449
|
+
quiet: "no hand-made wait timer \u2014 `run:progress` holds the one clock"
|
|
3450
|
+
}
|
|
3451
|
+
},
|
|
3452
|
+
// Before `run-tail`: whether to commit at all is decided before how to push.
|
|
3453
|
+
test_declared_commit.ROW,
|
|
3454
|
+
// Before `run-tail`, so a lane child goes straight to `ship --detach` instead of through both.
|
|
3455
|
+
lane_background.ROW,
|
|
3456
|
+
// Reads `run_in_background` beside the command; `decide`, since a push recurs.
|
|
3457
|
+
{
|
|
3458
|
+
id: "run-tail",
|
|
3459
|
+
is_trigger: run_tail_rule.is_foreground_push_step,
|
|
3460
|
+
reason: run_tail_rule.RUN_TAIL_REASON,
|
|
3461
|
+
decide: run_tail_rule.decide,
|
|
3462
|
+
keeps: run_tail_rule.is_backgrounded_push_step,
|
|
3463
|
+
reaches: run_tail_rule.is_push_step_call,
|
|
3464
|
+
about: {
|
|
3465
|
+
title: "Idle run tail",
|
|
3466
|
+
topic: "`.claude/skills/workflow-commands/background-commands.md`",
|
|
3467
|
+
fires: "`pnpm josh git -y` (`josh g`, `--yes` alike) issued in the **foreground**. Every occurrence",
|
|
3468
|
+
quiet: "issued in the background \u2014 its completion resumes the run"
|
|
3469
|
+
}
|
|
3470
|
+
},
|
|
3471
|
+
// Reads the lane name and the cut record. Once per run, not `decide`: taking the cut ends the
|
|
3472
|
+
// process, and the verdicts that leave a run at the gate all need the reissue through. `reaches`
|
|
3473
|
+
// tells the cutting run from the resumed one by the run's calls. Overlaps `piped-verification`
|
|
3474
|
+
// safely: that row speaks first and this one delivers on the reissue.
|
|
3475
|
+
{
|
|
3476
|
+
id: "pre-gate-cut",
|
|
3477
|
+
is_trigger: on_bash_command(pre_gate_cut.is_uncut_gate),
|
|
3478
|
+
reason: pre_gate_cut.PRE_GATE_CUT_REASON,
|
|
3479
|
+
keeps: on_bash_command(pre_gate_cut.takes_the_cut),
|
|
3480
|
+
reaches: pre_gate_cut.reaches_the_pre_gate_boundary,
|
|
3481
|
+
about: {
|
|
3482
|
+
title: "The cut before the gate",
|
|
3483
|
+
topic: "`.claude/skills/workflow-commands/pre-gate-cut.md`",
|
|
3484
|
+
fires: "`pnpm josh gate` (`josh ga`) in a lane work tree whose `JOSH_LANE_CHILD` names the Issue and has no cut record",
|
|
3485
|
+
quiet: "outside a lane, no marker (a person in the lane), or already cut"
|
|
3486
|
+
}
|
|
3487
|
+
},
|
|
3488
|
+
// A split child's park is refused before `lane-park` sees it.
|
|
3489
|
+
lane_split_park.ROW,
|
|
3490
|
+
lane_park.ROW,
|
|
3491
|
+
lane_interactive_ask.ROW,
|
|
3492
|
+
agent_model.ROW,
|
|
3493
|
+
lane_carry_conflict.ROW,
|
|
3494
|
+
lane_switch_main.ROW,
|
|
3495
|
+
// Before `lane-direct-tool`, its one overlap (a timed tool in a lane): the record is the right first
|
|
3496
|
+
// act, and `lane-direct-tool` delivers on the reissue of a `time`-prefixed tool.
|
|
3497
|
+
timed_rerun.ROW,
|
|
3498
|
+
lane_direct_tool.ROW,
|
|
3499
|
+
// Disjoint from `run-tail` by the `-y` flag.
|
|
3500
|
+
josh_git_bare.ROW,
|
|
3501
|
+
// The Bash-string gaps the deny glob cannot express, read by argv; `delivered-rules-bash.test.ts`
|
|
3502
|
+
// pins that no two rows claim one command.
|
|
3503
|
+
git_force.ROW,
|
|
3504
|
+
worktree_guard.ROW,
|
|
3505
|
+
file_body.ROW,
|
|
3506
|
+
...permission_guards.ROWS,
|
|
3507
|
+
poll_loop.ROW,
|
|
3508
|
+
// Before `rule-body`, its one overlap: the cut is the right first act, and `rule-body` delivers on
|
|
3509
|
+
// the reissue in the fresh process.
|
|
3510
|
+
implementation_cut.ROW,
|
|
3511
|
+
rule_body_guard.ROW,
|
|
3512
|
+
...oracle_consulted.ROWS
|
|
3513
|
+
];
|
|
3514
|
+
var ALONE_IN_TURN = 1;
|
|
3515
|
+
function batches_the_turn(call, turn) {
|
|
3516
|
+
return time_batch_guard.is_guarded_call(call) && turn.length > ALONE_IN_TURN;
|
|
3517
|
+
}
|
|
3518
|
+
var BATCHING_RULE = {
|
|
3519
|
+
id: "batching",
|
|
3520
|
+
reason: time_batch_guard.REASON,
|
|
3521
|
+
keeps: batches_the_turn,
|
|
3522
|
+
reaches: time_batch_guard.is_guarded_call,
|
|
3523
|
+
about: {
|
|
3524
|
+
title: "Turn batching",
|
|
3525
|
+
topic: "`turn-batching.md`",
|
|
3526
|
+
fires: "the `Bash` / `Edit` / `Read` after three single-call turns in a row; fires **again** every three more such turns",
|
|
3527
|
+
quiet: "calls per round trip above the floor"
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
var INVESTIGATION_RULE = {
|
|
3531
|
+
id: "investigation",
|
|
3532
|
+
reason: investigation_reads.REASON,
|
|
3533
|
+
reaches: investigation_reads.is_refusable_call,
|
|
3534
|
+
about: {
|
|
3535
|
+
title: "Investigation delegation threshold",
|
|
3536
|
+
topic: "`delegation.md`",
|
|
3537
|
+
fires: "the `Read` / `Bash` that makes the third read of a file the run will not edit; fires **again** once reads pile up to the threshold again",
|
|
3538
|
+
quiet: "reads under the threshold \u2014 nothing to delegate yet"
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
var MEASURED_RULES = [
|
|
3542
|
+
...DELIVERED_RULES,
|
|
3543
|
+
BATCHING_RULE,
|
|
3544
|
+
INVESTIGATION_RULE
|
|
3545
|
+
];
|
|
3546
|
+
var BATCH_STAMP = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX);
|
|
3547
|
+
var BATCH_REFUSAL_WINDOW_MS = 1e4;
|
|
3548
|
+
function will_batch_guard_refuse(tail, call, run) {
|
|
3549
|
+
if (lane_guard_policy.mode_here("batching") !== "refuse") return false;
|
|
3550
|
+
const refused_at_ms = BATCH_STAMP.last_ms(BATCH_STAMP.path(run.transcript));
|
|
3551
|
+
if (run.now_ms - refused_at_ms < BATCH_REFUSAL_WINDOW_MS) return true;
|
|
3552
|
+
return time_batch_guard.should_block(tail, call, refused_at_ms);
|
|
3553
|
+
}
|
|
3554
|
+
function is_first_delivery(tail, call, delivered_at_ms, run) {
|
|
3555
|
+
return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run);
|
|
3556
|
+
}
|
|
3557
|
+
function delivery_decision(rule) {
|
|
3558
|
+
const { decide: decide4, already_satisfied } = rule;
|
|
3559
|
+
if (decide4 === void 0 && already_satisfied === void 0) return is_first_delivery;
|
|
3560
|
+
return function should_block3(tail, call, delivered_at_ms, run) {
|
|
3561
|
+
if (already_satisfied?.(tail, call) === true) return false;
|
|
3562
|
+
if (decide4 === void 0) return true;
|
|
3563
|
+
return decide4(call, run, !will_batch_guard_refuse(tail, call, run), delivered_at_ms);
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
function guard_of(rule) {
|
|
3567
|
+
return hook_decision.create_transcript_guard({
|
|
3568
|
+
prefix: `${STAMP_PREFIX3}${rule.id}-`,
|
|
3569
|
+
switch_key: SWITCH_ENV_KEY3,
|
|
3570
|
+
is_candidate: rule.is_trigger,
|
|
3571
|
+
should_block: delivery_decision(rule),
|
|
3572
|
+
reason: rule.reason
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
var GUARDS = new Map(DELIVERED_RULES.map((rule) => [rule.id, guard_of(rule)]));
|
|
3576
|
+
function delivery_path(rule_id, transcript_path) {
|
|
3577
|
+
return GUARDS.get(rule_id)?.refusal_path(transcript_path) ?? "";
|
|
3578
|
+
}
|
|
3579
|
+
function delivery(raw_payload, now_ms = Date.now()) {
|
|
3580
|
+
for (const guard of GUARDS.values()) {
|
|
3581
|
+
const reason = guard.refusal(raw_payload, now_ms);
|
|
3582
|
+
if (reason !== void 0) return reason;
|
|
3583
|
+
}
|
|
3584
|
+
return void 0;
|
|
3585
|
+
}
|
|
3586
|
+
function is_enabled3() {
|
|
3587
|
+
return hook_decision.is_switch_enabled(SWITCH_ENV_KEY3);
|
|
3588
|
+
}
|
|
3589
|
+
var delivered_rules = {
|
|
3590
|
+
DELIVERED_RULES,
|
|
3591
|
+
EARLY_HEARTBEAT_REASON: early_heartbeat.EARLY_HEARTBEAT_REASON,
|
|
3592
|
+
FILE_BODY_REASON: file_body.FILE_BODY_REASON,
|
|
3593
|
+
DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
|
|
3594
|
+
FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
|
|
3595
|
+
GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
|
|
3596
|
+
ISSUE_COMMENTS_REASON: issue_comments.ISSUE_COMMENTS_REASON,
|
|
3597
|
+
LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
|
|
3598
|
+
LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
|
|
3599
|
+
MEASURED_RULES,
|
|
3600
|
+
PIPED_VERIFICATION_REASON: piped_verification.PIPED_VERIFICATION_REASON,
|
|
3601
|
+
POLL_LOOP_REASON: poll_loop.POLL_LOOP_REASON,
|
|
3602
|
+
PRE_GATE_CUT_REASON: pre_gate_cut.PRE_GATE_CUT_REASON,
|
|
3603
|
+
PRETOOL_GUARD,
|
|
3604
|
+
RAW_FIELD_BODY_REASON: raw_field_body.RAW_FIELD_BODY_REASON,
|
|
3605
|
+
RUN_TAIL_REASON: run_tail_rule.RUN_TAIL_REASON,
|
|
3606
|
+
SHELL_BODY_REASON: SHELL_BODY_REASON2,
|
|
3607
|
+
SWITCH_ENV_KEY: SWITCH_ENV_KEY3,
|
|
3608
|
+
THIRD_PARTY_WRITE_REASON: third_party_write.THIRD_PARTY_WRITE_REASON,
|
|
3609
|
+
WORKTREE_MUTATION_REASON: worktree_guard.WORKTREE_MUTATION_REASON,
|
|
3610
|
+
delivery,
|
|
3611
|
+
delivery_path,
|
|
3612
|
+
is_body_only_issue_read: issue_comments.is_body_only_issue_read,
|
|
3613
|
+
is_enabled: is_enabled3,
|
|
3614
|
+
is_issue_filing
|
|
3615
|
+
};
|
|
3616
|
+
|
|
3617
|
+
// scripts/rules/guard-remedy.ts
|
|
3618
|
+
var REWRITTEN = "the hook makes the same call right itself; it refuses only a spelling it cannot rewrite";
|
|
3619
|
+
var ANOTHER_TOOL = "the right form is another tool, which `updatedInput` cannot switch to";
|
|
3620
|
+
var ANOTHER_COMMAND = "the right act is another command, whose arguments the call does not carry";
|
|
3621
|
+
var NO_RUN = "the call must not run at all, or only on a person's word \u2014 refusing is the remedy";
|
|
3622
|
+
var STOP_NOTIFY = "the hook sends the notify itself; it blocks only when the send fails";
|
|
3623
|
+
var HOLD_RELEASE = "the hook releases the hold itself; it blocks only when that release fails";
|
|
3624
|
+
var ISSUE_CITATION = "the hook prints the citation lines itself; it blocks only on a number it cannot cite";
|
|
3625
|
+
var FILING_OFFER = "the filing needs a title and a body only the model can write";
|
|
3626
|
+
function rewrite() {
|
|
3627
|
+
return { remedy: "rewrite", why: REWRITTEN };
|
|
3628
|
+
}
|
|
3629
|
+
function judgment(why) {
|
|
3630
|
+
return { remedy: "judgment", why };
|
|
3631
|
+
}
|
|
3632
|
+
var GUARD_REMEDIES = {
|
|
3633
|
+
// The rewrites: each fix is the same call made right.
|
|
3634
|
+
"piped-verification": rewrite(),
|
|
3635
|
+
// wrapped in `(set -o pipefail; …)`
|
|
3636
|
+
"issue-comments": rewrite(),
|
|
3637
|
+
// the same Issue's comments read on the same line
|
|
3638
|
+
"lane-background": rewrite(),
|
|
3639
|
+
// a lane child's `ship` run in the foreground (`pretool-guard.ts`)
|
|
3640
|
+
"file-body": judgment(ANOTHER_TOOL),
|
|
3641
|
+
// Edit or Write
|
|
3642
|
+
"shell-body": judgment(
|
|
3643
|
+
"which part of the body the shell was meant to expand is the author's, not the hook's"
|
|
3644
|
+
),
|
|
3645
|
+
"raw-field-body": judgment("the body moves to a file the call does not name"),
|
|
3646
|
+
"rule-body": judgment("a trigger-plus-pointer line is written, not derived"),
|
|
3647
|
+
"direct-filing": judgment(ANOTHER_COMMAND),
|
|
3648
|
+
"direct-issue-comment": judgment(ANOTHER_COMMAND),
|
|
3649
|
+
"direct-pr-create": judgment(ANOTHER_COMMAND),
|
|
3650
|
+
"pr-merge": judgment(ANOTHER_COMMAND),
|
|
3651
|
+
"josh-git-bare": judgment(ANOTHER_COMMAND),
|
|
3652
|
+
"run-tail": judgment(ANOTHER_COMMAND),
|
|
3653
|
+
"pre-gate-cut": judgment(ANOTHER_COMMAND),
|
|
3654
|
+
"implementation-cut": judgment(ANOTHER_COMMAND),
|
|
3655
|
+
"early-heartbeat": judgment(ANOTHER_COMMAND),
|
|
3656
|
+
"poll-loop": judgment(ANOTHER_COMMAND),
|
|
3657
|
+
"lane-park": judgment(ANOTHER_COMMAND),
|
|
3658
|
+
"lane-split-park": judgment(ANOTHER_COMMAND),
|
|
3659
|
+
"lane-interactive-ask": judgment(ANOTHER_COMMAND),
|
|
3660
|
+
"agent-model": judgment("which model fits the unit is the author's call"),
|
|
3661
|
+
"lane-carry-conflict": judgment(ANOTHER_COMMAND),
|
|
3662
|
+
"lane-switch-main": judgment(ANOTHER_COMMAND),
|
|
3663
|
+
"lane-direct-tool": judgment(ANOTHER_COMMAND),
|
|
3664
|
+
"timed-rerun": judgment(ANOTHER_COMMAND),
|
|
3665
|
+
"oracle-consulted": judgment(ANOTHER_COMMAND),
|
|
3666
|
+
"test-declared": judgment(ANOTHER_COMMAND),
|
|
3667
|
+
batching: judgment(ANOTHER_COMMAND),
|
|
3668
|
+
investigation: judgment(ANOTHER_COMMAND),
|
|
3669
|
+
"duplicate-read": judgment(ANOTHER_COMMAND),
|
|
3670
|
+
"run-watcher": judgment(ANOTHER_COMMAND),
|
|
3671
|
+
"parent-cut": judgment(ANOTHER_COMMAND),
|
|
3672
|
+
"issue-watch": judgment(ANOTHER_COMMAND),
|
|
3673
|
+
"third-party-write": judgment(NO_RUN),
|
|
3674
|
+
"filing-cap": judgment(NO_RUN),
|
|
3675
|
+
"git-force": judgment(NO_RUN),
|
|
3676
|
+
"worktree-mutation": judgment(NO_RUN),
|
|
3677
|
+
"index-mutation": judgment(NO_RUN),
|
|
3678
|
+
"destructive-command": judgment(NO_RUN),
|
|
3679
|
+
"protected-file": judgment(NO_RUN),
|
|
3680
|
+
// The `Stop` rows: the hook judges a finished reply, so it has no call to rewrite.
|
|
3681
|
+
"stop-notify": judgment(STOP_NOTIFY),
|
|
3682
|
+
"issue-citation": judgment(ISSUE_CITATION),
|
|
3683
|
+
"filing-offer": judgment(FILING_OFFER),
|
|
3684
|
+
"hold-release": judgment(HOLD_RELEASE)
|
|
3685
|
+
};
|
|
3686
|
+
var FAMILY_SEPARATOR = ":";
|
|
3687
|
+
function entry_of(id) {
|
|
3688
|
+
return GUARD_REMEDIES[id] ?? GUARD_REMEDIES[id.split(FAMILY_SEPARATOR)[0] ?? ""];
|
|
3689
|
+
}
|
|
3690
|
+
function remedy_of(id) {
|
|
3691
|
+
return entry_of(id)?.remedy;
|
|
3692
|
+
}
|
|
3693
|
+
function why_of(id) {
|
|
3694
|
+
return entry_of(id)?.why;
|
|
3695
|
+
}
|
|
3696
|
+
function is_rewrite(id) {
|
|
3697
|
+
return remedy_of(id) === "rewrite";
|
|
3698
|
+
}
|
|
3699
|
+
var guard_remedy = {
|
|
3700
|
+
GUARD_REMEDIES,
|
|
3701
|
+
is_rewrite,
|
|
3702
|
+
remedy_of,
|
|
3703
|
+
why_of
|
|
3704
|
+
};
|
|
3705
|
+
|
|
3706
|
+
// scripts/rules/rule-rewrite.ts
|
|
3707
|
+
var REWRITING_RULES = delivered_rules.DELIVERED_RULES.filter(
|
|
3708
|
+
(rule) => guard_remedy.is_rewrite(rule.id) && rule.rewrite !== void 0
|
|
3709
|
+
);
|
|
3710
|
+
var NOTE_SEPARATOR = "\n\n";
|
|
3711
|
+
function read_payload_tail(raw_payload) {
|
|
3712
|
+
try {
|
|
3713
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
3714
|
+
return payload ? time_density_hook.read_tail(
|
|
3715
|
+
time_hook_transcript.transcript_of(payload.transcript_path, payload.agent_id)
|
|
3716
|
+
) : "";
|
|
3717
|
+
} catch {
|
|
3718
|
+
return "";
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
function tail_reader(raw_payload) {
|
|
3722
|
+
const cache = {};
|
|
3723
|
+
return function tail() {
|
|
3724
|
+
cache.tail ??= read_payload_tail(raw_payload);
|
|
3725
|
+
return cache.tail;
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
function fold(name, typed, tail) {
|
|
3729
|
+
const folded = { input: typed, notes: [] };
|
|
3730
|
+
for (const rule of REWRITING_RULES) {
|
|
3731
|
+
const input = rule.rewrite?.({ name, input: folded.input }, tail);
|
|
3732
|
+
if (input === void 0) {
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
folded.input = input;
|
|
3736
|
+
folded.notes = [...folded.notes, rule.rewrite_note ?? ""];
|
|
3737
|
+
}
|
|
3738
|
+
return folded;
|
|
3739
|
+
}
|
|
3740
|
+
function typed_payload(raw_payload) {
|
|
3741
|
+
const fields = json_value.parse_or_undefined(raw_payload);
|
|
3742
|
+
if (!json_value.is_record(fields)) return void 0;
|
|
3743
|
+
const input = fields["tool_input"];
|
|
3744
|
+
return json_value.is_record(input) ? { fields, name: String(fields["tool_name"]), input } : void 0;
|
|
3745
|
+
}
|
|
3746
|
+
function rewrite2(raw_payload) {
|
|
3747
|
+
const typed = delivered_rules.is_enabled() ? typed_payload(raw_payload) : void 0;
|
|
3748
|
+
if (typed === void 0) return void 0;
|
|
3749
|
+
const { input, notes } = fold(typed.name, typed.input, tail_reader(raw_payload));
|
|
3750
|
+
if (notes.length === 0) return void 0;
|
|
3751
|
+
const note = notes.join(NOTE_SEPARATOR);
|
|
3752
|
+
return { payload: JSON.stringify({ ...typed.fields, tool_input: input }), input, note };
|
|
3753
|
+
}
|
|
3754
|
+
var rule_rewrite = {
|
|
3755
|
+
NOTE_SEPARATOR,
|
|
3756
|
+
rewrite: rewrite2
|
|
3757
|
+
};
|
|
3758
|
+
|
|
3759
|
+
// scripts/run/progress/run-progress-read.ts
|
|
3760
|
+
import { loadavg } from "node:os";
|
|
3761
|
+
|
|
3762
|
+
// scripts/lane/lane-report.ts
|
|
3763
|
+
var NO_LANES2 = "No lanes are open.";
|
|
3764
|
+
var OPEN_STATE = "open";
|
|
3765
|
+
var STRANDED_STATE = "stranded";
|
|
3766
|
+
var UNREADABLE_STATE = "unreadable";
|
|
3767
|
+
var UNKNOWN_VALUE = "-";
|
|
3768
|
+
var COLUMN_SEPARATOR = " ";
|
|
3769
|
+
function lane_state(lane) {
|
|
3770
|
+
if (lane.is_stranded) return STRANDED_STATE;
|
|
3771
|
+
return lane.seat === void 0 ? UNREADABLE_STATE : OPEN_STATE;
|
|
3772
|
+
}
|
|
3773
|
+
function value_or_unknown(value) {
|
|
3774
|
+
return value === void 0 ? UNKNOWN_VALUE : String(value);
|
|
3775
|
+
}
|
|
3776
|
+
function output_column(output) {
|
|
3777
|
+
return `output ${output ?? UNKNOWN_VALUE}`;
|
|
3778
|
+
}
|
|
3779
|
+
function profile_column(lane) {
|
|
3780
|
+
return `profile ${lane.profile === void 0 ? UNKNOWN_VALUE : agent_role_profile.describe(lane.profile)}`;
|
|
3781
|
+
}
|
|
3782
|
+
function event_column(lane) {
|
|
3783
|
+
if (lane.output === void 0) return "agent -";
|
|
3784
|
+
const state = run_liveness.describe_agent_state(lane.output);
|
|
3785
|
+
return state ?? "agent -";
|
|
3786
|
+
}
|
|
3787
|
+
var PLAIN_CITE = issue_cite.citer(void 0, /* @__PURE__ */ new Map());
|
|
3788
|
+
function describe_lane(lane, cite = PLAIN_CITE) {
|
|
3789
|
+
return [
|
|
3790
|
+
cite(lane.issue),
|
|
3791
|
+
`seat ${value_or_unknown(lane.seat)}`,
|
|
3792
|
+
`dev ${value_or_unknown(lane.development_port)}`,
|
|
3793
|
+
`preview ${value_or_unknown(lane.preview_port)}`,
|
|
3794
|
+
lane.branch,
|
|
3795
|
+
lane_state(lane),
|
|
3796
|
+
lane.directory,
|
|
3797
|
+
output_column(lane.output),
|
|
3798
|
+
profile_column(lane),
|
|
3799
|
+
event_column(lane)
|
|
3800
|
+
].join(COLUMN_SEPARATOR);
|
|
3801
|
+
}
|
|
3802
|
+
function describe_lanes(lanes, cite = PLAIN_CITE) {
|
|
3803
|
+
if (lanes.length === 0) return NO_LANES2;
|
|
3804
|
+
return lanes.map((lane) => describe_lane(lane, cite)).join("\n");
|
|
3805
|
+
}
|
|
3806
|
+
function describe_ports(lane) {
|
|
3807
|
+
if (lane.seat === void 0) return "no lane seat could be read";
|
|
3808
|
+
return `seat ${String(lane.seat)}, dev ${value_or_unknown(lane.development_port)}, preview ${value_or_unknown(lane.preview_port)}`;
|
|
3809
|
+
}
|
|
3810
|
+
function describe_opened(lane) {
|
|
3811
|
+
return `Opened a lane for ${session_cite.issue(lane.issue)} on ${lane.branch}: ${describe_ports(lane)}.`;
|
|
3812
|
+
}
|
|
3813
|
+
var lane_report = {
|
|
3814
|
+
NO_LANES: NO_LANES2,
|
|
3815
|
+
OPEN_STATE,
|
|
3816
|
+
STRANDED_STATE,
|
|
3817
|
+
UNREADABLE_STATE,
|
|
3818
|
+
describe_lane,
|
|
3819
|
+
describe_lanes,
|
|
3820
|
+
describe_opened,
|
|
3821
|
+
lane_state
|
|
3822
|
+
};
|
|
3823
|
+
|
|
3824
|
+
// scripts/run/progress/run-progress-read.ts
|
|
3825
|
+
var { mark: mark2, parse_stamp: parse_stamp2, read_last_report: read_last_report2 } = run_progress_clock;
|
|
3826
|
+
var FIRST_LOAD_AVERAGE = 0;
|
|
3827
|
+
var IDLE_READ = { kind: "idle" };
|
|
3828
|
+
var UNREADABLE_READ = { kind: "unreadable" };
|
|
3829
|
+
async function worktree_git_directory() {
|
|
3830
|
+
const [git_directory] = await git_command.git_directories();
|
|
3831
|
+
return git_directory;
|
|
3832
|
+
}
|
|
3833
|
+
async function stamp_target() {
|
|
3834
|
+
return run_progress_clock.stamp_target_of(await worktree_git_directory());
|
|
3835
|
+
}
|
|
3836
|
+
async function log_target() {
|
|
3837
|
+
return run_progress_clock.log_path_of(await stamp_target());
|
|
3838
|
+
}
|
|
3839
|
+
async function live_target() {
|
|
3840
|
+
return run_progress_clock.life_target_of(await worktree_git_directory());
|
|
3841
|
+
}
|
|
3842
|
+
function to_labels(issue) {
|
|
3843
|
+
return (issue.labels ?? []).map((label) => label.name);
|
|
3844
|
+
}
|
|
3845
|
+
async function to_child(issue) {
|
|
3846
|
+
const number = String(issue.number);
|
|
3847
|
+
const child = await run_preflight.read_child_state(number);
|
|
3848
|
+
return { issue: number, title: issue.title, labels: to_labels(issue), pr_state: child.pr_state };
|
|
3849
|
+
}
|
|
3850
|
+
async function read_children(repo) {
|
|
3851
|
+
const read2 = await epic_busy.read_repository(repo);
|
|
3852
|
+
if (read2.kind === "idle") return [];
|
|
3853
|
+
if (read2.kind !== "busy") return void 0;
|
|
3854
|
+
return await Promise.all(read2.issues.map(async (issue) => await to_child(issue)));
|
|
3855
|
+
}
|
|
3856
|
+
async function read_lanes() {
|
|
3857
|
+
const lanes = await lane_registry.list_lanes();
|
|
3858
|
+
return lanes.map((lane) => ({ issue: lane.issue, state: lane_report.lane_state(lane) }));
|
|
3859
|
+
}
|
|
3860
|
+
function is_mtime(value) {
|
|
3861
|
+
return value !== void 0;
|
|
3862
|
+
}
|
|
3863
|
+
function read_record_age(paths, now_ms) {
|
|
3864
|
+
const times = paths.map((output_path) => run_liveness.sample_output(output_path)?.mtime_ms).filter(is_mtime);
|
|
3865
|
+
if (times.length === 0) return void 0;
|
|
3866
|
+
return now_ms - Math.max(...times);
|
|
3867
|
+
}
|
|
3868
|
+
function is_hold_present(read2) {
|
|
3869
|
+
return read2.kind === "held" || read2.kind === "stale";
|
|
3870
|
+
}
|
|
3871
|
+
function is_carry_present(read2) {
|
|
3872
|
+
return read2.kind === "carried";
|
|
3873
|
+
}
|
|
3874
|
+
function hold_present(worktree) {
|
|
3875
|
+
return worktree !== void 0 && is_hold_present(run_hold.read_hold(run_hold.hold_path(worktree)));
|
|
3876
|
+
}
|
|
3877
|
+
function carry_present(repository) {
|
|
3878
|
+
if (repository === void 0) return false;
|
|
3879
|
+
return is_carry_present(run_carry.read_carry(run_carry.carry_path(repository)));
|
|
3880
|
+
}
|
|
3881
|
+
async function has_run_record() {
|
|
3882
|
+
const [worktree, repository] = await git_command.git_directories();
|
|
3883
|
+
return hold_present(worktree) || carry_present(repository);
|
|
3884
|
+
}
|
|
3885
|
+
async function has_run_started(lanes) {
|
|
3886
|
+
return lanes.length > 0 || await has_run_record();
|
|
3887
|
+
}
|
|
3888
|
+
async function read_observations(request) {
|
|
3889
|
+
const [children, lanes] = await Promise.all([read_children(request.repo), read_lanes()]);
|
|
3890
|
+
if (children === void 0) return UNREADABLE_READ;
|
|
3891
|
+
if (children.length === 0 && !await has_run_started(lanes)) return IDLE_READ;
|
|
3892
|
+
const observations = {
|
|
3893
|
+
repo: request.repo,
|
|
3894
|
+
children,
|
|
3895
|
+
lanes,
|
|
3896
|
+
load_average: loadavg()[FIRST_LOAD_AVERAGE] ?? 0,
|
|
3897
|
+
record_age_ms: read_record_age(request.output_paths, request.now_ms)
|
|
3898
|
+
};
|
|
3899
|
+
return { kind: "observed", observations };
|
|
3900
|
+
}
|
|
3901
|
+
var run_progress_read = {
|
|
3902
|
+
has_run_started,
|
|
3903
|
+
live_target,
|
|
3904
|
+
log_target,
|
|
3905
|
+
mark: mark2,
|
|
3906
|
+
parse_stamp: parse_stamp2,
|
|
3907
|
+
read_children,
|
|
3908
|
+
read_last_report: read_last_report2,
|
|
3909
|
+
read_lanes,
|
|
3910
|
+
read_observations,
|
|
3911
|
+
read_record_age,
|
|
3912
|
+
stamp_target
|
|
3913
|
+
};
|
|
3914
|
+
|
|
3915
|
+
// scripts/run/run-watcher-hook.ts
|
|
3916
|
+
import { z as z5 } from "zod";
|
|
3917
|
+
|
|
3918
|
+
// scripts/run/run-watcher-guard.ts
|
|
3919
|
+
var WATCHER_TICK_MS = 3e4;
|
|
3920
|
+
var TICK_MULTIPLIER = 3;
|
|
3921
|
+
var STALE_THRESHOLD_MS = WATCHER_TICK_MS * TICK_MULTIPLIER;
|
|
3922
|
+
var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND;
|
|
3923
|
+
var STALE_NOTE = `Lane children are in-flight but the progress watcher has not pinged in the last ${String(STALE_THRESHOLD_S)}s \u2014 run \`pnpm josh run:progress --wait\` in the background before proceeding. It reports to the run event stream on its own and exits only when newly runnable work arrives, so start it once and relay nothing`;
|
|
3924
|
+
var OK_RESULT = { kind: "ok" };
|
|
3925
|
+
async function owes_watcher() {
|
|
3926
|
+
if (run_headless.is_headless() || !await run_headless.is_backlog_parent()) return false;
|
|
3927
|
+
return await lane_registry.has_lanes_in_flight();
|
|
3928
|
+
}
|
|
3929
|
+
async function check(life_target) {
|
|
3930
|
+
if (!await owes_watcher() || run_progress_clock.is_life_fresh(life_target, STALE_THRESHOLD_MS)) {
|
|
3931
|
+
return OK_RESULT;
|
|
3932
|
+
}
|
|
3933
|
+
return { kind: "stale", note: STALE_NOTE };
|
|
3934
|
+
}
|
|
3935
|
+
var run_watcher_guard = {
|
|
3936
|
+
STALE_NOTE,
|
|
3937
|
+
STALE_THRESHOLD_MS,
|
|
3938
|
+
check
|
|
3939
|
+
};
|
|
3940
|
+
|
|
3941
|
+
// scripts/run/run-watcher-hook.ts
|
|
3942
|
+
var SWITCH_ENV_KEY4 = "JOSH_WATCHER_GUARD";
|
|
3943
|
+
var STAMP_PREFIX4 = "josh-watcher-guard-";
|
|
3944
|
+
var payload_schema = z5.object({ transcript_path: z5.string().min(1) });
|
|
3945
|
+
function transcript_of(raw_payload) {
|
|
3946
|
+
return json_value.parse_with(raw_payload, payload_schema)?.transcript_path;
|
|
3947
|
+
}
|
|
3948
|
+
function fires_once(transcript, now_ms) {
|
|
3949
|
+
const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX4);
|
|
3950
|
+
const target = stamp.path(transcript);
|
|
3951
|
+
if (stamp.last_ms(target) !== hook_decision.NEVER_MS) return false;
|
|
3952
|
+
return stamp.record(target, now_ms);
|
|
3953
|
+
}
|
|
3954
|
+
function is_active() {
|
|
3955
|
+
return hook_decision.is_switch_enabled(SWITCH_ENV_KEY4) && lane_child_marker.marked_issue() === void 0;
|
|
3956
|
+
}
|
|
3957
|
+
async function stale_reason(transcript, now_ms) {
|
|
3958
|
+
const result = await run_watcher_guard.check(await run_progress_read.live_target());
|
|
3959
|
+
if (result.kind === "ok") return void 0;
|
|
3960
|
+
return fires_once(transcript, now_ms) ? result.note : void 0;
|
|
3961
|
+
}
|
|
3962
|
+
async function watcher_hook_reason(raw_payload, now_ms = Date.now()) {
|
|
3963
|
+
if (!is_active()) return void 0;
|
|
3964
|
+
const transcript = transcript_of(raw_payload);
|
|
3965
|
+
if (transcript === void 0) return void 0;
|
|
3966
|
+
return await stale_reason(transcript, now_ms);
|
|
3967
|
+
}
|
|
3968
|
+
var run_watcher_hook = { transcript_of, watcher_hook_reason };
|
|
3969
|
+
|
|
3970
|
+
// scripts/run/run-parent-cut-hook.ts
|
|
3971
|
+
var SWITCH_ENV_KEY5 = "JOSH_PARENT_CUT_GUARD";
|
|
3972
|
+
var STAMP_PREFIX5 = "josh-parent-cut-guard-";
|
|
3973
|
+
var QUIET_WINDOW_MS = 6e5;
|
|
3974
|
+
function current_state2() {
|
|
3975
|
+
return {
|
|
3976
|
+
carry: async () => await run_headless.driving_carry(),
|
|
3977
|
+
verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict())
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
var THRESHOLD_TEXT2 = `${figure_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
|
|
3981
|
+
var PARENT_CUT_REASON = `\u26D4 backlogrun parent hand-off: this session drives a \`backlogrun\` and its recent-context cost crossed the ${THRESHOLD_TEXT2} threshold between merges. Take the hand-off now: open no new lane, follow \`backlogrun-progress.md\` \u2192 "The hand-off" (\`pnpm josh run:carry --cut --owner "$PPID"\`, then \`pnpm josh run:wake\` unless \`capped\`) and end the turn. Reissue this call once done or if it cannot be taken.`;
|
|
3982
|
+
async function is_due2(state) {
|
|
3983
|
+
const carry = await state.carry();
|
|
3984
|
+
if (carry === void 0 || run_headless.is_cut_capped(carry)) return false;
|
|
3985
|
+
return state.verdict() === cost_verdict.OVER_VERDICT;
|
|
3986
|
+
}
|
|
3987
|
+
function is_quiet(target, now_ms) {
|
|
3988
|
+
const last_ms = hook_decision.create_refusal_stamp(STAMP_PREFIX5).last_ms(target);
|
|
3989
|
+
return last_ms !== hook_decision.NEVER_MS && now_ms - last_ms < QUIET_WINDOW_MS;
|
|
3990
|
+
}
|
|
3991
|
+
async function refusal_for(transcript, now_ms, state) {
|
|
3992
|
+
const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX5);
|
|
3993
|
+
const target = stamp.path(transcript);
|
|
3994
|
+
if (is_quiet(target, now_ms) || !await is_due2(state)) return void 0;
|
|
3995
|
+
return stamp.record(target, now_ms) ? PARENT_CUT_REASON : void 0;
|
|
3996
|
+
}
|
|
3997
|
+
function is_unit_call(raw_payload) {
|
|
3998
|
+
try {
|
|
3999
|
+
return (hook_decision.parse_hook_payload(raw_payload)?.agent_id ?? "") !== "";
|
|
4000
|
+
} catch (error) {
|
|
4001
|
+
error_text.trace_swallowed("run_parent_cut_hook.is_unit_call", error);
|
|
4002
|
+
return false;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
function is_exempt2(raw_payload) {
|
|
4006
|
+
return !hook_decision.is_switch_enabled(SWITCH_ENV_KEY5) || is_unit_call(raw_payload);
|
|
4007
|
+
}
|
|
4008
|
+
async function parent_cut_reason(raw_payload, now_ms = Date.now(), state = current_state2()) {
|
|
4009
|
+
if (is_exempt2(raw_payload)) return void 0;
|
|
4010
|
+
const transcript = run_watcher_hook.transcript_of(raw_payload);
|
|
4011
|
+
if (transcript === void 0) return void 0;
|
|
4012
|
+
return await refusal_for(transcript, now_ms, state);
|
|
4013
|
+
}
|
|
4014
|
+
var run_parent_cut_hook = {
|
|
4015
|
+
PARENT_CUT_REASON,
|
|
4016
|
+
QUIET_WINDOW_MS,
|
|
4017
|
+
SWITCH_ENV_KEY: SWITCH_ENV_KEY5,
|
|
4018
|
+
parent_cut_reason
|
|
4019
|
+
};
|
|
4020
|
+
|
|
4021
|
+
// scripts/run/run-poll-hook.ts
|
|
4022
|
+
var SWITCH_ENV_KEY6 = "JOSH_POLL_GUARD";
|
|
4023
|
+
var BASH_TOOL = "Bash";
|
|
4024
|
+
var STATE_READS = /* @__PURE__ */ new Set([
|
|
4025
|
+
"josh lane:list",
|
|
4026
|
+
"josh lane:output",
|
|
4027
|
+
"josh run:board",
|
|
4028
|
+
"josh run:carry",
|
|
4029
|
+
"josh run:liveness",
|
|
4030
|
+
"josh run:progress",
|
|
4031
|
+
"josh run:wake"
|
|
4032
|
+
]);
|
|
4033
|
+
var POLL_REASON = '\u26D4 poll: this `backlogrun` session repeats a state read with nothing decided in between, on every occurrence. Wait on `pnpm josh backlog:drive`, or `pnpm josh lane:await <N...> --owner "$PPID"` in the background, and end the turn; its exit is the wake.';
|
|
4034
|
+
var WRITING_FLAGS = /* @__PURE__ */ new Set([
|
|
4035
|
+
"--begin",
|
|
4036
|
+
"--cut",
|
|
4037
|
+
"--done",
|
|
4038
|
+
"--end",
|
|
4039
|
+
"--every",
|
|
4040
|
+
"--filed",
|
|
4041
|
+
"--loop",
|
|
4042
|
+
"--mark",
|
|
4043
|
+
"--merged",
|
|
4044
|
+
"--resume",
|
|
4045
|
+
"--retrospective",
|
|
4046
|
+
"--start",
|
|
4047
|
+
"--stop",
|
|
4048
|
+
"--wait"
|
|
4049
|
+
]);
|
|
4050
|
+
var FLAG_PREFIX3 = "-";
|
|
4051
|
+
var NAME_WORDS = 2;
|
|
4052
|
+
var SIGNATURE_SEPARATOR = " ";
|
|
4053
|
+
function read_key(signature) {
|
|
4054
|
+
const words = signature.split(SIGNATURE_SEPARATOR);
|
|
4055
|
+
const name = words.slice(0, NAME_WORDS).join(SIGNATURE_SEPARATOR);
|
|
4056
|
+
if (!STATE_READS.has(name) || words.some((word) => WRITING_FLAGS.has(word))) return void 0;
|
|
4057
|
+
return words.filter((word) => !word.startsWith(FLAG_PREFIX3)).join(SIGNATURE_SEPARATOR);
|
|
4058
|
+
}
|
|
4059
|
+
function read_keys(signatures) {
|
|
4060
|
+
return signatures.map((signature) => read_key(signature)).filter((key) => key !== void 0);
|
|
4061
|
+
}
|
|
4062
|
+
function state_reads_of(command) {
|
|
4063
|
+
return read_keys(time_shell.josh_signatures_of(command));
|
|
4064
|
+
}
|
|
4065
|
+
function command_of(input) {
|
|
4066
|
+
if (typeof input !== "object" || input === null || !("command" in input)) return "";
|
|
4067
|
+
return typeof input.command === "string" ? input.command : "";
|
|
4068
|
+
}
|
|
4069
|
+
function candidate_reads(raw_payload) {
|
|
4070
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4071
|
+
if (payload?.tool_name !== BASH_TOOL || (payload.agent_id ?? "") !== "") return [];
|
|
4072
|
+
return state_reads_of(command_of(payload.tool_input));
|
|
4073
|
+
}
|
|
4074
|
+
function last_step(tail) {
|
|
4075
|
+
return time_spans.parse_timeline(tail).spans.findLast((span) => span.category !== time_spans.MODEL_CATEGORY);
|
|
4076
|
+
}
|
|
4077
|
+
function repeats_last_call(tail, reads) {
|
|
4078
|
+
const last = last_step(tail);
|
|
4079
|
+
if (last?.category !== time_spans.TOOL_CATEGORY) return false;
|
|
4080
|
+
const last_reads = read_keys(last.josh_signatures);
|
|
4081
|
+
return reads.some((key) => last_reads.includes(key));
|
|
4082
|
+
}
|
|
4083
|
+
function is_repeat(raw_payload) {
|
|
4084
|
+
const reads = hook_decision.is_switch_enabled(SWITCH_ENV_KEY6) ? candidate_reads(raw_payload) : [];
|
|
4085
|
+
const transcript = reads.length === 0 ? void 0 : run_watcher_hook.transcript_of(raw_payload);
|
|
4086
|
+
if (transcript === void 0) return false;
|
|
4087
|
+
return repeats_last_call(time_density_hook.read_tail(transcript), reads);
|
|
4088
|
+
}
|
|
4089
|
+
async function poll_reason(raw_payload, is_parent = run_headless.is_backlog_parent) {
|
|
4090
|
+
if (!is_repeat(raw_payload)) return void 0;
|
|
4091
|
+
return await is_parent() ? POLL_REASON : void 0;
|
|
4092
|
+
}
|
|
4093
|
+
async function safe_poll_reason(raw_payload) {
|
|
4094
|
+
try {
|
|
4095
|
+
return await poll_reason(raw_payload);
|
|
4096
|
+
} catch (error) {
|
|
4097
|
+
error_text.trace_swallowed("run_poll_hook.poll_reason", error);
|
|
4098
|
+
return void 0;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
var run_poll_hook = {
|
|
4102
|
+
POLL_REASON,
|
|
4103
|
+
SWITCH_ENV_KEY: SWITCH_ENV_KEY6,
|
|
4104
|
+
poll_reason,
|
|
4105
|
+
safe_poll_reason
|
|
4106
|
+
};
|
|
4107
|
+
|
|
4108
|
+
// scripts/hooks/batch-guard.ts
|
|
4109
|
+
var SWITCH_ENV_KEY7 = "JOSH_BATCH_GUARD";
|
|
4110
|
+
function is_batch_candidate(call) {
|
|
4111
|
+
return time_batch_guard.is_guarded_call(call) || time_batch_guard.is_notice_call(call);
|
|
4112
|
+
}
|
|
4113
|
+
function batching_mode() {
|
|
4114
|
+
return lane_guard_policy.mode_here("batching");
|
|
4115
|
+
}
|
|
4116
|
+
function is_candidate3(call) {
|
|
4117
|
+
return is_batch_candidate(call) && batching_mode() !== "off";
|
|
4118
|
+
}
|
|
4119
|
+
function should_block_here2(tail, call, refused_at_ms) {
|
|
4120
|
+
if (batching_mode() !== "refuse") return false;
|
|
4121
|
+
return time_batch_guard.should_block(tail, call, refused_at_ms);
|
|
4122
|
+
}
|
|
4123
|
+
function should_notify_here2(tail, call, notified_at_ms) {
|
|
4124
|
+
if (time_batch_guard.should_notify(tail, call, notified_at_ms)) return true;
|
|
4125
|
+
if (batching_mode() !== "notice") return false;
|
|
4126
|
+
return time_batch_guard.should_block(
|
|
4127
|
+
tail,
|
|
4128
|
+
call,
|
|
4129
|
+
notified_at_ms,
|
|
4130
|
+
time_batch_guard.NOTICE_REFIRE_EVERY
|
|
4131
|
+
);
|
|
4132
|
+
}
|
|
4133
|
+
function notice_text(call, tail) {
|
|
4134
|
+
const base = time_batch_guard.is_notice_call(call) ? time_batch_guard.NOTICE : time_batch_guard.LANE_NOTICE;
|
|
4135
|
+
return `${base}${time_batch_guard.recent_candidates(tail)}`;
|
|
4136
|
+
}
|
|
4137
|
+
var GUARD3 = hook_decision.create_transcript_guard({
|
|
4138
|
+
prefix: time_batch_guard.STAMP_PREFIX,
|
|
4139
|
+
switch_key: SWITCH_ENV_KEY7,
|
|
4140
|
+
is_candidate: is_candidate3,
|
|
4141
|
+
should_block: should_block_here2,
|
|
4142
|
+
reason: time_batch_guard.REASON,
|
|
4143
|
+
// The whole-file write is notified rather than refused, on a record of its own so it never spends
|
|
4144
|
+
// the refusal's stamp; the lane-child refusable-call notice kit#2164 added
|
|
4145
|
+
// here fires again since kit#2276 restored batching to `notice` in a lane child.
|
|
4146
|
+
notify: {
|
|
4147
|
+
prefix: time_batch_guard.NOTICE_STAMP_PREFIX,
|
|
4148
|
+
should_notify: should_notify_here2,
|
|
4149
|
+
text: notice_text
|
|
4150
|
+
}
|
|
4151
|
+
});
|
|
4152
|
+
var batch_refusal = GUARD3.refusal;
|
|
4153
|
+
var batch_outcome = GUARD3.outcome;
|
|
4154
|
+
var { is_enabled: is_enabled4, refusal_path: refusal_path3 } = GUARD3;
|
|
4155
|
+
var notice_refusal_path2 = hook_decision.create_refusal_stamp(
|
|
4156
|
+
time_batch_guard.NOTICE_STAMP_PREFIX
|
|
4157
|
+
).path;
|
|
4158
|
+
var { deny_envelope: deny_envelope3, load_environment_file: load_environment_file2, DISABLED_VALUES: DISABLED_VALUES3 } = hook_decision;
|
|
4159
|
+
|
|
4160
|
+
// scripts/hooks/shell-rules-notice.ts
|
|
4161
|
+
var STAMP_PREFIX6 = "josh-shell-rules-notice-";
|
|
4162
|
+
var NOTICE2 = "Shell rules: change an existing file with Edit or Write, never a script or heredoc that writes it (file-edits.md); run a josh check with no pipe or under `set -o pipefail`, never `| head` (output-bounds.md).";
|
|
4163
|
+
var STAMP = hook_decision.create_refusal_stamp(STAMP_PREFIX6);
|
|
4164
|
+
function notice(raw_payload) {
|
|
4165
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4166
|
+
if (payload?.tool_name !== cost_blocks.BASH_TOOL) return void 0;
|
|
4167
|
+
const target = STAMP.path(payload.transcript_path);
|
|
4168
|
+
if (STAMP.last_ms(target) !== hook_decision.NEVER_MS || !STAMP.record(target, Date.now())) {
|
|
4169
|
+
return void 0;
|
|
4170
|
+
}
|
|
4171
|
+
return NOTICE2;
|
|
4172
|
+
}
|
|
4173
|
+
var shell_rules_notice = { NOTICE: NOTICE2, notice, stamp_path: STAMP.path };
|
|
4174
|
+
|
|
4175
|
+
// scripts/hooks/step-zero-notice.ts
|
|
4176
|
+
import path4 from "node:path";
|
|
4177
|
+
var STAMP_PREFIX7 = "josh-step-zero-notice-";
|
|
4178
|
+
var EDIT_TOOLS3 = /* @__PURE__ */ new Set(["Edit", "Write"]);
|
|
4179
|
+
var PARENT_PREFIX = "..";
|
|
4180
|
+
var NOTICE3 = "Before writing any implementation code: present the two-layer work summary (Now / Change / Check, then every change with its test) once per Issue. Full rule: Code Change Rules Step 0 in CLAUDE.md.";
|
|
4181
|
+
var STAMP2 = hook_decision.create_refusal_stamp(STAMP_PREFIX7);
|
|
4182
|
+
function edited_paths(tool_input) {
|
|
4183
|
+
if (!json_value.is_record(tool_input)) return [];
|
|
4184
|
+
const { file_path, file_paths } = tool_input;
|
|
4185
|
+
const listed = Array.isArray(file_paths) ? file_paths : [];
|
|
4186
|
+
return [file_path, ...listed].filter(
|
|
4187
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
function is_runtime_file(file_path, root) {
|
|
4191
|
+
const relative = path4.relative(root, path4.resolve(root, file_path));
|
|
4192
|
+
if (relative.startsWith(PARENT_PREFIX) || path4.isAbsolute(relative)) return false;
|
|
4193
|
+
return !test_declared_logic.is_exempt(relative.split(path4.sep).join("/"));
|
|
4194
|
+
}
|
|
4195
|
+
function is_candidate4(raw_payload, root) {
|
|
4196
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4197
|
+
if (payload === void 0 || !EDIT_TOOLS3.has(payload.tool_name)) return void 0;
|
|
4198
|
+
const has_runtime_file = edited_paths(payload.tool_input).some(
|
|
4199
|
+
(file_path) => is_runtime_file(file_path, root)
|
|
4200
|
+
);
|
|
4201
|
+
return has_runtime_file ? payload.transcript_path : void 0;
|
|
4202
|
+
}
|
|
4203
|
+
function notice2(raw_payload, root = process.cwd()) {
|
|
4204
|
+
const transcript = is_candidate4(raw_payload, root);
|
|
4205
|
+
if (transcript === void 0) return void 0;
|
|
4206
|
+
const target = STAMP2.path(transcript);
|
|
4207
|
+
if (STAMP2.last_ms(target) !== hook_decision.NEVER_MS || !STAMP2.record(target, Date.now())) {
|
|
4208
|
+
return void 0;
|
|
4209
|
+
}
|
|
4210
|
+
return NOTICE3;
|
|
4211
|
+
}
|
|
4212
|
+
var step_zero_notice = { NOTICE: NOTICE3, notice: notice2, stamp_path: STAMP2.path };
|
|
4213
|
+
|
|
4214
|
+
// scripts/hooks/pretool-guard.ts
|
|
4215
|
+
var CLEAR = { reason: void 0, notice: void 0, fault: void 0 };
|
|
4216
|
+
function is_clear(outcome) {
|
|
4217
|
+
return outcome.reason === void 0 && outcome.notice === void 0 && outcome.fault === void 0;
|
|
4218
|
+
}
|
|
4219
|
+
function combine_outcomes(outcomes, rule_reason) {
|
|
4220
|
+
const reason = outcomes.find((outcome) => outcome.reason !== void 0)?.reason ?? rule_reason;
|
|
4221
|
+
if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
|
|
4222
|
+
return outcomes.find((outcome) => !is_clear(outcome)) ?? CLEAR;
|
|
4223
|
+
}
|
|
4224
|
+
function with_session_notice(outcome, raw_payload) {
|
|
4225
|
+
if (!is_clear(outcome)) return outcome;
|
|
4226
|
+
const notice3 = step_zero_notice.notice(raw_payload) ?? shell_rules_notice.notice(raw_payload);
|
|
4227
|
+
return notice3 === void 0 ? outcome : { reason: void 0, notice: notice3, fault: void 0 };
|
|
4228
|
+
}
|
|
4229
|
+
function guard_outcome(raw_payload) {
|
|
4230
|
+
const outcomes = [
|
|
4231
|
+
batch_outcome(raw_payload),
|
|
4232
|
+
investigation_outcome(raw_payload),
|
|
4233
|
+
duplicate_read_outcome(raw_payload)
|
|
4234
|
+
];
|
|
4235
|
+
return combine_outcomes(outcomes, delivered_rules.delivery(raw_payload));
|
|
4236
|
+
}
|
|
4237
|
+
function pretool_outcome(raw_payload) {
|
|
4238
|
+
return with_session_notice(guard_outcome(raw_payload), raw_payload);
|
|
4239
|
+
}
|
|
4240
|
+
async function mark_phase(outcome) {
|
|
4241
|
+
if (outcome.notice === step_zero_notice.NOTICE) await lane_phase.mark_implement();
|
|
4242
|
+
}
|
|
4243
|
+
async function async_reason(raw_payload) {
|
|
4244
|
+
const watcher = await run_watcher_hook.watcher_hook_reason(raw_payload);
|
|
4245
|
+
const cut = watcher ?? await run_parent_cut_hook.parent_cut_reason(raw_payload);
|
|
4246
|
+
const poll = cut ?? await run_poll_hook.safe_poll_reason(raw_payload);
|
|
4247
|
+
return poll ?? await issue_watch_hook.watch_reason();
|
|
4248
|
+
}
|
|
4249
|
+
async function pretool_outcome_async(raw_payload) {
|
|
4250
|
+
const base = guard_outcome(raw_payload);
|
|
4251
|
+
if (!is_clear(base)) return base;
|
|
4252
|
+
const reason = await async_reason(raw_payload);
|
|
4253
|
+
if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
|
|
4254
|
+
const outcome = with_session_notice(base, raw_payload);
|
|
4255
|
+
await mark_phase(outcome);
|
|
4256
|
+
return outcome;
|
|
4257
|
+
}
|
|
4258
|
+
function guarded_call(raw_payload) {
|
|
4259
|
+
try {
|
|
4260
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4261
|
+
return payload === void 0 ? void 0 : { name: payload.tool_name, input: payload.tool_input };
|
|
4262
|
+
} catch {
|
|
4263
|
+
return void 0;
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
function rewrite_context(note, outcome) {
|
|
4267
|
+
return [note, outcome.notice ?? outcome.fault].filter(Boolean).join("\n\n");
|
|
4268
|
+
}
|
|
4269
|
+
function foreground_input_of(raw_payload, is_lane_child) {
|
|
4270
|
+
const call = guarded_call(raw_payload);
|
|
4271
|
+
return call === void 0 ? void 0 : lane_background.foreground_input(call, is_lane_child);
|
|
4272
|
+
}
|
|
4273
|
+
function foreground_rewrite(outcome, raw_payload, is_lane_child, rewrite3) {
|
|
4274
|
+
const input = outcome.reason === void 0 ? foreground_input_of(raw_payload, is_lane_child) : void 0;
|
|
4275
|
+
if (input === void 0) return void 0;
|
|
4276
|
+
const note = [rewrite3?.note, lane_background.FOREGROUND_NOTE].filter(Boolean).join("\n\n");
|
|
4277
|
+
const envelope_of = rewrite3 === void 0 ? hook_decision.rewrite_envelope : hook_decision.flow_rewrite_envelope;
|
|
4278
|
+
return envelope_of(input, rewrite_context(note, outcome));
|
|
4279
|
+
}
|
|
4280
|
+
function rule_envelope(outcome, rewrite3) {
|
|
4281
|
+
if (rewrite3 === void 0 || outcome.reason !== void 0) return void 0;
|
|
4282
|
+
return hook_decision.flow_rewrite_envelope(rewrite3.input, rewrite_context(rewrite3.note, outcome));
|
|
4283
|
+
}
|
|
4284
|
+
function emit(raw_payload, outcome, rewrite3) {
|
|
4285
|
+
const envelope = foreground_rewrite(outcome, raw_payload, void 0, rewrite3) ?? rule_envelope(outcome, rewrite3);
|
|
4286
|
+
if (envelope === void 0) hook_decision.emit_outcome(outcome);
|
|
4287
|
+
else process.stdout.write(`${envelope}
|
|
4288
|
+
`);
|
|
4289
|
+
}
|
|
4290
|
+
async function respond(raw_payload) {
|
|
4291
|
+
const rewrite3 = rule_rewrite.rewrite(raw_payload);
|
|
4292
|
+
const judged = rewrite3?.payload ?? raw_payload;
|
|
4293
|
+
emit(judged, await pretool_outcome_async(judged), rewrite3);
|
|
4294
|
+
}
|
|
4295
|
+
var pretool_guard = {
|
|
4296
|
+
combine_outcomes,
|
|
4297
|
+
emit,
|
|
4298
|
+
foreground_rewrite,
|
|
4299
|
+
mark_phase,
|
|
4300
|
+
pretool_outcome,
|
|
4301
|
+
respond
|
|
4302
|
+
};
|
|
4303
|
+
|
|
4304
|
+
export {
|
|
4305
|
+
pretool_guard
|
|
4306
|
+
};
|