@joshuafolkken/kit 1.1166.0 → 1.1229.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/settings.json +2 -2
- package/.claude/skills/dependency-update/SKILL.md +4 -8
- package/.claude/skills/epic-commands/SKILL.md +2 -2
- package/.claude/skills/epic-commands/epic-bundle.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -5
- package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
- package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
- package/.claude/skills/workflow-commands/chain-rule.md +1 -1
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +5 -0
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
- package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
- package/.claude/skills/workflow-commands/prerequisite.md +2 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
- package/.codex/hooks.json +2 -2
- package/.cursorrules +6 -41
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/auto-tag.yml +3 -4
- package/.github/workflows/ci.yml +47 -33
- package/.github/workflows/dependabot-auto-merge.yml +13 -6
- package/.github/workflows/publish.yml +48 -4
- package/.github/workflows/sonar-qube.yml +3 -0
- package/.pnpmfile.mjs +4 -3
- package/.prettierignore +8 -2
- package/CLAUDE.md +18 -20
- package/README.md +26 -19
- package/SECURITY.md +15 -1
- package/dist/CLAUDE.md +18 -20
- package/dist/commands/document-section-cli.js +734 -0
- package/dist/hooks/audit-provision.js +346 -0
- package/dist/hooks/batch-guard.js +5 -4
- package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
- package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
- package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
- package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
- package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
- package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
- package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
- package/dist/hooks/codex-hook-adapter.js +14 -12
- package/dist/hooks/duplicate-read-guard.js +6 -5
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/inputs.json +232 -210
- package/dist/hooks/investigation-guard.js +6 -5
- package/dist/hooks/pretool-guard.js +12 -10
- package/dist/hooks/session-lang.js +2 -4
- package/dist/hooks/stop-guard.js +81 -59
- package/dist/josh.js +872 -75
- package/dist/version/index.d.ts +7 -2
- package/dist/version/index.js +152 -75
- package/eslint/base.js +23 -1
- package/eslint/rules/code-quality.js +3 -0
- package/eslint/rules/early-return-one-liner.js +98 -0
- package/eslint/rules/unicorn.js +16 -1
- package/package.json +7 -6
- package/pnpm-workspace.yaml +9 -4
- package/prettier/basic.d.ts +3 -1
- package/prettier/index.d.ts +3 -1
- package/prettier/static.d.ts +3 -1
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +4 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/operating-rules.md +17 -9
- package/prompts/collaboration-workflow/overview.md +2 -2
- package/prompts/collaboration-workflow/principles.md +14 -12
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +6 -6
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +1 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
- package/prompts/collaboration-workflow/wip-cap.md +10 -10
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +1 -1
- package/scripts/agent/agent-headless.ts +29 -0
- package/scripts/agent/agent-launch-environment.ts +6 -2
- package/scripts/agent/codex-agent-argv.ts +2 -1
- package/scripts/auto-ok/auto-ok-cli.ts +1 -1
- package/scripts/backlog/backlog-arrival.ts +3 -3
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-args.ts +135 -0
- package/scripts/backlog/backlog-drive-cli.ts +11 -137
- package/scripts/backlog/backlog-drive-launch.ts +39 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
- package/scripts/backlog/backlog-drive-owner.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +1 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
- package/scripts/backlog/backlog-drive.ts +1 -1
- package/scripts/backlog/backlog-next.ts +31 -9
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +7 -6
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-rank.ts +18 -9
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-restructure.ts +152 -0
- package/scripts/backlog/backlog-scope.ts +1 -1
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +1 -1
- package/scripts/backlog/backlog-waves.ts +15 -17
- package/scripts/build/build-codex-hooks.ts +3 -1
- package/scripts/build/build-commands.ts +66 -0
- package/scripts/build/build-hooks.ts +2 -1
- package/scripts/build/build-library.ts +12 -3
- package/scripts/build/build.ts +70 -0
- package/scripts/build/local-bin.ts +3 -13
- package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
- package/scripts/cost-runtime/cost-verdict.ts +15 -28
- package/scripts/delegation/investigation-reads.ts +3 -1
- package/scripts/discovery/repo-origin.ts +5 -5
- package/scripts/doctor/doctor-consumer.ts +2 -2
- package/scripts/doctor/doctor-io.ts +2 -3
- package/scripts/document/capped-print-part.ts +30 -0
- package/scripts/document/capped-print.ts +3 -4
- package/scripts/document/document-byte-budget.ts +11 -2
- package/scripts/document/document-scan.ts +117 -0
- package/scripts/document/document-section.ts +143 -16
- package/scripts/document/entry-read-budget.ts +5 -3
- package/scripts/document/entry-read-set.ts +10 -89
- package/scripts/document/read-set-cost.ts +98 -0
- package/scripts/epic/epic-add-body.ts +1 -0
- package/scripts/epic/epic-audit-checks.ts +1 -1
- package/scripts/epic/epic-audit-rationale.ts +4 -3
- package/scripts/epic/epic-bundle-cli.ts +14 -3
- package/scripts/epic/epic-bundle-poll.ts +41 -0
- package/scripts/epic/epic-busy.ts +1 -1
- package/scripts/epic/epic-candidate-confirm.ts +3 -2
- package/scripts/epic/epic-classify.ts +1 -1
- package/scripts/epic/epic-cli-argv.ts +78 -46
- package/scripts/epic/epic-cli-remove.ts +11 -19
- package/scripts/epic/epic-cli.ts +74 -73
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +1 -1
- package/scripts/epic/epic-cross-repo.ts +5 -5
- package/scripts/epic/epic-graph.ts +1 -1
- package/scripts/epic/epic-index.ts +1 -1
- package/scripts/epic/epic-issue.ts +2 -6
- package/scripts/epic/epic-lane-offer.ts +2 -2
- package/scripts/epic/epic-next.ts +29 -42
- package/scripts/epic/epic-solo-stale.ts +1 -1
- package/scripts/epic/epic-triage.ts +8 -4
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/followup/git-followup-finish.ts +2 -2
- package/scripts/followup/git-followup-label.ts +2 -2
- package/scripts/followup/git-followup-merged.ts +2 -2
- package/scripts/followup/git-followup-stages.ts +9 -0
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +5 -130
- package/scripts/gate/core-budget.ts +69 -1
- package/scripts/gate/gate-plan.ts +47 -7
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- package/scripts/gate/pre-commit-type-check.ts +4 -1
- package/scripts/gate/project-checks.ts +6 -9
- package/scripts/gate/type-check-step.ts +2 -3
- package/scripts/gate/verification-gate.ts +27 -8
- package/scripts/gh/git-gh-api-path.ts +2 -2
- package/scripts/gh/git-gh-issue-rest.ts +3 -3
- package/scripts/gh/git-gh-issue-write.ts +53 -7
- package/scripts/gh/git-gh-issue.ts +5 -3
- package/scripts/gh/git-gh-pr-read.ts +68 -19
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr.ts +5 -1
- package/scripts/gh/git-pr-ai-review.ts +1 -1
- package/scripts/gh/git-pr-checks-eval.ts +9 -3
- package/scripts/gh/git-pr-checks-parse.ts +1 -1
- package/scripts/gh/git-pr-checks-watch.ts +6 -5
- package/scripts/gh/git-pr-checks.ts +18 -6
- package/scripts/gh/git-pr-coderabbit.ts +1 -1
- package/scripts/gh/git-pr-error.ts +1 -3
- package/scripts/gh/git-pr.ts +9 -13
- package/scripts/gh/github-issue-url.ts +2 -2
- package/scripts/git/constants.ts +12 -6
- package/scripts/git/git-command.ts +20 -218
- package/scripts/git/git-diff-reads.ts +225 -0
- package/scripts/git/git-error.ts +4 -12
- package/scripts/git/git-pre-push-hook.ts +139 -0
- package/scripts/git/git-prompt.ts +2 -6
- package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
- package/scripts/git/git-spawn-sync.ts +3 -1
- package/scripts/git/git-spawn.ts +1 -1
- package/scripts/git/git-staging.ts +4 -6
- package/scripts/git/git-status.ts +3 -9
- package/scripts/git/main-merge.ts +57 -21
- package/scripts/hooks/codex-hook-adapter.ts +1 -1
- package/scripts/hooks/edited-cspell.ts +2 -4
- package/scripts/hooks/format-edited-file.ts +3 -12
- package/scripts/hooks/pre-push-unit.ts +31 -9
- package/scripts/hooks/reserved-run.ts +2 -2
- package/scripts/hooks/run-hook.sh +5 -4
- package/scripts/hooks/stop-guard.ts +5 -3
- package/scripts/init/init-actions.ts +0 -5
- package/scripts/init/init-ai-copy.ts +13 -2
- package/scripts/init/init-logic-json-merge.ts +19 -0
- package/scripts/init/init-logic-secretlint.ts +7 -7
- package/scripts/init/init-logic-workspace.ts +41 -2
- package/scripts/init/init-logic.ts +16 -26
- package/scripts/init/init.ts +5 -15
- package/scripts/init/initial-commit.ts +4 -4
- package/scripts/init/kit-development-versions.ts +40 -0
- package/scripts/init/kit-setup-state.ts +2 -2
- package/scripts/init/start-exec.ts +68 -19
- package/scripts/init/start-setup-pr.ts +9 -9
- package/scripts/init/start-steps.ts +6 -10
- package/scripts/issue/defect-rate-cli.ts +1 -1
- package/scripts/issue/git-next-issues.ts +1 -1
- package/scripts/issue/issue-auto-ok.ts +1 -1
- package/scripts/issue/issue-backlinks-cli.ts +2 -2
- package/scripts/issue/issue-backlinks.ts +9 -9
- package/scripts/issue/issue-comment-cli.ts +19 -13
- package/scripts/issue/issue-file-cli.ts +45 -18
- package/scripts/issue/issue-file.ts +24 -1
- package/scripts/issue/issue-scout-closed.ts +1 -1
- package/scripts/issue/issue-state-cli.ts +30 -74
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +11 -0
- package/scripts/josh/josh-commands-ai.ts +4 -4
- package/scripts/josh/josh-commands-backlog.ts +4 -1
- package/scripts/josh/josh-commands-development.ts +11 -1
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-lane.ts +22 -0
- package/scripts/josh/josh-commands-maintenance.ts +4 -4
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +33 -15
- package/scripts/josh/process-identity.ts +1 -3
- package/scripts/josh/session-language-cli.ts +3 -4
- package/scripts/lane/lane-await.ts +5 -1
- package/scripts/lane/lane-child-invocation.ts +2 -2
- package/scripts/lane/lane-cli.ts +3 -3
- package/scripts/lane/lane-dispatch.ts +14 -10
- package/scripts/lane/lane-environment.ts +5 -1
- package/scripts/lane/lane-handoff.ts +1 -1
- package/scripts/lane/lane-install.ts +2 -13
- package/scripts/lane/lane-launch-cli.ts +35 -3
- package/scripts/lane/lane-ledger.ts +145 -0
- package/scripts/lane/lane-leftover.ts +1 -1
- package/scripts/lane/lane-load.ts +108 -0
- package/scripts/lane/lane-measure-cli.ts +149 -0
- package/scripts/lane/lane-occupancy.ts +3 -0
- package/scripts/lane/lane-reap.ts +1 -3
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -0
- package/scripts/lane/lane-vacant.ts +36 -0
- package/scripts/lane/openai-lane-ship.ts +2 -2
- package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
- package/scripts/lane/openai-lane-supervisor.ts +7 -165
- package/scripts/lane/openai-review-broker.ts +2 -4
- package/scripts/lib/ancestor-directories.ts +35 -0
- package/scripts/lib/animation-helpers.ts +2 -2
- package/scripts/lib/buffered-process.ts +3 -7
- package/scripts/lib/cli-flags.ts +71 -1
- package/scripts/lib/spawn-exit.ts +2 -2
- package/scripts/lib/timeouts.ts +35 -2
- package/scripts/lines/line-budget.ts +4 -6
- package/scripts/notify/telegram-notify.ts +26 -1
- package/scripts/notify/telegram-test.ts +6 -0
- package/scripts/observations/observation-ledger-line.ts +6 -6
- package/scripts/observations/observation-ledger-migrate.ts +55 -4
- package/scripts/observations/observation-ledger.ts +26 -7
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/propagate/propagate-git.ts +1 -1
- package/scripts/propagate/propagate-publish.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +20 -15
- package/scripts/propagate/propagate.ts +36 -27
- package/scripts/refactor/import-graph.ts +18 -1
- package/scripts/refactor/refactor-lint.ts +4 -4
- package/scripts/repo/repository-labels.ts +2 -2
- package/scripts/repo/required-checks-logic.ts +1 -3
- package/scripts/review/live-evidence.ts +2 -4
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-findings-cli.ts +1 -3
- package/scripts/rules/decision-oracle-batch.ts +128 -0
- package/scripts/rules/decision-oracle-tokens.ts +21 -0
- package/scripts/rules/decision-oracle.ts +8 -120
- package/scripts/rules/delivered-rules.ts +26 -182
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/early-heartbeat.ts +3 -3
- package/scripts/rules/git-argv.ts +1 -1
- package/scripts/rules/git-force.ts +1 -1
- package/scripts/rules/implementation-cut.ts +2 -2
- package/scripts/rules/index-guard.ts +1 -1
- package/scripts/rules/issue-comments.ts +118 -0
- package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
- package/scripts/rules/lane-background.ts +2 -2
- package/scripts/rules/lane-park.ts +18 -0
- package/scripts/rules/lane-split-park.ts +128 -0
- package/scripts/rules/oracle-firing.ts +1 -1
- package/scripts/rules/poll-loop.ts +1 -1
- package/scripts/rules/pre-gate-cut.ts +3 -3
- package/scripts/rules/protected-files.ts +59 -4
- package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
- package/scripts/rules/shell-body-trigger.ts +1 -1
- package/scripts/rules/stop-rules.ts +18 -7
- package/scripts/rules/test-declared-commit.ts +4 -4
- package/scripts/rules/wip-cap.ts +63 -0
- package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
- package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
- package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
- package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
- package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
- package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
- package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
- package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
- package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
- package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
- package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
- package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
- package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
- package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
- package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
- package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
- package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
- package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
- package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
- package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
- package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
- package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
- package/scripts/run/run-ending.ts +2 -2
- package/scripts/run/run-halfrun-resume.ts +6 -1
- package/scripts/run/run-headless.ts +1 -1
- package/scripts/run/run-invocation.ts +85 -84
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +1 -1
- package/scripts/run/run-preflight.ts +6 -6
- package/scripts/run/run-prep-cli.ts +1 -1
- package/scripts/run/run-prrun-resume.ts +1 -1
- package/scripts/run/run-report-cli.ts +4 -4
- package/scripts/run/run-report.ts +2 -2
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +3 -3
- package/scripts/run/run-step.ts +2 -2
- package/scripts/run/run-stop-notify.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +4 -4
- package/scripts/run/run-stranded.ts +2 -2
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +5 -3
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
- package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
- package/scripts/run/ship/run-ship-lock.ts +50 -0
- package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
- package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
- package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
- package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
- package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
- package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
- package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/ship/run-ship-sync.ts +128 -0
- package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
- package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
- package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
- package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
- package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
- package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
- package/scripts/run/wake/run-wake-restart.ts +58 -0
- package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
- package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
- package/scripts/safe-chain/preinstall-command.ts +20 -21
- package/scripts/safe-chain/project-config.ts +19 -15
- package/scripts/sync/sync-ai-files.ts +1 -1
- package/scripts/sync/sync-configs.ts +2 -1
- package/scripts/sync/sync.ts +15 -2
- package/scripts/test/test-red-logic.ts +17 -2
- package/scripts/test/test-red.ts +8 -7
- package/scripts/test/unit-worker-share.ts +1 -0
- package/scripts/time-runtime/time-background.ts +5 -1
- package/scripts/time-runtime/time-instant.ts +3 -1
- package/scripts/time-runtime/time-spans.ts +6 -139
- package/scripts/time-runtime/time-tool-call.ts +160 -0
- package/scripts/time-runtime/time-transcript-line.ts +27 -1
- package/scripts/version/effective-upstream.ts +2 -20
- package/scripts/version/latest-corepack.ts +17 -17
- package/scripts/version/latest-regression.ts +1 -1
- package/scripts/version/latest-update.ts +4 -4
- package/scripts/version/npm-registry.ts +20 -5
- package/scripts/version/publishable-range-check.ts +2 -2
- package/scripts/version/publishable-range.ts +1 -1
- package/scripts/version/release-age.ts +48 -38
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +40 -0
- package/scripts/version/upgrade-shell-command.ts +48 -8
- package/scripts/version/version-check-logic.ts +23 -14
- package/scripts/version/version-commands.ts +23 -11
- package/scripts/version/version-remote.ts +28 -13
- package/templates/pnpm-workspace.basic.yaml +4 -0
- package/templates/workflows/dependabot-auto-merge.yml +5 -5
- package/templates/workflows/github-release.yml +2 -0
- package/scripts/build/build-config-merge.ts +0 -27
- package/scripts/build/build-managed-marker.ts +0 -27
- package/scripts/build/build-self-sync-guard.ts +0 -27
- package/scripts/build/build-version.ts +0 -26
- package/scripts/version/preinstall-version-update.ts +0 -86
- /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
- /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
- /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
- /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
- /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
- /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
- /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
- /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
- /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
- /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
- /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
- /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
- /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
- /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
- /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
- /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
- /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
- /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
- /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
- /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
duplicate_read_outcome
|
|
3
|
+
} from "./chunk-TQNPDZNH.js";
|
|
1
4
|
import {
|
|
2
5
|
ALREADY_DONE_LABEL,
|
|
3
6
|
CONTEXT_CUT_THRESHOLD,
|
|
@@ -5,11 +8,9 @@ import {
|
|
|
5
8
|
NEEDS_DECISION_LABEL,
|
|
6
9
|
NEEDS_HUMAN_REVIEW_LABEL,
|
|
7
10
|
PORCELAIN_FLAG,
|
|
8
|
-
RECENT_REQUEST_WINDOW,
|
|
9
11
|
agent_role_profile,
|
|
10
12
|
bash_triggers,
|
|
11
13
|
cli_flags,
|
|
12
|
-
error_text,
|
|
13
14
|
filing_cap,
|
|
14
15
|
git_command,
|
|
15
16
|
git_common_directory,
|
|
@@ -22,7 +23,6 @@ import {
|
|
|
22
23
|
lane_background,
|
|
23
24
|
lane_park,
|
|
24
25
|
lane_registry,
|
|
25
|
-
open_issue_schema,
|
|
26
26
|
own_session,
|
|
27
27
|
parse_json_object_safe,
|
|
28
28
|
read_json_listing,
|
|
@@ -33,35 +33,35 @@ import {
|
|
|
33
33
|
run_cut_handoff,
|
|
34
34
|
run_headless,
|
|
35
35
|
run_hold,
|
|
36
|
-
|
|
36
|
+
run_tail_rule,
|
|
37
37
|
shell_segments,
|
|
38
38
|
tail_commands
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-NV2JRZZO.js";
|
|
40
40
|
import {
|
|
41
|
+
ancestor_directories,
|
|
41
42
|
find_local_bin_upwards
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-JBBMAPC4.js";
|
|
44
|
+
import {
|
|
45
|
+
error_text
|
|
46
|
+
} from "./chunk-CDY4NFQG.js";
|
|
43
47
|
import {
|
|
44
48
|
batch_outcome
|
|
45
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-RNWHN5BP.js";
|
|
46
50
|
import {
|
|
47
51
|
investigation_refusal
|
|
48
|
-
} from "./chunk-
|
|
49
|
-
import {
|
|
50
|
-
duplicate_read_outcome
|
|
51
|
-
} from "./chunk-URJA6XY4.js";
|
|
52
|
+
} from "./chunk-F56MFS4B.js";
|
|
52
53
|
import {
|
|
53
54
|
investigation_reads
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-5VMKCCC4.js";
|
|
55
56
|
import {
|
|
56
57
|
lane_guard_policy
|
|
57
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-VC3PZPZO.js";
|
|
58
59
|
import {
|
|
59
60
|
hook_decision,
|
|
60
61
|
lane_child_marker,
|
|
61
62
|
run_issue_number
|
|
62
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-6HJJXRBX.js";
|
|
63
64
|
import {
|
|
64
|
-
PLATFORM_TEMP_ROOT,
|
|
65
65
|
cost_attribute,
|
|
66
66
|
cost_blocks,
|
|
67
67
|
cost_transcript,
|
|
@@ -69,13 +69,19 @@ import {
|
|
|
69
69
|
file_reader,
|
|
70
70
|
json_value,
|
|
71
71
|
lane_paths,
|
|
72
|
-
|
|
72
|
+
open_issue_schema,
|
|
73
73
|
time_batch_guard,
|
|
74
|
+
time_density_hook,
|
|
74
75
|
time_shell
|
|
75
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-55HHG5PX.js";
|
|
77
|
+
import {
|
|
78
|
+
PLATFORM_TEMP_ROOT,
|
|
79
|
+
PROBE_TIMEOUT_MS,
|
|
80
|
+
stamp_file
|
|
81
|
+
} from "./chunk-ELCN5LCH.js";
|
|
76
82
|
|
|
77
83
|
// scripts/rules/direct-filing.ts
|
|
78
|
-
var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands-
|
|
84
|
+
var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands-backlog.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
|
|
79
85
|
function is_any_filing(command) {
|
|
80
86
|
return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
|
|
81
87
|
}
|
|
@@ -89,7 +95,7 @@ var ROW = {
|
|
|
89
95
|
};
|
|
90
96
|
var direct_filing = { DIRECT_FILING_REASON, ROW };
|
|
91
97
|
|
|
92
|
-
// scripts/run/run-progress.ts
|
|
98
|
+
// scripts/run/progress/run-progress.ts
|
|
93
99
|
var DEFAULT_INTERVAL_MINUTES = 20;
|
|
94
100
|
var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
|
|
95
101
|
var MS_PER_MINUTE = 6e4;
|
|
@@ -208,7 +214,7 @@ var run_progress = {
|
|
|
208
214
|
observation_key
|
|
209
215
|
};
|
|
210
216
|
|
|
211
|
-
// scripts/run/run-progress-clock.ts
|
|
217
|
+
// scripts/run/progress/run-progress-clock.ts
|
|
212
218
|
import { z } from "zod";
|
|
213
219
|
var PROGRESS_PREFIX = "josh-run-progress-";
|
|
214
220
|
var LIFE_PREFIX = "josh-run-progress-life-";
|
|
@@ -334,24 +340,14 @@ var run_progress_clock = {
|
|
|
334
340
|
stamp_target_of
|
|
335
341
|
};
|
|
336
342
|
|
|
337
|
-
// scripts/run/run-progress-config.ts
|
|
343
|
+
// scripts/run/progress/run-progress-config.ts
|
|
338
344
|
import { readFileSync } from "node:fs";
|
|
339
345
|
import path from "node:path";
|
|
340
346
|
import { z as z2 } from "zod";
|
|
341
347
|
var PACKAGE_FILE = "package.json";
|
|
342
|
-
var MAX_DEPTH = 10;
|
|
343
348
|
var package_schema = z2.object({
|
|
344
349
|
josh: z2.object({ progress_interval_minutes: z2.number().positive() }).optional()
|
|
345
350
|
});
|
|
346
|
-
function ancestors_of(start) {
|
|
347
|
-
const directories = [];
|
|
348
|
-
let current = path.resolve(start);
|
|
349
|
-
while (directories.length < MAX_DEPTH && !directories.includes(current)) {
|
|
350
|
-
directories.push(current);
|
|
351
|
-
current = path.dirname(current);
|
|
352
|
-
}
|
|
353
|
-
return directories;
|
|
354
|
-
}
|
|
355
351
|
function read_declared_minutes(directory) {
|
|
356
352
|
try {
|
|
357
353
|
const raw = readFileSync(path.join(directory, PACKAGE_FILE), "utf8");
|
|
@@ -362,7 +358,7 @@ function read_declared_minutes(directory) {
|
|
|
362
358
|
}
|
|
363
359
|
}
|
|
364
360
|
function read_package_minutes(start) {
|
|
365
|
-
for (const directory of
|
|
361
|
+
for (const directory of ancestor_directories.list(start)) {
|
|
366
362
|
const minutes = read_declared_minutes(directory);
|
|
367
363
|
if (minutes !== void 0) return minutes;
|
|
368
364
|
}
|
|
@@ -552,48 +548,6 @@ var file_body = {
|
|
|
552
548
|
carries_a_file_body
|
|
553
549
|
};
|
|
554
550
|
|
|
555
|
-
// scripts/rules/gh-api.ts
|
|
556
|
-
var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
|
|
557
|
-
var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
|
|
558
|
-
var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
|
|
559
|
-
var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
|
|
560
|
-
var READ_METHOD = "GET";
|
|
561
|
-
var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
|
|
562
|
-
var OWNER_GROUP = 1;
|
|
563
|
-
var REPO_GROUP = 2;
|
|
564
|
-
var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
|
|
565
|
-
function is_gh_api(segment) {
|
|
566
|
-
return GH_API_COMMAND.test(segment);
|
|
567
|
-
}
|
|
568
|
-
function method_of(segment) {
|
|
569
|
-
return API_METHOD.exec(segment)?.[1]?.toUpperCase();
|
|
570
|
-
}
|
|
571
|
-
function is_read(segment) {
|
|
572
|
-
const method = method_of(segment);
|
|
573
|
-
if (method !== void 0) return method.toUpperCase() === READ_METHOD;
|
|
574
|
-
return !API_FIELD.test(segment);
|
|
575
|
-
}
|
|
576
|
-
function is_write(segment) {
|
|
577
|
-
return !is_read(segment);
|
|
578
|
-
}
|
|
579
|
-
function repo_target(segment) {
|
|
580
|
-
const match = REPO_PATH.exec(segment);
|
|
581
|
-
if (match === null) return void 0;
|
|
582
|
-
return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
|
|
583
|
-
}
|
|
584
|
-
function is_owner_placeholder(owner) {
|
|
585
|
-
return OWNER_PLACEHOLDERS.has(owner);
|
|
586
|
-
}
|
|
587
|
-
var gh_api = {
|
|
588
|
-
GH_FLAGS,
|
|
589
|
-
is_gh_api,
|
|
590
|
-
is_owner_placeholder,
|
|
591
|
-
is_read,
|
|
592
|
-
is_write,
|
|
593
|
-
method_of,
|
|
594
|
-
repo_target
|
|
595
|
-
};
|
|
596
|
-
|
|
597
551
|
// scripts/rules/git-argv.ts
|
|
598
552
|
var ENV_ASSIGNMENT = /^[A-Za-z_]\w*=/u;
|
|
599
553
|
var WRAPPER_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -897,22 +851,15 @@ var cost_corpus = {
|
|
|
897
851
|
var FAILURE_EXIT_CODE = 1;
|
|
898
852
|
var OVER_VERDICT = "over";
|
|
899
853
|
var UNDER_VERDICT = "under";
|
|
900
|
-
function recent_requests(measurement2) {
|
|
901
|
-
return measurement2.billed_input_per_request.slice(-RECENT_REQUEST_WINDOW);
|
|
902
|
-
}
|
|
903
854
|
function per_request_cost(measurement2) {
|
|
904
|
-
|
|
905
|
-
if (window.length === 0) return 0;
|
|
906
|
-
const total = window.reduce((sum, billed) => sum + billed, 0);
|
|
907
|
-
return Math.round(total / window.length);
|
|
855
|
+
return measurement2.billed_input_per_request.at(-1) ?? 0;
|
|
908
856
|
}
|
|
909
857
|
function classify(measurement2, limit) {
|
|
910
858
|
return per_request_cost(measurement2) > limit ? OVER_VERDICT : UNDER_VERDICT;
|
|
911
859
|
}
|
|
912
860
|
function over_summary(measurement2, limit) {
|
|
913
861
|
const total = measurement2.billed_input_per_request.length;
|
|
914
|
-
|
|
915
|
-
return `${String(per_request_cost(measurement2))} billed input tokens per request over the last ${String(window)} of ${String(total)} request(s); limit ${String(limit)}`;
|
|
862
|
+
return `${String(per_request_cost(measurement2))} billed input tokens per request, on the newest of ${String(total)} request(s); limit ${String(limit)}`;
|
|
916
863
|
}
|
|
917
864
|
function report_over(measurement2, limit) {
|
|
918
865
|
if (measurement2 === void 0 || measurement2.billed_input_per_request.length === 0) {
|
|
@@ -1208,7 +1155,141 @@ var implementation_cut = {
|
|
|
1208
1155
|
uncut_run_issue
|
|
1209
1156
|
};
|
|
1210
1157
|
|
|
1211
|
-
// scripts/rules/
|
|
1158
|
+
// scripts/rules/gh-api.ts
|
|
1159
|
+
var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
|
|
1160
|
+
var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
|
|
1161
|
+
var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
|
|
1162
|
+
var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
|
|
1163
|
+
var READ_METHOD = "GET";
|
|
1164
|
+
var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
|
|
1165
|
+
var OWNER_GROUP = 1;
|
|
1166
|
+
var REPO_GROUP = 2;
|
|
1167
|
+
var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
|
|
1168
|
+
function is_gh_api(segment) {
|
|
1169
|
+
return GH_API_COMMAND.test(segment);
|
|
1170
|
+
}
|
|
1171
|
+
function method_of(segment) {
|
|
1172
|
+
return API_METHOD.exec(segment)?.[1]?.toUpperCase();
|
|
1173
|
+
}
|
|
1174
|
+
function is_read(segment) {
|
|
1175
|
+
const method = method_of(segment);
|
|
1176
|
+
if (method !== void 0) return method.toUpperCase() === READ_METHOD;
|
|
1177
|
+
return !API_FIELD.test(segment);
|
|
1178
|
+
}
|
|
1179
|
+
function is_write(segment) {
|
|
1180
|
+
return !is_read(segment);
|
|
1181
|
+
}
|
|
1182
|
+
function repo_target(segment) {
|
|
1183
|
+
const match = REPO_PATH.exec(segment);
|
|
1184
|
+
if (match === null) return void 0;
|
|
1185
|
+
return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
|
|
1186
|
+
}
|
|
1187
|
+
function is_owner_placeholder(owner) {
|
|
1188
|
+
return OWNER_PLACEHOLDERS.has(owner);
|
|
1189
|
+
}
|
|
1190
|
+
var gh_api = {
|
|
1191
|
+
GH_FLAGS,
|
|
1192
|
+
is_gh_api,
|
|
1193
|
+
is_owner_placeholder,
|
|
1194
|
+
is_read,
|
|
1195
|
+
is_write,
|
|
1196
|
+
method_of,
|
|
1197
|
+
repo_target
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// scripts/rules/prior-comment-read.ts
|
|
1201
|
+
var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
|
|
1202
|
+
var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
|
|
1203
|
+
var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
|
|
1204
|
+
var A_NUMBER = /^\d+$/u;
|
|
1205
|
+
var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
|
|
1206
|
+
var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
|
|
1207
|
+
var ISSUE_READ_COMMAND = /\bissue:read\b/u;
|
|
1208
|
+
function add_issue_read_numbers(command, numbers) {
|
|
1209
|
+
const match = ISSUE_READ_ARGUMENTS.exec(command);
|
|
1210
|
+
if (match === null) return;
|
|
1211
|
+
const tokens = (match[1] ?? "").split(/\s+/u);
|
|
1212
|
+
for (const token of tokens) {
|
|
1213
|
+
if (A_NUMBER.test(token)) numbers.add(Number(token));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function issue_numbers_of(command) {
|
|
1217
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
1218
|
+
for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
|
|
1219
|
+
const view = ISSUE_VIEW_NUMBER.exec(command);
|
|
1220
|
+
if (view !== null) numbers.add(Number(view[1]));
|
|
1221
|
+
add_issue_read_numbers(command, numbers);
|
|
1222
|
+
return numbers;
|
|
1223
|
+
}
|
|
1224
|
+
function fetches_comments(command) {
|
|
1225
|
+
return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
|
|
1226
|
+
}
|
|
1227
|
+
function reads_target_comments(command, target) {
|
|
1228
|
+
if (!fetches_comments(command)) return false;
|
|
1229
|
+
for (const number of issue_numbers_of(command)) {
|
|
1230
|
+
if (target.has(number)) return true;
|
|
1231
|
+
}
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
function already_read_comments_for(tail, command) {
|
|
1235
|
+
const target = issue_numbers_of(command);
|
|
1236
|
+
if (target.size === 0) return false;
|
|
1237
|
+
return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
|
|
1238
|
+
}
|
|
1239
|
+
function already_read_for_call(tail, call) {
|
|
1240
|
+
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
1241
|
+
return already_read_comments_for(tail, time_shell.bash_command(call.input));
|
|
1242
|
+
}
|
|
1243
|
+
var prior_comment_read = {
|
|
1244
|
+
already_read_comments_for,
|
|
1245
|
+
already_read_for_call,
|
|
1246
|
+
fetches_comments,
|
|
1247
|
+
issue_numbers_of
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
// scripts/rules/issue-comments.ts
|
|
1251
|
+
var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
|
|
1252
|
+
var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
|
|
1253
|
+
var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
|
|
1254
|
+
var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
|
|
1255
|
+
var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
|
|
1256
|
+
var NAMES_THE_BODY = /\bbody\b/u;
|
|
1257
|
+
function projects_away_body(segment) {
|
|
1258
|
+
const projection = FIELD_PROJECTION.exec(segment);
|
|
1259
|
+
return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
|
|
1260
|
+
}
|
|
1261
|
+
function is_body_read_segment(segment) {
|
|
1262
|
+
if (projects_away_body(segment)) return false;
|
|
1263
|
+
if (ISSUE_VIEW_COMMAND.test(segment)) return true;
|
|
1264
|
+
return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
|
|
1265
|
+
}
|
|
1266
|
+
function fetches_issue_comments(segment) {
|
|
1267
|
+
if (!FETCHES_COMMENTS.test(segment)) return false;
|
|
1268
|
+
return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
|
|
1269
|
+
}
|
|
1270
|
+
function is_body_only_issue_read(command) {
|
|
1271
|
+
const segments = shell_segments.segments_of(command);
|
|
1272
|
+
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
1273
|
+
return segments.some((segment) => is_body_read_segment(segment));
|
|
1274
|
+
}
|
|
1275
|
+
function reads_issue_comments(command) {
|
|
1276
|
+
return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
|
|
1277
|
+
}
|
|
1278
|
+
var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/issue-comments.md`. Every body-only read is refused until the comments are read.";
|
|
1279
|
+
var ROW5 = {
|
|
1280
|
+
id: "issue-comments",
|
|
1281
|
+
is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
|
|
1282
|
+
reason: ISSUE_COMMENTS_REASON,
|
|
1283
|
+
already_satisfied: prior_comment_read.already_read_for_call,
|
|
1284
|
+
keeps: bash_triggers.on_bash_command(reads_issue_comments)
|
|
1285
|
+
};
|
|
1286
|
+
var issue_comments = {
|
|
1287
|
+
ISSUE_COMMENTS_REASON,
|
|
1288
|
+
ROW: ROW5,
|
|
1289
|
+
is_body_only_issue_read
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
// scripts/rules/issue-fold-rule.ts
|
|
1212
1293
|
var FOLD_NAMES = /* @__PURE__ */ new Set(["issue:fold"]);
|
|
1213
1294
|
function runs_the_fold(command) {
|
|
1214
1295
|
return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, FOLD_NAMES));
|
|
@@ -1218,14 +1299,14 @@ function already_folded(tail, _call) {
|
|
|
1218
1299
|
return tail_commands.prior_bash_commands(tail).some((command) => runs_the_fold(command));
|
|
1219
1300
|
}
|
|
1220
1301
|
var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question". Every later filing is refused until the fold is on the transcript.';
|
|
1221
|
-
var
|
|
1302
|
+
var ROW6 = {
|
|
1222
1303
|
id: "issue-fold",
|
|
1223
1304
|
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
1224
1305
|
reason: ISSUE_FOLD_REASON,
|
|
1225
1306
|
already_satisfied: already_folded,
|
|
1226
1307
|
keeps: bash_triggers.on_bash_command(runs_the_fold)
|
|
1227
1308
|
};
|
|
1228
|
-
var
|
|
1309
|
+
var issue_fold_rule = { ISSUE_FOLD_REASON, ROW: ROW6, already_folded, runs_the_fold };
|
|
1229
1310
|
|
|
1230
1311
|
// scripts/rules/josh-git-bare.ts
|
|
1231
1312
|
var GIT_COMMAND2 = /* @__PURE__ */ new Set(["git"]);
|
|
@@ -1241,7 +1322,7 @@ function is_bare_josh_git(command) {
|
|
|
1241
1322
|
return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
|
|
1242
1323
|
}
|
|
1243
1324
|
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 \u2014 `Checking unstaged files... Found` then `Operation cancelled.` \u2014 so the call throws away the time it takes to fail (about 31 seconds in one measured lane) and you reissue it with `-y` anyway (joshuafolkken/kit#2297). Run it unattended from the start: `pnpm josh git -y "<title> #<N>"`. The recovery form that opens the PR for a push that already landed is `pnpm josh git -y --skip-commit --skip-push`. This rule fires on every occurrence, not once per run.';
|
|
1244
|
-
var
|
|
1325
|
+
var ROW7 = {
|
|
1245
1326
|
id: "josh-git-bare",
|
|
1246
1327
|
is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
|
|
1247
1328
|
reason: JOSH_GIT_BARE_REASON,
|
|
@@ -1249,7 +1330,7 @@ var ROW6 = {
|
|
|
1249
1330
|
};
|
|
1250
1331
|
var josh_git_bare = {
|
|
1251
1332
|
JOSH_GIT_BARE_REASON,
|
|
1252
|
-
ROW:
|
|
1333
|
+
ROW: ROW7,
|
|
1253
1334
|
is_bare_josh_git
|
|
1254
1335
|
};
|
|
1255
1336
|
|
|
@@ -1263,7 +1344,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
|
|
|
1263
1344
|
return is_lane_child;
|
|
1264
1345
|
}
|
|
1265
1346
|
var LANE_CARRY_CONFLICT_REASON = '\u26D4 lane child carry conflict: this session is a dispatched lane child, and `pnpm josh run:merge` / `pnpm josh run:carry` are the parent backlogrun budget commands \u2014 a child must never run them. The live carry record here belongs to the parent that launched this child, not to a competing run: the parent holds the one run budget for the repository, and every lane shares the common git directory it is keyed on, so a live owner a lane child reads is always its parent (joshuafolkken/kit#2267, measured 2026-09-21 in backlogrun #2252 \u2014 a child read run:merge `busy` as a competitor and parked #2258 unimplemented). Do not run this command, and do not treat it as a conflict: no `needs-decision`, no `confirmation` Telegram, no question to a person \u2014 a dispatched child cannot ask, and there is nothing to decide. Continue implementing this Issue; the parent owns the budget and counts the outcome at the return. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` and `backlogrun-progress.md` \u2192 "The hand-off". This rule fires on every occurrence, not once per run.';
|
|
1266
|
-
var
|
|
1347
|
+
var ROW8 = {
|
|
1267
1348
|
id: "lane-carry-conflict",
|
|
1268
1349
|
is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
|
|
1269
1350
|
reason: LANE_CARRY_CONFLICT_REASON,
|
|
@@ -1271,7 +1352,7 @@ var ROW7 = {
|
|
|
1271
1352
|
};
|
|
1272
1353
|
var lane_carry_conflict = {
|
|
1273
1354
|
LANE_CARRY_CONFLICT_REASON,
|
|
1274
|
-
ROW:
|
|
1355
|
+
ROW: ROW8,
|
|
1275
1356
|
invokes_budget_command,
|
|
1276
1357
|
is_carry_conflict
|
|
1277
1358
|
};
|
|
@@ -1324,13 +1405,73 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
|
|
|
1324
1405
|
return is_lane_child;
|
|
1325
1406
|
}
|
|
1326
1407
|
var LANE_INTERACTIVE_ASK_REASON = '\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so this call is refused. Left to run it would end the turn with nothing on the Issue \u2014 the parent then finds an OPEN Issue with `in-progress` and no question (joshuafolkken/kit#2201; joshuafolkken/kit#2034 placed the same rule one tool-call too late). Do not ask: park the decision instead. Apply `needs-decision` and post a comment carrying the question, the options and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `pnpm josh issue:comment <N> --body-file <path>` \u2014 then send the `confirmation` Telegram and stop. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue". This rule fires on every occurrence, not once per run.';
|
|
1327
|
-
var
|
|
1408
|
+
var ROW9 = {
|
|
1328
1409
|
id: "lane-interactive-ask",
|
|
1329
1410
|
is_trigger: is_interactive_ask,
|
|
1330
1411
|
reason: LANE_INTERACTIVE_ASK_REASON,
|
|
1331
1412
|
decide: () => true
|
|
1332
1413
|
};
|
|
1333
|
-
var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW:
|
|
1414
|
+
var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW9, is_interactive_ask };
|
|
1415
|
+
|
|
1416
|
+
// scripts/rules/lane-split-park.ts
|
|
1417
|
+
var EPIC_COMMANDS = /* @__PURE__ */ new Set(["epic"]);
|
|
1418
|
+
var PROMOTED_ISSUE = /--promote[=\s]+["']?(?<issue>\d+)\b/u;
|
|
1419
|
+
var LABEL_TARGETS = [
|
|
1420
|
+
/\/issues\/(?<issue>\d+)\/labels\b/u,
|
|
1421
|
+
/\bissue\s+edit\s+["']?(?<issue>\d+)\b/u
|
|
1422
|
+
];
|
|
1423
|
+
var ISSUE_URL = /--issue-url[=\s]+["']?([^\s"']+)/u;
|
|
1424
|
+
var URL_ISSUE = /\/issues\/(\d+)\b/u;
|
|
1425
|
+
function promoted_in(segment) {
|
|
1426
|
+
if (!shell_segments.is_josh_command(segment, EPIC_COMMANDS)) return void 0;
|
|
1427
|
+
return PROMOTED_ISSUE.exec(segment)?.groups?.["issue"];
|
|
1428
|
+
}
|
|
1429
|
+
function promoted_issues(commands) {
|
|
1430
|
+
const segments = commands.flatMap((command) => shell_segments.segments_of(command));
|
|
1431
|
+
const issues = segments.map((segment) => promoted_in(segment));
|
|
1432
|
+
return new Set(issues.filter((issue) => issue !== void 0));
|
|
1433
|
+
}
|
|
1434
|
+
function label_target(command) {
|
|
1435
|
+
for (const pattern of LABEL_TARGETS) {
|
|
1436
|
+
const issue = pattern.exec(command)?.groups?.["issue"];
|
|
1437
|
+
if (issue !== void 0) return issue;
|
|
1438
|
+
}
|
|
1439
|
+
return void 0;
|
|
1440
|
+
}
|
|
1441
|
+
function is_park_of(command, issue) {
|
|
1442
|
+
return lane_park.records_the_park(command) && label_target(command) === issue;
|
|
1443
|
+
}
|
|
1444
|
+
function notified_issue(command) {
|
|
1445
|
+
const [, url = ""] = ISSUE_URL.exec(command) ?? [];
|
|
1446
|
+
const [, issue] = URL_ISSUE.exec(url) ?? [];
|
|
1447
|
+
return issue;
|
|
1448
|
+
}
|
|
1449
|
+
function is_notify_of(command, issue) {
|
|
1450
|
+
return lane_park.is_confirmation_notify(command) && notified_issue(command) === issue;
|
|
1451
|
+
}
|
|
1452
|
+
function is_candidate(command) {
|
|
1453
|
+
const issue = lane_child_marker.marked_issue();
|
|
1454
|
+
if (issue === void 0) return false;
|
|
1455
|
+
if (!is_park_of(command, issue) && !is_notify_of(command, issue)) return false;
|
|
1456
|
+
return lane_child_marker.is_child_of(process.cwd());
|
|
1457
|
+
}
|
|
1458
|
+
function decide3(_call, run2) {
|
|
1459
|
+
const issue = lane_child_marker.marked_issue();
|
|
1460
|
+
const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run2.transcript));
|
|
1461
|
+
return issue !== void 0 && promoted_issues(commands).has(issue);
|
|
1462
|
+
}
|
|
1463
|
+
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 \u2014 do not apply `needs-decision` to the promoted epic and do not send a `confirmation` Telegram. The parent reads an open Issue carrying `epic` as a split and continues through its children; a question here has nobody to answer it (joshuafolkken/kit#3296, measured 2026-10-06 when the child for #3255 parked its promoted epic). If what remains of the original work needs a person, park that child issue instead, then end the turn. The procedure is `.claude/skills/workflow-commands/backlogrun-park.md` \u2192 "Splitting a child mid-run". This rule fires on every occurrence, not once per run.';
|
|
1464
|
+
var ROW10 = {
|
|
1465
|
+
id: "lane-split-park",
|
|
1466
|
+
is_trigger: bash_triggers.on_bash_command(is_candidate),
|
|
1467
|
+
reason: LANE_SPLIT_PARK_REASON,
|
|
1468
|
+
decide: decide3
|
|
1469
|
+
};
|
|
1470
|
+
var lane_split_park = {
|
|
1471
|
+
LANE_SPLIT_PARK_REASON,
|
|
1472
|
+
ROW: ROW10,
|
|
1473
|
+
promoted_issues
|
|
1474
|
+
};
|
|
1334
1475
|
|
|
1335
1476
|
// scripts/rules/lane-switch-main.ts
|
|
1336
1477
|
var SWITCH_SUBCOMMAND = "switch";
|
|
@@ -1361,7 +1502,7 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
|
|
|
1361
1502
|
return switch_targets(command).some((target) => target !== own_branch);
|
|
1362
1503
|
}
|
|
1363
1504
|
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 \u2014 a lane is a linked work tree, the primary checkout holds the default branch, and git allows one branch in one work tree at a time (`fatal: main is already used by worktree at <the primary checkout>`, measured 11 times in the 2026-09-21 backlogrun). Do not switch: the lane is already on its own issue branch, branched from a fresh default before it opened, and the latest default is brought in during the gate by `pnpm josh main:merge`. So skip this step and keep implementing on the current branch \u2014 no `git switch`, no `git pull`. `pnpm josh main:sync` (`josh ms`) is refused in a lane for the same reason, and a lane is finished with `pnpm josh lane:close <issue-number>`. The parent runs `git switch main && git pull` in the primary checkout before each lane opens, never the child \u2014 `backlogrun-lanes.md` and `backlogrun-child.md` are the single sources. This rule fires on every occurrence, not once per run.";
|
|
1364
|
-
var
|
|
1505
|
+
var ROW11 = {
|
|
1365
1506
|
id: "lane-switch-main",
|
|
1366
1507
|
is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
|
|
1367
1508
|
reason: LANE_SWITCH_MAIN_REASON,
|
|
@@ -1369,12 +1510,138 @@ var ROW9 = {
|
|
|
1369
1510
|
};
|
|
1370
1511
|
var lane_switch_main = {
|
|
1371
1512
|
LANE_SWITCH_MAIN_REASON,
|
|
1372
|
-
ROW:
|
|
1513
|
+
ROW: ROW11,
|
|
1373
1514
|
is_switch_away_from_lane,
|
|
1374
1515
|
switch_target,
|
|
1375
1516
|
switch_targets
|
|
1376
1517
|
};
|
|
1377
1518
|
|
|
1519
|
+
// scripts/rules/decision-oracle-tokens.ts
|
|
1520
|
+
var oracle_tokens = {
|
|
1521
|
+
REQUIRED: "required",
|
|
1522
|
+
SKIP: "skip",
|
|
1523
|
+
STOP: "stop",
|
|
1524
|
+
WAIT: "wait",
|
|
1525
|
+
RETRY: "retry",
|
|
1526
|
+
OVER: "over",
|
|
1527
|
+
UNKNOWN: "unknown",
|
|
1528
|
+
NONE: "none",
|
|
1529
|
+
NOT_A_LANE: "not-a-lane",
|
|
1530
|
+
BUSY: "busy",
|
|
1531
|
+
HUMAN_REVIEW: "human-review",
|
|
1532
|
+
ISSUE_N_ARG: "<N>",
|
|
1533
|
+
EXCLUDE_ARG: "[--exclude <n>...]"
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
// scripts/rules/decision-oracle-batch.ts
|
|
1537
|
+
var {
|
|
1538
|
+
BUSY,
|
|
1539
|
+
EXCLUDE_ARG,
|
|
1540
|
+
HUMAN_REVIEW,
|
|
1541
|
+
ISSUE_N_ARG,
|
|
1542
|
+
NONE,
|
|
1543
|
+
NOT_A_LANE,
|
|
1544
|
+
OVER,
|
|
1545
|
+
RETRY,
|
|
1546
|
+
STOP,
|
|
1547
|
+
UNKNOWN,
|
|
1548
|
+
WAIT
|
|
1549
|
+
} = oracle_tokens;
|
|
1550
|
+
var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
|
|
1551
|
+
var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
|
|
1552
|
+
var RUN_MERGE_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:merge`";
|
|
1553
|
+
var EPIC_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh epic:next`";
|
|
1554
|
+
var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh backlog:budget`";
|
|
1555
|
+
var RUN_LIVENESS_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:liveness`";
|
|
1556
|
+
var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh auto-ok:next`";
|
|
1557
|
+
var RUN_CUT_CMD = "run:cut";
|
|
1558
|
+
var BATCH_ORACLES = [
|
|
1559
|
+
{
|
|
1560
|
+
name: "run:cut:resume",
|
|
1561
|
+
command: RUN_CUT_CMD,
|
|
1562
|
+
decision: "Whether a lane child is resuming prior work or starting fresh",
|
|
1563
|
+
args: `--resume ${ISSUE_N_ARG}`,
|
|
1564
|
+
vocabulary: ["fresh", "resume", NOT_A_LANE],
|
|
1565
|
+
single_source: PRE_GATE_CUT_MD
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
name: "run:cut:gate",
|
|
1569
|
+
command: RUN_CUT_CMD,
|
|
1570
|
+
decision: "Whether a lane child should take its pre-gate cut now",
|
|
1571
|
+
args: ISSUE_N_ARG,
|
|
1572
|
+
vocabulary: ["cut", NOT_A_LANE],
|
|
1573
|
+
single_source: PRE_GATE_CUT_MD
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
name: "backlog:budget",
|
|
1577
|
+
decision: "Whether a backlogrun may start more work, keep watching, or finish",
|
|
1578
|
+
args: "",
|
|
1579
|
+
vocabulary: ["run", "watch", STOP],
|
|
1580
|
+
single_source: BACKLOG_BUDGET_REFERENCE
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
name: "run:liveness",
|
|
1584
|
+
decision: "Whether a delegated unit is still working or has stopped without reporting",
|
|
1585
|
+
args: `${ISSUE_N_ARG} --output <path>`,
|
|
1586
|
+
vocabulary: ["alive", "stopped", "settled", "undetermined"],
|
|
1587
|
+
single_source: RUN_LIVENESS_REFERENCE
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
name: "stash:pop",
|
|
1591
|
+
decision: "Whether a named stash entry exists and can be applied",
|
|
1592
|
+
args: "<message>",
|
|
1593
|
+
vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
|
|
1594
|
+
single_source: ".claude/skills/workflow-commands/prerequisite.md"
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
name: "backlog:next",
|
|
1598
|
+
decision: "Which issue the backlog offers next, or whether to wait or stop",
|
|
1599
|
+
args: EXCLUDE_ARG,
|
|
1600
|
+
vocabulary: [WAIT, STOP, RETRY, "error", NONE],
|
|
1601
|
+
single_source: BACKLOGRUN_MD
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
name: "auto-ok:next",
|
|
1605
|
+
decision: "The next opted-in issue outside any epic, or none",
|
|
1606
|
+
args: EXCLUDE_ARG,
|
|
1607
|
+
vocabulary: [NONE],
|
|
1608
|
+
single_source: AUTO_OK_NEXT_REFERENCE
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
name: "epic:next",
|
|
1612
|
+
decision: "The next runnable child of an epic, or the epic's status",
|
|
1613
|
+
args: "<epic>",
|
|
1614
|
+
vocabulary: [WAIT, STOP, "complete"],
|
|
1615
|
+
single_source: EPIC_NEXT_REFERENCE
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
name: "run:merge",
|
|
1619
|
+
decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
|
|
1620
|
+
args: ISSUE_N_ARG,
|
|
1621
|
+
vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
|
|
1622
|
+
single_source: RUN_MERGE_REFERENCE
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
name: "run:step",
|
|
1626
|
+
decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
|
|
1627
|
+
args: ISSUE_N_ARG,
|
|
1628
|
+
// The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
|
|
1629
|
+
// one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
|
|
1630
|
+
// number is not one for `backlog:next`.
|
|
1631
|
+
vocabulary: [
|
|
1632
|
+
"implement",
|
|
1633
|
+
HUMAN_REVIEW,
|
|
1634
|
+
"update-deps",
|
|
1635
|
+
"already-done",
|
|
1636
|
+
"keep-work",
|
|
1637
|
+
WAIT,
|
|
1638
|
+
STOP,
|
|
1639
|
+
UNKNOWN
|
|
1640
|
+
],
|
|
1641
|
+
single_source: BACKLOGRUN_MD
|
|
1642
|
+
}
|
|
1643
|
+
];
|
|
1644
|
+
|
|
1378
1645
|
// scripts/run/run-stage.ts
|
|
1379
1646
|
var KICKOFF = "kickoff";
|
|
1380
1647
|
var HALFRUN = "halfrun";
|
|
@@ -1417,7 +1684,7 @@ function state_of(facts) {
|
|
|
1417
1684
|
if (facts.is_halfrun_stopped) return HALFRUN_STOPPED;
|
|
1418
1685
|
return facts.is_planned ? PLANNED : FRESH;
|
|
1419
1686
|
}
|
|
1420
|
-
function
|
|
1687
|
+
function decide4(state, command) {
|
|
1421
1688
|
const is_reached = STATES.indexOf(state) > COMMANDS.indexOf(command);
|
|
1422
1689
|
const start = is_reached ? REACHED : STARTS[state];
|
|
1423
1690
|
return { state, command, start, end: ENDS[command], is_reached };
|
|
@@ -1434,7 +1701,7 @@ function format_decision(issue, decision) {
|
|
|
1434
1701
|
return `stage #${issue} \u2014 ${fields.join(" \xB7 ")}`;
|
|
1435
1702
|
}
|
|
1436
1703
|
function cell(state, command) {
|
|
1437
|
-
const decision =
|
|
1704
|
+
const decision = decide4(state, command);
|
|
1438
1705
|
return decision.is_reached ? REACHED_CELL : `${decision.start}${CELL_ARROW}${decision.end}`;
|
|
1439
1706
|
}
|
|
1440
1707
|
function table_rows() {
|
|
@@ -1459,7 +1726,7 @@ var run_stage = {
|
|
|
1459
1726
|
REACHED,
|
|
1460
1727
|
START_TOKENS,
|
|
1461
1728
|
STATES,
|
|
1462
|
-
decide:
|
|
1729
|
+
decide: decide4,
|
|
1463
1730
|
format_decision,
|
|
1464
1731
|
next_commands,
|
|
1465
1732
|
next_line,
|
|
@@ -1478,35 +1745,15 @@ var RUN_ENTRY_ORACLE = {
|
|
|
1478
1745
|
};
|
|
1479
1746
|
|
|
1480
1747
|
// scripts/rules/decision-oracle.ts
|
|
1748
|
+
var { BUSY: BUSY2, ISSUE_N_ARG: ISSUE_N_ARG2, NONE: NONE2, OVER: OVER2, REQUIRED, SKIP, UNKNOWN: UNKNOWN2 } = oracle_tokens;
|
|
1481
1749
|
var CHAIN_RULE_MD = ".claude/skills/workflow-commands/chain-rule.md";
|
|
1482
1750
|
var BACKLOGRUN_PROGRESS_MD = ".claude/skills/workflow-commands/backlogrun-progress.md";
|
|
1483
1751
|
var SKILL_2E = ".claude/skills/workflow-commands/issue-scout.md";
|
|
1484
1752
|
var SPLIT_ASSESSMENT_QUESTION = ".claude/skills/workflow-commands/split-assessment.md \u2192 The question";
|
|
1485
|
-
var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
|
|
1486
|
-
var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
|
|
1487
|
-
var RUN_MERGE_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:merge`";
|
|
1488
|
-
var EPIC_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh epic:next`";
|
|
1489
|
-
var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh backlog:budget`";
|
|
1490
|
-
var RUN_LIVENESS_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:liveness`";
|
|
1491
|
-
var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh auto-ok:next`";
|
|
1492
|
-
var RUN_CUT_CMD = "run:cut";
|
|
1493
|
-
var REQUIRED = "required";
|
|
1494
|
-
var SKIP = "skip";
|
|
1495
|
-
var STOP = "stop";
|
|
1496
|
-
var WAIT = "wait";
|
|
1497
|
-
var RETRY = "retry";
|
|
1498
|
-
var OVER = "over";
|
|
1499
|
-
var UNKNOWN = "unknown";
|
|
1500
|
-
var NONE = "none";
|
|
1501
|
-
var NOT_A_LANE = "not-a-lane";
|
|
1502
|
-
var BUSY = "busy";
|
|
1503
|
-
var HUMAN_REVIEW = "human-review";
|
|
1504
1753
|
var LIVE = "live";
|
|
1505
1754
|
var STOPPED = "stopped";
|
|
1506
1755
|
var RECONCILED = "reconciled";
|
|
1507
1756
|
var NOTHING_TO_RECONCILE = "nothing to reconcile";
|
|
1508
|
-
var ISSUE_N_ARG = "<N>";
|
|
1509
|
-
var EXCLUDE_ARG = "[--exclude <n>...]";
|
|
1510
1757
|
var DECISION_ORACLES = [
|
|
1511
1758
|
{
|
|
1512
1759
|
name: "delegate",
|
|
@@ -1554,8 +1801,8 @@ var DECISION_ORACLES = [
|
|
|
1554
1801
|
{
|
|
1555
1802
|
name: "run:hold",
|
|
1556
1803
|
decision: "Whether this working tree is free for a run to claim",
|
|
1557
|
-
args: `[${
|
|
1558
|
-
vocabulary: ["hold",
|
|
1804
|
+
args: `[${ISSUE_N_ARG2}]`,
|
|
1805
|
+
vocabulary: ["hold", BUSY2, UNKNOWN2],
|
|
1559
1806
|
single_source: ".claude/skills/workflow-commands/working-tree-hold.md"
|
|
1560
1807
|
},
|
|
1561
1808
|
{
|
|
@@ -1563,20 +1810,20 @@ var DECISION_ORACLES = [
|
|
|
1563
1810
|
command: "cost",
|
|
1564
1811
|
decision: "Whether the session cost exceeds the hand-off threshold",
|
|
1565
1812
|
args: "--cut",
|
|
1566
|
-
vocabulary: [
|
|
1813
|
+
vocabulary: [OVER2, "under"],
|
|
1567
1814
|
single_source: BACKLOGRUN_PROGRESS_MD
|
|
1568
1815
|
},
|
|
1569
1816
|
{
|
|
1570
1817
|
name: "release:scope",
|
|
1571
1818
|
decision: "Whether a release is owed after this merge",
|
|
1572
1819
|
args: "",
|
|
1573
|
-
vocabulary: [REQUIRED, SKIP,
|
|
1820
|
+
vocabulary: [REQUIRED, SKIP, UNKNOWN2],
|
|
1574
1821
|
single_source: ".claude/skills/workflow-commands/followup.md"
|
|
1575
1822
|
},
|
|
1576
1823
|
{
|
|
1577
1824
|
name: "epic:bundle",
|
|
1578
1825
|
decision: "Which epic a newly filed issue belongs to",
|
|
1579
|
-
args:
|
|
1826
|
+
args: ISSUE_N_ARG2,
|
|
1580
1827
|
vocabulary: ["add_to_epic", "create_epic", "ask", "nothing_to_bundle"],
|
|
1581
1828
|
single_source: SKILL_2E
|
|
1582
1829
|
},
|
|
@@ -1612,100 +1859,17 @@ var DECISION_ORACLES = [
|
|
|
1612
1859
|
name: "cases",
|
|
1613
1860
|
decision: "Each boundary a change crosses (network, process, fs, time) and the abnormal cases it owes",
|
|
1614
1861
|
args: "<path...>",
|
|
1615
|
-
vocabulary: ["network", "process", "fs", "time",
|
|
1862
|
+
vocabulary: ["network", "process", "fs", "time", NONE2],
|
|
1616
1863
|
single_source: "prompts/collaboration-workflow/report-format.md \u2192 \u5909\u66F4\u3068\u30C6\u30B9\u30C8"
|
|
1617
1864
|
},
|
|
1618
1865
|
{
|
|
1619
1866
|
name: "issue:state",
|
|
1620
1867
|
decision: "An issue's state, labels, and human-review flag",
|
|
1621
|
-
args:
|
|
1868
|
+
args: ISSUE_N_ARG2,
|
|
1622
1869
|
vocabulary: ["human_review: yes", "human_review: no"],
|
|
1623
1870
|
single_source: ".claude/skills/workflow-commands/needs-human-review.md"
|
|
1624
1871
|
},
|
|
1625
|
-
|
|
1626
|
-
name: "run:cut:resume",
|
|
1627
|
-
command: RUN_CUT_CMD,
|
|
1628
|
-
decision: "Whether a lane child is resuming prior work or starting fresh",
|
|
1629
|
-
args: `--resume ${ISSUE_N_ARG}`,
|
|
1630
|
-
vocabulary: ["fresh", "resume", NOT_A_LANE],
|
|
1631
|
-
single_source: PRE_GATE_CUT_MD
|
|
1632
|
-
},
|
|
1633
|
-
{
|
|
1634
|
-
name: "run:cut:gate",
|
|
1635
|
-
command: RUN_CUT_CMD,
|
|
1636
|
-
decision: "Whether a lane child should take its pre-gate cut now",
|
|
1637
|
-
args: ISSUE_N_ARG,
|
|
1638
|
-
vocabulary: ["cut", NOT_A_LANE],
|
|
1639
|
-
single_source: PRE_GATE_CUT_MD
|
|
1640
|
-
},
|
|
1641
|
-
{
|
|
1642
|
-
name: "backlog:budget",
|
|
1643
|
-
decision: "Whether a backlogrun may start more work, keep watching, or finish",
|
|
1644
|
-
args: "",
|
|
1645
|
-
vocabulary: ["run", "watch", STOP],
|
|
1646
|
-
single_source: BACKLOG_BUDGET_REFERENCE
|
|
1647
|
-
},
|
|
1648
|
-
{
|
|
1649
|
-
name: "run:liveness",
|
|
1650
|
-
decision: "Whether a delegated unit is still working or has stopped without reporting",
|
|
1651
|
-
args: `${ISSUE_N_ARG} --output <path>`,
|
|
1652
|
-
vocabulary: ["alive", "stopped", "settled", "undetermined"],
|
|
1653
|
-
single_source: RUN_LIVENESS_REFERENCE
|
|
1654
|
-
},
|
|
1655
|
-
{
|
|
1656
|
-
name: "stash:pop",
|
|
1657
|
-
decision: "Whether a named stash entry exists and can be applied",
|
|
1658
|
-
args: "<message>",
|
|
1659
|
-
vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
|
|
1660
|
-
single_source: ".claude/skills/workflow-commands/prerequisite.md"
|
|
1661
|
-
},
|
|
1662
|
-
{
|
|
1663
|
-
name: "backlog:next",
|
|
1664
|
-
decision: "Which issue the backlog offers next, or whether to wait or stop",
|
|
1665
|
-
args: EXCLUDE_ARG,
|
|
1666
|
-
vocabulary: [WAIT, STOP, RETRY, "error", NONE],
|
|
1667
|
-
single_source: BACKLOGRUN_MD
|
|
1668
|
-
},
|
|
1669
|
-
{
|
|
1670
|
-
name: "auto-ok:next",
|
|
1671
|
-
decision: "The next opted-in issue outside any epic, or none",
|
|
1672
|
-
args: EXCLUDE_ARG,
|
|
1673
|
-
vocabulary: [NONE],
|
|
1674
|
-
single_source: AUTO_OK_NEXT_REFERENCE
|
|
1675
|
-
},
|
|
1676
|
-
{
|
|
1677
|
-
name: "epic:next",
|
|
1678
|
-
decision: "The next runnable child of an epic, or the epic's status",
|
|
1679
|
-
args: "<epic>",
|
|
1680
|
-
vocabulary: [WAIT, STOP, "complete"],
|
|
1681
|
-
single_source: EPIC_NEXT_REFERENCE
|
|
1682
|
-
},
|
|
1683
|
-
{
|
|
1684
|
-
name: "run:merge",
|
|
1685
|
-
decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
|
|
1686
|
-
args: ISSUE_N_ARG,
|
|
1687
|
-
vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
|
|
1688
|
-
single_source: RUN_MERGE_REFERENCE
|
|
1689
|
-
},
|
|
1690
|
-
{
|
|
1691
|
-
name: "run:step",
|
|
1692
|
-
decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
|
|
1693
|
-
args: ISSUE_N_ARG,
|
|
1694
|
-
// The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
|
|
1695
|
-
// one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
|
|
1696
|
-
// number is not one for `backlog:next`.
|
|
1697
|
-
vocabulary: [
|
|
1698
|
-
"implement",
|
|
1699
|
-
HUMAN_REVIEW,
|
|
1700
|
-
"update-deps",
|
|
1701
|
-
"already-done",
|
|
1702
|
-
"keep-work",
|
|
1703
|
-
WAIT,
|
|
1704
|
-
STOP,
|
|
1705
|
-
UNKNOWN
|
|
1706
|
-
],
|
|
1707
|
-
single_source: BACKLOGRUN_MD
|
|
1708
|
-
},
|
|
1872
|
+
...BATCH_ORACLES,
|
|
1709
1873
|
{
|
|
1710
1874
|
name: "refactor:scan",
|
|
1711
1875
|
decision: "Whether the refactoring scope still holds high- or medium-priority candidates",
|
|
@@ -1732,7 +1896,7 @@ var DECISION_ORACLES = [
|
|
|
1732
1896
|
decision: "Whether cross-file or cross-repository code duplication exists",
|
|
1733
1897
|
args: "",
|
|
1734
1898
|
vocabulary: ["clean", "clones:"],
|
|
1735
|
-
single_source: "prompts/collaboration-workflow/principles.md \u2192
|
|
1899
|
+
single_source: "prompts/collaboration-workflow/principles.md \u2192 no-clones"
|
|
1736
1900
|
},
|
|
1737
1901
|
{
|
|
1738
1902
|
name: "epic:reconcile",
|
|
@@ -1740,14 +1904,14 @@ var DECISION_ORACLES = [
|
|
|
1740
1904
|
decision: "Whether an epic's declaration matches its recorded relations, and the repair when it does not",
|
|
1741
1905
|
args: "--reconcile <E>",
|
|
1742
1906
|
vocabulary: [RECONCILED, NOTHING_TO_RECONCILE],
|
|
1743
|
-
single_source: "docs/josh-commands-
|
|
1907
|
+
single_source: "docs/josh-commands-backlog.md \u2192 `josh epic --reconcile`"
|
|
1744
1908
|
},
|
|
1745
1909
|
{
|
|
1746
1910
|
name: "lane:list",
|
|
1747
1911
|
decision: "Whether each in-progress issue holds a live lane, and the two-directional difference",
|
|
1748
1912
|
args: "",
|
|
1749
|
-
vocabulary: [LIVE, STOPPED,
|
|
1750
|
-
single_source: "docs/josh-commands-
|
|
1913
|
+
vocabulary: [LIVE, STOPPED, UNKNOWN2],
|
|
1914
|
+
single_source: "docs/josh-commands-run.md \u2192 The `in-progress` / lane difference"
|
|
1751
1915
|
},
|
|
1752
1916
|
RUN_ENTRY_ORACLE
|
|
1753
1917
|
];
|
|
@@ -1829,7 +1993,7 @@ function declaration_for(name) {
|
|
|
1829
1993
|
function firing_oracles() {
|
|
1830
1994
|
return decision_oracle.DECISION_ORACLES.flatMap((oracle) => {
|
|
1831
1995
|
const firing_point = FIRING.get(oracle.name);
|
|
1832
|
-
return firing_point === void 0 ? [] :
|
|
1996
|
+
return firing_point === void 0 ? [] : { oracle, firing_point };
|
|
1833
1997
|
});
|
|
1834
1998
|
}
|
|
1835
1999
|
var oracle_firing = {
|
|
@@ -1919,13 +2083,13 @@ function is_destructive_command(command) {
|
|
|
1919
2083
|
return shell_segments.segments_of(command).some((segment) => is_destructive_segment2(segment));
|
|
1920
2084
|
}
|
|
1921
2085
|
var DESTRUCTIVE_COMMAND_REASON = "\u26D4 destructive command: a recursive forced `rm` (`rm -rf`, `rm -fr`, `rm -r -f`, `rm --recursive --force`), `gh repo delete` / `archive`, `gh repo edit --visibility`, `gh pr close`, or a `gh api -X DELETE repos/\u2026` call deletes or closes something that cannot be restored, which is Tier C and needs explicit current-turn user instruction (`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list matches prefix globs, so it misses these spellings; this row reads the argv instead. Remove a file or directory one path at a time without `-f`, or ask the user to run the command in their own terminal. `gh issue close` and removing a label from an Issue (`DELETE repos/<o>/<r>/issues/<N>/labels/<name>`) are not refused. **This rule fires on every occurrence, not once per run.**";
|
|
1922
|
-
var
|
|
2086
|
+
var ROW12 = {
|
|
1923
2087
|
id: "destructive-command",
|
|
1924
2088
|
is_trigger: bash_triggers.on_bash_command(is_destructive_command),
|
|
1925
2089
|
reason: DESTRUCTIVE_COMMAND_REASON,
|
|
1926
2090
|
decide: () => true
|
|
1927
2091
|
};
|
|
1928
|
-
var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW:
|
|
2092
|
+
var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW12, is_destructive_command };
|
|
1929
2093
|
|
|
1930
2094
|
// scripts/rules/direct-pr-create.ts
|
|
1931
2095
|
var GH_COMMAND2 = "gh";
|
|
@@ -1980,13 +2144,13 @@ function is_direct_pr_create(command) {
|
|
|
1980
2144
|
return shell_segments.segments_of(command).some((segment) => is_pr_create_segment(segment));
|
|
1981
2145
|
}
|
|
1982
2146
|
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.**";
|
|
1983
|
-
var
|
|
2147
|
+
var ROW13 = {
|
|
1984
2148
|
id: "direct-pr-create",
|
|
1985
2149
|
is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
|
|
1986
2150
|
reason: DIRECT_PR_CREATE_REASON,
|
|
1987
2151
|
decide: () => true
|
|
1988
2152
|
};
|
|
1989
|
-
var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW:
|
|
2153
|
+
var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW13, is_direct_pr_create };
|
|
1990
2154
|
|
|
1991
2155
|
// scripts/rules/worktree-guard.ts
|
|
1992
2156
|
var CHECKOUT_SUBCOMMAND = "checkout";
|
|
@@ -2048,14 +2212,14 @@ function is_unauthorized_worktree_change(command) {
|
|
|
2048
2212
|
return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
|
|
2049
2213
|
}
|
|
2050
2214
|
var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout -- <path>`, `git restore <path>`, a forced `git clean` and an unauthorized `git stash` discard work or mutate the repository-wide stash every work tree shares, and none is on the `.claude/settings.json` deny list (`operating-rules.md`). Do not run them on your own judgement. A commit goes through `pnpm josh git`; a stash pop goes through `pnpm josh stash:pop "<message>"`, never a positional `git stash pop` a shared stack lets another lane divert. A stash push is authorized only inside a documented flow and only with a findable message \u2014 `git stash push -u -m "<message>"` \u2014 which this row allows; a bare `git stash`, a message-less push, or `pop` / `apply` / `drop` is refused. **This rule fires on every occurrence, not once per run.**';
|
|
2051
|
-
var
|
|
2215
|
+
var ROW14 = {
|
|
2052
2216
|
id: "worktree-mutation",
|
|
2053
2217
|
is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
|
|
2054
2218
|
reason: WORKTREE_MUTATION_REASON,
|
|
2055
2219
|
decide: () => true
|
|
2056
2220
|
};
|
|
2057
2221
|
var worktree_guard = {
|
|
2058
|
-
ROW:
|
|
2222
|
+
ROW: ROW14,
|
|
2059
2223
|
WORKTREE_MUTATION_REASON,
|
|
2060
2224
|
is_unauthorized_worktree_change,
|
|
2061
2225
|
touches_worktree
|
|
@@ -2083,14 +2247,14 @@ function is_index_segment(segment) {
|
|
|
2083
2247
|
function is_index_mutation(command) {
|
|
2084
2248
|
return shell_segments.segments_of(command).some((segment) => is_index_segment(segment));
|
|
2085
2249
|
}
|
|
2086
|
-
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, which `CLAUDE.md` \u2192 Git Rules forbids on your own judgement (`prompts/collaboration-workflow/operating-rules.md` \u2192 "
|
|
2087
|
-
var
|
|
2250
|
+
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, which `CLAUDE.md` \u2192 Git Rules forbids on your own judgement (`prompts/collaboration-workflow/operating-rules.md` \u2192 "no-self-staging"). The `.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user instruction, run by the user in their own terminal. **This rule fires on every occurrence, not once per run.**';
|
|
2251
|
+
var ROW15 = {
|
|
2088
2252
|
id: "index-mutation",
|
|
2089
2253
|
is_trigger: bash_triggers.on_bash_command(is_index_mutation),
|
|
2090
2254
|
reason: INDEX_MUTATION_REASON,
|
|
2091
2255
|
decide: () => true
|
|
2092
2256
|
};
|
|
2093
|
-
var index_guard = { INDEX_MUTATION_REASON, ROW:
|
|
2257
|
+
var index_guard = { INDEX_MUTATION_REASON, ROW: ROW15, is_index_mutation };
|
|
2094
2258
|
|
|
2095
2259
|
// scripts/rules/protected-files.ts
|
|
2096
2260
|
import path5 from "node:path";
|
|
@@ -2110,7 +2274,6 @@ var CLAUDE_MD = "CLAUDE.md";
|
|
|
2110
2274
|
var PLUGIN_ID = "kit@kit";
|
|
2111
2275
|
var CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`;
|
|
2112
2276
|
var SECRETLINT_BIN = path4.join(NODE_MODULES, ".bin", "secretlint");
|
|
2113
|
-
var GIT_TIMEOUT_MS = 2e3;
|
|
2114
2277
|
var manifest_schema = z6.object({
|
|
2115
2278
|
name: z6.string().optional(),
|
|
2116
2279
|
dependencies: z6.record(z6.string(), z6.string()).optional(),
|
|
@@ -2145,7 +2308,7 @@ function is_plugin_declared(root) {
|
|
|
2145
2308
|
}
|
|
2146
2309
|
function configured_hooks_path(root) {
|
|
2147
2310
|
const value = git_spawn_sync.read(["-C", root, "config", "--get", "core.hooksPath"], {
|
|
2148
|
-
timeout:
|
|
2311
|
+
timeout: PROBE_TIMEOUT_MS
|
|
2149
2312
|
});
|
|
2150
2313
|
return value === "" ? void 0 : value;
|
|
2151
2314
|
}
|
|
@@ -2221,13 +2384,51 @@ var READ_TOOL = "Read";
|
|
|
2221
2384
|
var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
|
|
2222
2385
|
var ENVIRONMENT_FILE = ".env";
|
|
2223
2386
|
var SETTINGS_FILE = path5.join(".claude", "settings.json");
|
|
2387
|
+
var WHITESPACE3 = /\s+/u;
|
|
2388
|
+
var TOKEN_QUOTES = /["']/gu;
|
|
2389
|
+
var INPUT_REDIRECT = "<";
|
|
2390
|
+
var SHELL_READERS = /* @__PURE__ */ new Set([
|
|
2391
|
+
".",
|
|
2392
|
+
"awk",
|
|
2393
|
+
"bat",
|
|
2394
|
+
"cat",
|
|
2395
|
+
"cut",
|
|
2396
|
+
"grep",
|
|
2397
|
+
"head",
|
|
2398
|
+
"less",
|
|
2399
|
+
"more",
|
|
2400
|
+
"nl",
|
|
2401
|
+
"od",
|
|
2402
|
+
"rg",
|
|
2403
|
+
"sed",
|
|
2404
|
+
"sort",
|
|
2405
|
+
"source",
|
|
2406
|
+
"strings",
|
|
2407
|
+
"tail",
|
|
2408
|
+
"xxd"
|
|
2409
|
+
]);
|
|
2224
2410
|
function file_path_of(call) {
|
|
2225
2411
|
if (!json_value.is_record(call.input)) return "";
|
|
2226
2412
|
const value = call.input["file_path"];
|
|
2227
2413
|
return typeof value === "string" ? value : "";
|
|
2228
2414
|
}
|
|
2415
|
+
function is_environment_path(token) {
|
|
2416
|
+
return path5.basename(token.replaceAll(TOKEN_QUOTES, "")) === ENVIRONMENT_FILE;
|
|
2417
|
+
}
|
|
2418
|
+
function is_environment_read_segment(segment) {
|
|
2419
|
+
const words = segment.replaceAll(INPUT_REDIRECT, " < ").split(WHITESPACE3).filter((word) => word !== "");
|
|
2420
|
+
const is_redirected = words.some(
|
|
2421
|
+
(word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? "")
|
|
2422
|
+
);
|
|
2423
|
+
const [command = "", ...rest] = words;
|
|
2424
|
+
return is_redirected || SHELL_READERS.has(command) && rest.some((word) => is_environment_path(word));
|
|
2425
|
+
}
|
|
2426
|
+
function is_environment_shell_read(command) {
|
|
2427
|
+
return shell_segments.segments_of(command).some((segment) => is_environment_read_segment(segment));
|
|
2428
|
+
}
|
|
2229
2429
|
function is_environment_read(call) {
|
|
2230
|
-
|
|
2430
|
+
if (call.name === READ_TOOL) return is_environment_path(file_path_of(call));
|
|
2431
|
+
return bash_triggers.on_bash_command(is_environment_shell_read)(call);
|
|
2231
2432
|
}
|
|
2232
2433
|
function is_consumer_settings_edit(call, is_kit_consumer2) {
|
|
2233
2434
|
if (!EDIT_TOOLS2.has(call.name)) return false;
|
|
@@ -2238,14 +2439,14 @@ function is_consumer_settings_edit(call, is_kit_consumer2) {
|
|
|
2238
2439
|
function is_protected_file_call(call, is_kit_consumer2 = doctor_consumer.is_kit_consumer) {
|
|
2239
2440
|
return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer2);
|
|
2240
2441
|
}
|
|
2241
|
-
var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, so reading it puts a secret in the transcript \u2014 the scripts that need it load it in their own process. `.claude/settings.json` in a consumer repository is the deny list and the hooks every other rule rests on, so a run must not rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` \u2192 "Route distributed-doc / config changes upstream to kit"). Leave both files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
|
|
2242
|
-
var
|
|
2442
|
+
var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, so reading it \u2014 with the Read tool or a shell reader such as `cat` / `grep` / `source` \u2014 puts a secret in the transcript \u2014 the scripts that need it load it in their own process. `.claude/settings.json` in a consumer repository is the deny list and the hooks every other rule rests on, so a run must not rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` \u2192 "Route distributed-doc / config changes upstream to kit"). Leave both files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
|
|
2443
|
+
var ROW16 = {
|
|
2243
2444
|
id: "protected-file",
|
|
2244
2445
|
is_trigger: is_protected_file_call,
|
|
2245
2446
|
reason: PROTECTED_FILE_REASON,
|
|
2246
2447
|
decide: () => true
|
|
2247
2448
|
};
|
|
2248
|
-
var protected_files = { ROW:
|
|
2449
|
+
var protected_files = { ROW: ROW16, is_protected_file_call };
|
|
2249
2450
|
|
|
2250
2451
|
// scripts/rules/permission-guards.ts
|
|
2251
2452
|
var ROWS2 = [
|
|
@@ -2324,7 +2525,7 @@ function is_poll_loop(command) {
|
|
|
2324
2525
|
return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
|
|
2325
2526
|
}
|
|
2326
2527
|
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 \u2014 with a `sleep` or, worse, without one \u2014 and the loop does not know when the command finished. A regex that never matches waits to the harness block limit; one that matches a mid-run line exits too early \u2014 either way the command exit is not what ends the wait, and each pass spends a full-context round trip (joshuafolkken/kit#2371 measured about 45 minutes, 13% of six lanes, lost to exactly this). Do not poll the output file. A command known to take minutes \u2014 `pnpm josh gate`, `pnpm josh followup`, `pnpm josh format` \u2014 is started detached from the first with `run_in_background` set, and the harness re-invokes you when it exits: its completion notification is what ends the wait, on the task exit rather than a regex or the block limit. So issue the long command in the background and end the turn \u2014 do not arm a loop; the wake-up brings you back. To glance at interim output, `Read` the output file once, never in a sleep loop. If you are waiting on CI, `pnpm josh followup` waits on it for you. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every occurrence, not once per run.**';
|
|
2327
|
-
var
|
|
2528
|
+
var ROW17 = {
|
|
2328
2529
|
id: "poll-loop",
|
|
2329
2530
|
is_trigger: bash_triggers.on_bash_command(is_poll_loop),
|
|
2330
2531
|
reason: POLL_LOOP_REASON,
|
|
@@ -2332,7 +2533,7 @@ var ROW15 = {
|
|
|
2332
2533
|
};
|
|
2333
2534
|
var poll_loop = {
|
|
2334
2535
|
POLL_LOOP_REASON,
|
|
2335
|
-
ROW:
|
|
2536
|
+
ROW: ROW17,
|
|
2336
2537
|
is_poll_loop
|
|
2337
2538
|
};
|
|
2338
2539
|
|
|
@@ -2396,69 +2597,19 @@ var pre_gate_cut = {
|
|
|
2396
2597
|
uncut_lane_issue
|
|
2397
2598
|
};
|
|
2398
2599
|
|
|
2399
|
-
// scripts/rules/prior-comment-read.ts
|
|
2400
|
-
var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
|
|
2401
|
-
var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
|
|
2402
|
-
var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
|
|
2403
|
-
var A_NUMBER = /^\d+$/u;
|
|
2404
|
-
var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
|
|
2405
|
-
var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
|
|
2406
|
-
var ISSUE_READ_COMMAND = /\bissue:read\b/u;
|
|
2407
|
-
function add_issue_read_numbers(command, numbers) {
|
|
2408
|
-
const match = ISSUE_READ_ARGUMENTS.exec(command);
|
|
2409
|
-
if (match === null) return;
|
|
2410
|
-
const tokens = (match[1] ?? "").split(/\s+/u);
|
|
2411
|
-
for (const token of tokens) {
|
|
2412
|
-
if (A_NUMBER.test(token)) numbers.add(Number(token));
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
function issue_numbers_of(command) {
|
|
2416
|
-
const numbers = /* @__PURE__ */ new Set();
|
|
2417
|
-
for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
|
|
2418
|
-
const view = ISSUE_VIEW_NUMBER.exec(command);
|
|
2419
|
-
if (view !== null) numbers.add(Number(view[1]));
|
|
2420
|
-
add_issue_read_numbers(command, numbers);
|
|
2421
|
-
return numbers;
|
|
2422
|
-
}
|
|
2423
|
-
function fetches_comments(command) {
|
|
2424
|
-
return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
|
|
2425
|
-
}
|
|
2426
|
-
function reads_target_comments(command, target) {
|
|
2427
|
-
if (!fetches_comments(command)) return false;
|
|
2428
|
-
for (const number of issue_numbers_of(command)) {
|
|
2429
|
-
if (target.has(number)) return true;
|
|
2430
|
-
}
|
|
2431
|
-
return false;
|
|
2432
|
-
}
|
|
2433
|
-
function already_read_comments_for(tail, command) {
|
|
2434
|
-
const target = issue_numbers_of(command);
|
|
2435
|
-
if (target.size === 0) return false;
|
|
2436
|
-
return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
|
|
2437
|
-
}
|
|
2438
|
-
function already_read_for_call(tail, call) {
|
|
2439
|
-
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
2440
|
-
return already_read_comments_for(tail, time_shell.bash_command(call.input));
|
|
2441
|
-
}
|
|
2442
|
-
var prior_comment_read = {
|
|
2443
|
-
already_read_comments_for,
|
|
2444
|
-
already_read_for_call,
|
|
2445
|
-
fetches_comments,
|
|
2446
|
-
issue_numbers_of
|
|
2447
|
-
};
|
|
2448
|
-
|
|
2449
2600
|
// scripts/rules/raw-field-body.ts
|
|
2450
2601
|
var RAW_FIELD_BODY_AT = /(?:^|\s)(?:-f|--raw-field)[\s=]+['"]?body=['"]?@/u;
|
|
2451
2602
|
function posts_at_path_literally(command) {
|
|
2452
2603
|
return RAW_FIELD_BODY_AT.test(command);
|
|
2453
2604
|
}
|
|
2454
2605
|
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 sends the value verbatim \u2014 the string `@<path>` is posted as the comment, not the file it points at. `-F` / `--field` is the flag that reads the file, and the two spellings differ by one character while `gh` exits 0 and returns a comment URL either way, so the break is invisible until a person reads the issue (joshuafolkken/kit#2304 lost two park comments this way). Post an issue or PR comment with `pnpm josh issue:comment <N> --body-file <path>` \u2014 the one spelling, which cannot take the wrong flag. The rule and the safe forms are in `prompts/collaboration-workflow/shell-body.md`. This rule fires on every occurrence, not once per run.";
|
|
2455
|
-
var
|
|
2606
|
+
var ROW18 = {
|
|
2456
2607
|
id: "raw-field-body",
|
|
2457
2608
|
is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
|
|
2458
2609
|
reason: RAW_FIELD_BODY_REASON,
|
|
2459
2610
|
decide: () => true
|
|
2460
2611
|
};
|
|
2461
|
-
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW:
|
|
2612
|
+
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW18, posts_at_path_literally };
|
|
2462
2613
|
|
|
2463
2614
|
// scripts/rules/rule-body-guard.ts
|
|
2464
2615
|
var EDIT_TOOL = "Edit";
|
|
@@ -2529,7 +2680,7 @@ function runs_placement_oracle(command) {
|
|
|
2529
2680
|
(names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
|
|
2530
2681
|
);
|
|
2531
2682
|
}
|
|
2532
|
-
var
|
|
2683
|
+
var ROW19 = {
|
|
2533
2684
|
id: "rule-body",
|
|
2534
2685
|
is_trigger: writes_rule_prose,
|
|
2535
2686
|
reason: RULE_BODY_REASON,
|
|
@@ -2538,7 +2689,7 @@ var ROW17 = {
|
|
|
2538
2689
|
};
|
|
2539
2690
|
var rule_body_guard = {
|
|
2540
2691
|
RULE_BODY_REASON,
|
|
2541
|
-
ROW:
|
|
2692
|
+
ROW: ROW19,
|
|
2542
2693
|
answered_placement,
|
|
2543
2694
|
is_rule_document,
|
|
2544
2695
|
writes_rule_prose
|
|
@@ -2769,13 +2920,10 @@ var TYPE_CONFIGS = ["tsconfig.json", "jsconfig.json"];
|
|
|
2769
2920
|
var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
|
|
2770
2921
|
var object_schema = z7.record(z7.string(), z7.unknown());
|
|
2771
2922
|
function project_root(directory) {
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
current = parent;
|
|
2777
|
-
}
|
|
2778
|
-
return current;
|
|
2923
|
+
return ancestor_directories.nearest(
|
|
2924
|
+
directory,
|
|
2925
|
+
(candidate) => existsSync4(path7.join(candidate, PACKAGE_JSON))
|
|
2926
|
+
) ?? directory;
|
|
2779
2927
|
}
|
|
2780
2928
|
function is_ignored(entry) {
|
|
2781
2929
|
return entry.split(/[/\\]/u).some((part) => IGNORED_DIRS.has(part));
|
|
@@ -2969,12 +3117,12 @@ var test_declared_changed = { current_verdict, path_of, read_changed_paths_sync,
|
|
|
2969
3117
|
|
|
2970
3118
|
// scripts/rules/test-declared-commit.ts
|
|
2971
3119
|
function is_untested_commit(call) {
|
|
2972
|
-
if (!
|
|
3120
|
+
if (!run_tail_rule.is_push_step_call(call)) return false;
|
|
2973
3121
|
return test_declared_changed.current_verdict() === "required";
|
|
2974
3122
|
}
|
|
2975
3123
|
var REASON = '\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads the working tree as `required` (a changed file that is neither a test \u2014 `*.test.ts` / `*.e2e.ts` \u2014 nor a mechanically-exempt non-runtime path). `CLAUDE.md` \u2192 Code Change Rules requires a test for ALL code changes: a bug fix gets a regression test, a refactor gets tests pinning behavior first, logic gets a unit test, an observable UI/timing change gets an E2E. Add the declared test and reissue this commit. **If the change genuinely touches no executable runtime path** it is the pre-approved non-runtime exception \u2014 declare it in your Step 0 work summary with the reason it is exempt (non-executable config, a cosmetic asset swap) and reissue; the exception is a presentation, not a stop. Run `pnpm josh test:declared` to see the files it names. The procedure is `prompts/collaboration-workflow/rule-delivery.md` \u2192 "\u914D\u9001\u3055\u308C\u3066\u3044\u308B\u898F\u5247". This fires once per run, so reissuing will not repeat on the call in hand.';
|
|
2976
|
-
var
|
|
2977
|
-
var test_declared_commit = { REASON, ROW:
|
|
3124
|
+
var ROW20 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
|
|
3125
|
+
var test_declared_commit = { REASON, ROW: ROW20 };
|
|
2978
3126
|
|
|
2979
3127
|
// scripts/rules/third-party-write.ts
|
|
2980
3128
|
function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
|
|
@@ -3012,50 +3160,21 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner, r
|
|
|
3012
3160
|
);
|
|
3013
3161
|
}
|
|
3014
3162
|
var THIRD_PARTY_WRITE_REASON = '\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session \u2014 a tracker we do not own is Tier C, and every write there (Issue, comment, PR, Discussion, review) needs explicit current-turn user instruction; typing the target, or holding a correct diagnosis, is not that instruction (`CLAUDE.md` \u2192 "Third-party repositories are Tier C", `prompts/collaboration-workflow/upstream-interrupt.md`). Confirm the party with `pnpm josh repo:party <owner/repo>` \u2014 owner equality, computed not judged. A read (`-X GET`, or no field flag) passes untouched; only a write (`-f`/`-F`/`--field`/`--raw-field`/`--input`, or `-X POST|PATCH|PUT|DELETE`) is refused. Record the finding on our own Issue and draft the artifact for a person to post \u2014 do not publish it yourself. **This rule fires on every occurrence, not once per run.**';
|
|
3015
|
-
var
|
|
3163
|
+
var ROW21 = {
|
|
3016
3164
|
id: "third-party-write",
|
|
3017
3165
|
is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
|
|
3018
3166
|
reason: THIRD_PARTY_WRITE_REASON,
|
|
3019
3167
|
decide: () => true
|
|
3020
3168
|
};
|
|
3021
3169
|
var third_party_write = {
|
|
3022
|
-
ROW:
|
|
3170
|
+
ROW: ROW21,
|
|
3023
3171
|
THIRD_PARTY_WRITE_REASON,
|
|
3024
3172
|
writes_third_party
|
|
3025
3173
|
};
|
|
3026
3174
|
|
|
3027
|
-
// scripts/rules/
|
|
3028
|
-
var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
|
|
3029
|
-
var STAMP_PREFIX = "josh-rule-guard-";
|
|
3030
|
-
var { is_issue_filing, on_bash_command } = bash_triggers;
|
|
3175
|
+
// scripts/rules/wip-cap.ts
|
|
3031
3176
|
var WIP_CAP = 30;
|
|
3032
3177
|
var WIP_CAP_REASON = `\u26D4 backlog WIP cap: \`pnpm josh issue:file\` counts the target repository's open Issues before filing. With more than ${String(WIP_CAP)} open, close one first; nothing honestly closable means do not file \u2014 the command holds such a filing. Two filings are exempt and proceed while stating the overage \u2014 one the run is blocked by (\`--over-cap\`, or the \`tier-a\` / \`split\` route), and an interrupt (\`--route interrupt\`), decided by three tests rather than judgement: a verification answers wrongly, a documented workflow cannot complete, or data is lost or written outside the repository. Meeting none of the three, the finding is discretionary and waits. Both procedures are in \`prompts/collaboration-workflow/wip-cap.md\`. Reissue this call and let the command count \u2014 it fires once per run and cannot repeat on the call in hand.`;
|
|
3033
|
-
var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
|
|
3034
|
-
var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
|
|
3035
|
-
var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
|
|
3036
|
-
var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
|
|
3037
|
-
var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
|
|
3038
|
-
var NAMES_THE_BODY = /\bbody\b/u;
|
|
3039
|
-
function projects_away_body(segment) {
|
|
3040
|
-
const projection = FIELD_PROJECTION.exec(segment);
|
|
3041
|
-
return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
|
|
3042
|
-
}
|
|
3043
|
-
function is_body_read_segment(segment) {
|
|
3044
|
-
if (projects_away_body(segment)) return false;
|
|
3045
|
-
if (ISSUE_VIEW_COMMAND.test(segment)) return true;
|
|
3046
|
-
return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
|
|
3047
|
-
}
|
|
3048
|
-
function fetches_issue_comments(segment) {
|
|
3049
|
-
if (!FETCHES_COMMENTS.test(segment)) return false;
|
|
3050
|
-
return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
|
|
3051
|
-
}
|
|
3052
|
-
function is_body_only_issue_read(command) {
|
|
3053
|
-
const segments = shell_segments.segments_of(command);
|
|
3054
|
-
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
3055
|
-
return segments.some((segment) => is_body_read_segment(segment));
|
|
3056
|
-
}
|
|
3057
|
-
var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/issue-comments.md`. Every body-only read is refused until the comments are read.";
|
|
3058
|
-
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;
|
|
3059
3178
|
var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
|
|
3060
3179
|
var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
|
|
3061
3180
|
var OPEN_STATE = /--state[= ]open|state=open/u;
|
|
@@ -3066,9 +3185,19 @@ function counts_open_issues(command) {
|
|
|
3066
3185
|
return ISSUE_LISTING.test(segment) && OPEN_STATE.test(segment) || ISSUES_QUERY.test(segment);
|
|
3067
3186
|
});
|
|
3068
3187
|
}
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3188
|
+
var ROW22 = {
|
|
3189
|
+
id: "wip-cap",
|
|
3190
|
+
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
3191
|
+
reason: WIP_CAP_REASON,
|
|
3192
|
+
keeps: bash_triggers.on_bash_command(counts_open_issues)
|
|
3193
|
+
};
|
|
3194
|
+
var wip_cap = { ROW: ROW22, WIP_CAP, WIP_CAP_REASON };
|
|
3195
|
+
|
|
3196
|
+
// scripts/rules/delivered-rules.ts
|
|
3197
|
+
var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
|
|
3198
|
+
var STAMP_PREFIX = "josh-rule-guard-";
|
|
3199
|
+
var { is_issue_filing, on_bash_command } = bash_triggers;
|
|
3200
|
+
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;
|
|
3072
3201
|
var DELIVERED_RULES = [
|
|
3073
3202
|
// **Listed first, ahead of the filing trigger it overlaps** (joshuafolkken/kit#2122). A `gh api`
|
|
3074
3203
|
// that files an Issue into a repository we do not own trips both this row and `wip-cap` /
|
|
@@ -3083,12 +3212,7 @@ var DELIVERED_RULES = [
|
|
|
3083
3212
|
// `epic:bundle` itself. It claims the direct call only, so the rows below — which trigger on the
|
|
3084
3213
|
// `issue:file` call — never overlap it.
|
|
3085
3214
|
direct_filing.ROW,
|
|
3086
|
-
|
|
3087
|
-
id: "wip-cap",
|
|
3088
|
-
is_trigger: on_bash_command(is_issue_filing),
|
|
3089
|
-
reason: WIP_CAP_REASON,
|
|
3090
|
-
keeps: on_bash_command(counts_open_issues)
|
|
3091
|
-
},
|
|
3215
|
+
wip_cap.ROW,
|
|
3092
3216
|
// **Two more rows share the filing trigger** (joshuafolkken/kit#2119, joshuafolkken/kit#2213), listed
|
|
3093
3217
|
// after `wip-cap` so the backlog count still speaks first: `filing-cap` refuses every filing past
|
|
3094
3218
|
// the per-run ceiling, and `issue-fold` refuses a *second* filing the run has not folded. Each is a
|
|
@@ -3096,14 +3220,8 @@ var DELIVERED_RULES = [
|
|
|
3096
3220
|
// delivery is still correct one reissue later — so an unfolded, over-cap second filing is delivered
|
|
3097
3221
|
// `wip-cap`, then `filing-cap`, then `issue-fold` across its reissues.
|
|
3098
3222
|
filing_cap.ROW,
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
id: "issue-comments",
|
|
3102
|
-
is_trigger: on_bash_command(is_body_only_issue_read),
|
|
3103
|
-
reason: ISSUE_COMMENTS_REASON,
|
|
3104
|
-
already_satisfied: prior_comment_read.already_read_for_call,
|
|
3105
|
-
keeps: on_bash_command(reads_issue_comments)
|
|
3106
|
-
},
|
|
3223
|
+
issue_fold_rule.ROW,
|
|
3224
|
+
issue_comments.ROW,
|
|
3107
3225
|
{
|
|
3108
3226
|
id: "shell-body",
|
|
3109
3227
|
is_trigger: on_bash_command(is_shell_evaluated_body2),
|
|
@@ -3158,11 +3276,11 @@ var DELIVERED_RULES = [
|
|
|
3158
3276
|
// second inside one `fullrun` when round 2 fixes a finding in place.
|
|
3159
3277
|
{
|
|
3160
3278
|
id: "run-tail",
|
|
3161
|
-
is_trigger:
|
|
3162
|
-
reason:
|
|
3163
|
-
decide:
|
|
3164
|
-
keeps:
|
|
3165
|
-
reaches:
|
|
3279
|
+
is_trigger: run_tail_rule.is_foreground_push_step,
|
|
3280
|
+
reason: run_tail_rule.RUN_TAIL_REASON,
|
|
3281
|
+
decide: run_tail_rule.decide,
|
|
3282
|
+
keeps: run_tail_rule.is_backgrounded_push_step,
|
|
3283
|
+
reaches: run_tail_rule.is_push_step_call
|
|
3166
3284
|
},
|
|
3167
3285
|
// **The one row whose trigger consults the world beside the command**, because "is this a lane that
|
|
3168
3286
|
// has not cut" is not readable from the call: it is the working directory's own name and the cut
|
|
@@ -3205,22 +3323,14 @@ var DELIVERED_RULES = [
|
|
|
3205
3323
|
// "is this a dispatched lane child" is the dispatch mark against this checkout's own issue, read
|
|
3206
3324
|
// synchronously by `lane_child_marker.is_child_of`, and the command match runs first so an ordinary
|
|
3207
3325
|
// run pays nothing for it.
|
|
3326
|
+
// Its once-per-run disposition is stated beside the row in `lane-park.ts`.
|
|
3208
3327
|
//
|
|
3209
|
-
// **
|
|
3210
|
-
//
|
|
3211
|
-
//
|
|
3212
|
-
//
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
// path is the safe direction. `keeps` reads an actual label-application `Bash` command, never the
|
|
3216
|
-
// tail, so it is not fooled the same way; `reaches` stays `is_trigger`, since a compliant run issues
|
|
3217
|
-
// the same confirmation notify and belongs in the denominator.
|
|
3218
|
-
{
|
|
3219
|
-
id: "lane-park",
|
|
3220
|
-
is_trigger: on_bash_command(lane_park.is_unparked_stop),
|
|
3221
|
-
reason: lane_park.LANE_PARK_REASON,
|
|
3222
|
-
keeps: on_bash_command(lane_park.records_the_park)
|
|
3223
|
-
},
|
|
3328
|
+
// **A split child's park is refused first** (joshuafolkken/kit#3296): a lane child that promoted its
|
|
3329
|
+
// own Issue to an epic this run has nothing to park, so `lane-split-park` refuses the label write
|
|
3330
|
+
// and the confirmation notify onto that epic on every occurrence. It shares the notify with
|
|
3331
|
+
// `lane-park` and is listed first; a run that promoted nothing falls through to `lane-park`.
|
|
3332
|
+
lane_split_park.ROW,
|
|
3333
|
+
lane_park.ROW,
|
|
3224
3334
|
// **joshuafolkken/kit#2034's rule, one tool-call earlier** (joshuafolkken/kit#2201). `lane-park`
|
|
3225
3335
|
// fires on the `confirmation` notify a routed child reaches; a child that reaches for
|
|
3226
3336
|
// `AskUserQuestion` never gets there, because the harness ends its turn at the refused ask. This
|
|
@@ -3362,12 +3472,12 @@ function is_first_delivery(tail, call, delivered_at_ms, run2) {
|
|
|
3362
3472
|
return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
|
|
3363
3473
|
}
|
|
3364
3474
|
function delivery_decision(rule) {
|
|
3365
|
-
const { decide:
|
|
3366
|
-
if (
|
|
3475
|
+
const { decide: decide7, already_satisfied } = rule;
|
|
3476
|
+
if (decide7 === void 0 && already_satisfied === void 0) return is_first_delivery;
|
|
3367
3477
|
return function should_block(tail, call, delivered_at_ms, run2) {
|
|
3368
3478
|
if (already_satisfied?.(tail, call) === true) return false;
|
|
3369
|
-
if (
|
|
3370
|
-
return
|
|
3479
|
+
if (decide7 === void 0) return true;
|
|
3480
|
+
return decide7(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
|
|
3371
3481
|
};
|
|
3372
3482
|
}
|
|
3373
3483
|
function guard_of(rule) {
|
|
@@ -3400,8 +3510,8 @@ var delivered_rules = {
|
|
|
3400
3510
|
DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
|
|
3401
3511
|
FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
|
|
3402
3512
|
GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
|
|
3403
|
-
ISSUE_COMMENTS_REASON,
|
|
3404
|
-
ISSUE_FOLD_REASON:
|
|
3513
|
+
ISSUE_COMMENTS_REASON: issue_comments.ISSUE_COMMENTS_REASON,
|
|
3514
|
+
ISSUE_FOLD_REASON: issue_fold_rule.ISSUE_FOLD_REASON,
|
|
3405
3515
|
LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
|
|
3406
3516
|
LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
|
|
3407
3517
|
MEASURED_RULES,
|
|
@@ -3409,24 +3519,21 @@ var delivered_rules = {
|
|
|
3409
3519
|
POLL_LOOP_REASON: poll_loop.POLL_LOOP_REASON,
|
|
3410
3520
|
PRE_GATE_CUT_REASON: pre_gate_cut.PRE_GATE_CUT_REASON,
|
|
3411
3521
|
RAW_FIELD_BODY_REASON: raw_field_body.RAW_FIELD_BODY_REASON,
|
|
3412
|
-
RUN_TAIL_REASON:
|
|
3522
|
+
RUN_TAIL_REASON: run_tail_rule.RUN_TAIL_REASON,
|
|
3413
3523
|
SHELL_BODY_REASON: SHELL_BODY_REASON2,
|
|
3414
3524
|
SWITCH_ENV_KEY,
|
|
3415
3525
|
THIRD_PARTY_WRITE_REASON: third_party_write.THIRD_PARTY_WRITE_REASON,
|
|
3416
|
-
WIP_CAP,
|
|
3417
|
-
WIP_CAP_REASON,
|
|
3526
|
+
WIP_CAP: wip_cap.WIP_CAP,
|
|
3527
|
+
WIP_CAP_REASON: wip_cap.WIP_CAP_REASON,
|
|
3418
3528
|
WORKTREE_MUTATION_REASON: worktree_guard.WORKTREE_MUTATION_REASON,
|
|
3419
3529
|
delivery,
|
|
3420
3530
|
delivery_path,
|
|
3421
|
-
is_body_only_issue_read,
|
|
3531
|
+
is_body_only_issue_read: issue_comments.is_body_only_issue_read,
|
|
3422
3532
|
is_enabled,
|
|
3423
3533
|
is_issue_filing
|
|
3424
3534
|
};
|
|
3425
3535
|
|
|
3426
|
-
// scripts/run/run-
|
|
3427
|
-
import { z as z12 } from "zod";
|
|
3428
|
-
|
|
3429
|
-
// scripts/run/run-progress-read.ts
|
|
3536
|
+
// scripts/run/progress/run-progress-read.ts
|
|
3430
3537
|
import { loadavg } from "node:os";
|
|
3431
3538
|
|
|
3432
3539
|
// scripts/epic/epic-busy.ts
|
|
@@ -3774,7 +3881,7 @@ function to_advice(verdict, has_work_to_stash) {
|
|
|
3774
3881
|
if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
|
|
3775
3882
|
return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
|
|
3776
3883
|
}
|
|
3777
|
-
function
|
|
3884
|
+
function decide5(traces) {
|
|
3778
3885
|
const verdict = to_verdict(traces);
|
|
3779
3886
|
const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
|
|
3780
3887
|
return {
|
|
@@ -3873,11 +3980,11 @@ async function read_traces(request) {
|
|
|
3873
3980
|
}
|
|
3874
3981
|
async function check(request) {
|
|
3875
3982
|
run_issue_number.require_issue_number(request.issue);
|
|
3876
|
-
return
|
|
3983
|
+
return decide5(await read_traces(request));
|
|
3877
3984
|
}
|
|
3878
3985
|
var run_liveness = {
|
|
3879
3986
|
check,
|
|
3880
|
-
decide:
|
|
3987
|
+
decide: decide5,
|
|
3881
3988
|
describe_agent_state,
|
|
3882
3989
|
read_child_settled,
|
|
3883
3990
|
read_output_frozen,
|
|
@@ -4027,7 +4134,7 @@ function to_advice2(verdict, issue, tree) {
|
|
|
4027
4134
|
if (verdict === PARK_VERDICT) return PARK_ADVICE;
|
|
4028
4135
|
return CLEAN_ADVICE;
|
|
4029
4136
|
}
|
|
4030
|
-
function
|
|
4137
|
+
function decide6(tree, child, issue) {
|
|
4031
4138
|
const verdict = to_verdict2(tree, child);
|
|
4032
4139
|
return {
|
|
4033
4140
|
advice: to_advice2(verdict, issue, tree),
|
|
@@ -4128,8 +4235,8 @@ async function read_child_state(issue) {
|
|
|
4128
4235
|
async function check2(issue) {
|
|
4129
4236
|
require_issue_number(issue);
|
|
4130
4237
|
const tree = await read_tree_state();
|
|
4131
|
-
if (needs_reclaim(tree)) return
|
|
4132
|
-
return
|
|
4238
|
+
if (needs_reclaim(tree)) return decide6(tree, NO_CHILD_WORK, issue);
|
|
4239
|
+
return decide6(tree, await read_child_state(issue), issue);
|
|
4133
4240
|
}
|
|
4134
4241
|
var run_preflight = {
|
|
4135
4242
|
CLEAN_VERDICT,
|
|
@@ -4137,13 +4244,13 @@ var run_preflight = {
|
|
|
4137
4244
|
MERGED_PR,
|
|
4138
4245
|
STASH_LABEL_PREFIX,
|
|
4139
4246
|
check: check2,
|
|
4140
|
-
decide:
|
|
4247
|
+
decide: decide6,
|
|
4141
4248
|
read_child_state,
|
|
4142
4249
|
read_pr_state,
|
|
4143
4250
|
to_pr_state
|
|
4144
4251
|
};
|
|
4145
4252
|
|
|
4146
|
-
// scripts/run/run-progress-read.ts
|
|
4253
|
+
// scripts/run/progress/run-progress-read.ts
|
|
4147
4254
|
var { mark: mark2, parse_stamp: parse_stamp2, read_last_report: read_last_report2 } = run_progress_clock;
|
|
4148
4255
|
var FIRST_LOAD_AVERAGE = 0;
|
|
4149
4256
|
var IDLE_READ = { kind: "idle" };
|
|
@@ -4234,6 +4341,9 @@ var run_progress_read = {
|
|
|
4234
4341
|
stamp_target
|
|
4235
4342
|
};
|
|
4236
4343
|
|
|
4344
|
+
// scripts/run/run-watcher-hook.ts
|
|
4345
|
+
import { z as z12 } from "zod";
|
|
4346
|
+
|
|
4237
4347
|
// scripts/run/run-watcher-guard.ts
|
|
4238
4348
|
var WATCHER_TICK_MS = 3e4;
|
|
4239
4349
|
var TICK_MULTIPLIER = 3;
|
|
@@ -4243,7 +4353,7 @@ var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
|
|
|
4243
4353
|
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`;
|
|
4244
4354
|
var OK_RESULT = { kind: "ok" };
|
|
4245
4355
|
async function owes_watcher() {
|
|
4246
|
-
if (!await run_headless.is_backlog_parent()) return false;
|
|
4356
|
+
if (run_headless.is_headless() || !await run_headless.is_backlog_parent()) return false;
|
|
4247
4357
|
return await lane_registry.has_lanes_in_flight();
|
|
4248
4358
|
}
|
|
4249
4359
|
async function check3(life_target) {
|
|
@@ -4362,7 +4472,7 @@ function is_runtime_file(file_path, root) {
|
|
|
4362
4472
|
if (relative.startsWith(PARENT_PREFIX) || path9.isAbsolute(relative)) return false;
|
|
4363
4473
|
return !test_declared_logic.is_exempt(relative.split(path9.sep).join("/"));
|
|
4364
4474
|
}
|
|
4365
|
-
function
|
|
4475
|
+
function is_candidate2(raw_payload, root) {
|
|
4366
4476
|
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4367
4477
|
if (payload === void 0 || !EDIT_TOOLS3.has(payload.tool_name)) return void 0;
|
|
4368
4478
|
const has_runtime_file = edited_paths(payload.tool_input).some(
|
|
@@ -4371,7 +4481,7 @@ function is_candidate(raw_payload, root) {
|
|
|
4371
4481
|
return has_runtime_file ? payload.transcript_path : void 0;
|
|
4372
4482
|
}
|
|
4373
4483
|
function notice(raw_payload, root = process.cwd()) {
|
|
4374
|
-
const transcript =
|
|
4484
|
+
const transcript = is_candidate2(raw_payload, root);
|
|
4375
4485
|
if (transcript === void 0) return void 0;
|
|
4376
4486
|
const target = STAMP.path(transcript);
|
|
4377
4487
|
if (STAMP.last_ms(target) !== hook_decision.NEVER_MS) return void 0;
|