@joshuafolkken/kit 1.1455.0 → 1.1514.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/settings.json +6 -6
- package/.claude/skills/dependency-update/SKILL.md +26 -55
- package/.claude/skills/epic-commands/SKILL.md +83 -156
- package/.claude/skills/epic-commands/epic-bundle.md +51 -79
- package/.claude/skills/epic-commands/execution-waves.md +43 -81
- package/.claude/skills/verify-ui/SKILL.md +24 -50
- package/.claude/skills/workflow-commands/SKILL.md +7 -9
- package/.claude/skills/workflow-commands/background-commands.md +31 -46
- package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
- package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
- package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
- package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
- package/.claude/skills/workflow-commands/backlogrun.md +31 -50
- package/.claude/skills/workflow-commands/chain-rule.md +31 -40
- package/.claude/skills/workflow-commands/delegation.md +12 -8
- package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
- package/.claude/skills/workflow-commands/followup.md +55 -77
- package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
- package/.claude/skills/workflow-commands/fullrun.md +4 -6
- package/.claude/skills/workflow-commands/halfrun.md +3 -7
- package/.claude/skills/workflow-commands/into-target.md +1 -2
- package/.claude/skills/workflow-commands/issue-comments.md +43 -60
- package/.claude/skills/workflow-commands/issue-scout.md +3 -2
- package/.claude/skills/workflow-commands/kickoff.md +65 -74
- package/.claude/skills/workflow-commands/latest-gate.md +2 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
- package/.claude/skills/workflow-commands/observation-filing.md +59 -118
- package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
- package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
- package/.claude/skills/workflow-commands/prerequisite.md +11 -17
- package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
- package/.claude/skills/workflow-commands/prrun.md +3 -5
- package/.claude/skills/workflow-commands/retrospective.md +35 -54
- package/.claude/skills/workflow-commands/split-assessment.md +32 -58
- package/.claude/skills/workflow-commands/target-repository.md +1 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
- package/.codex/hooks.json +6 -6
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/publish.yml +2 -2
- package/CLAUDE.md +7 -6
- package/cspell/index.yaml +7 -0
- package/dist/CLAUDE.md +7 -6
- package/dist/commands/document-section-cli.js +67 -34
- package/dist/hooks/batch-guard.js +6 -6
- package/dist/hooks/chunk-4KKD2UTK.js +66 -0
- package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
- package/dist/hooks/chunk-7DSBJVFH.js +50 -0
- package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
- package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
- package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
- package/dist/hooks/chunk-GP4IEC4O.js +20 -0
- package/dist/hooks/chunk-JP45SOCJ.js +56 -0
- package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
- package/dist/hooks/chunk-M4HGZV7U.js +83 -0
- package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
- package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
- package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
- package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
- package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
- package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
- package/dist/hooks/chunk-UPUZF3AN.js +279 -0
- package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
- package/dist/hooks/codex-hook-adapter.js +17 -14
- package/dist/hooks/duplicate-read-guard.js +7 -7
- package/dist/hooks/format-edited.js +5 -4
- package/dist/hooks/inputs.json +302 -247
- package/dist/hooks/investigation-guard.js +8 -11
- package/dist/hooks/pretool-guard.js +16 -13
- package/dist/hooks/session-lang.js +372 -26
- package/dist/hooks/stop-guard.js +2593 -189
- package/dist/josh.js +2128 -282
- package/dist/managed-marker/index.js +1 -2
- package/dist/version/index.js +15 -12
- package/eslint/base.js +2 -1
- package/eslint/rules/unicorn.js +16 -0
- package/lefthook/base.yml +5 -3
- package/package.json +14 -12
- package/prompts/coding-standards.md +12 -22
- package/prompts/collaboration-workflow/file-edits.md +25 -45
- package/prompts/collaboration-workflow/gh-rest.md +19 -36
- package/prompts/collaboration-workflow/glossary.md +5 -7
- package/prompts/collaboration-workflow/issue-citation.md +6 -14
- package/prompts/collaboration-workflow/issue-template.md +40 -32
- package/prompts/collaboration-workflow/lane-instructions.md +35 -0
- package/prompts/collaboration-workflow/operating-rules.md +33 -46
- package/prompts/collaboration-workflow/output-bounds.md +15 -15
- package/prompts/collaboration-workflow/overview.md +14 -19
- package/prompts/collaboration-workflow/plan-comment.md +13 -13
- package/prompts/collaboration-workflow/principles.md +57 -54
- package/prompts/collaboration-workflow/proposal-request.md +1 -9
- package/prompts/collaboration-workflow/report-format.md +41 -49
- package/prompts/collaboration-workflow/residency.md +15 -11
- package/prompts/collaboration-workflow/rule-delivery.md +7 -15
- package/prompts/collaboration-workflow/shell-body.md +10 -20
- package/prompts/collaboration-workflow/turn-batching.md +8 -14
- package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
- package/prompts/collaboration-workflow/wip-cap.md +26 -32
- package/prompts/collaboration-workflow.md +25 -25
- package/prompts/refactoring.md +33 -43
- package/prompts/review-rubric.md +84 -129
- package/prompts/review.md +110 -141
- package/prompts/sonar-hotspot-handling.md +22 -50
- package/prompts/testing-guide.md +48 -99
- package/scripts/adopt/adopt-toolkits.ts +2 -4
- package/scripts/agent/agent-argv.ts +20 -4
- package/scripts/agent/agent-exit-record.ts +23 -1
- package/scripts/agent/agent-role-profile.ts +101 -45
- package/scripts/agent/agent-safe-text.ts +60 -0
- package/scripts/agent/claude-agent-argv.ts +14 -0
- package/scripts/agent/codex-agent-argv.ts +9 -2
- package/scripts/agent/lane-instructions.ts +144 -0
- package/scripts/backlog/backlog-budget-cli.ts +1 -2
- package/scripts/backlog/backlog-budget.ts +4 -4
- package/scripts/backlog/backlog-drive-carried.ts +26 -0
- package/scripts/backlog/backlog-drive-cli.ts +10 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -5
- package/scripts/backlog/backlog-drive-restore.ts +28 -2
- package/scripts/backlog/backlog-drive-seats.ts +27 -0
- package/scripts/backlog/backlog-drive.ts +11 -6
- package/scripts/backlog/backlog-occupied.ts +56 -0
- package/scripts/backlog/backlog-ready.ts +59 -15
- package/scripts/behavior/behavior-rules.ts +3 -2
- package/scripts/cost-runtime/codex-usage.ts +4 -2
- package/scripts/cost-runtime/cost-cli.ts +1 -2
- package/scripts/delegation/duplicate-read-guard.ts +2 -2
- package/scripts/delegation/investigation-guard.ts +15 -65
- package/scripts/delegation/investigation-reads.ts +1 -2
- package/scripts/discovery/repo-discovery.ts +4 -2
- package/scripts/discovery/repo-origin.ts +2 -4
- package/scripts/doctor/doctor-logic.ts +6 -3
- package/scripts/document/document-byte-budget.ts +28 -27
- package/scripts/document/document-byte-check.ts +1 -2
- package/scripts/document/document-section-cli.ts +17 -5
- package/scripts/document/entry-read-budget.ts +3 -3
- package/scripts/document/read-set-cli.ts +4 -1
- package/scripts/epic/epic-audit-orphans.ts +5 -8
- package/scripts/epic/epic-audit-rationale.ts +8 -2
- package/scripts/epic/epic-audit.ts +52 -1
- package/scripts/epic/epic-bundle-cli.ts +1 -2
- package/scripts/epic/epic-bundle-evidence.ts +49 -26
- package/scripts/epic/epic-bundle-referenced.ts +14 -3
- package/scripts/epic/epic-bundle.ts +17 -9
- package/scripts/epic/epic-chains.ts +6 -2
- package/scripts/epic/epic-close-comment.ts +1 -2
- package/scripts/followup/git-followup-workflow.ts +19 -1
- package/scripts/gate/check-inputs.ts +129 -0
- package/scripts/gate/check-reuse.ts +162 -0
- package/scripts/gate/core-budget.ts +93 -44
- package/scripts/gate/core-wait.ts +130 -0
- package/scripts/gate/free-cores.ts +49 -0
- package/scripts/gate/gate-ledger.ts +17 -3
- package/scripts/gate/gate-plan.ts +23 -1
- package/scripts/gate/gate-report.ts +15 -2
- package/scripts/gate/gate-run-steps.ts +150 -0
- package/scripts/gate/held-mark.ts +55 -0
- package/scripts/gate/machine-capacity.ts +70 -11
- package/scripts/gate/pre-commit-type-check.ts +14 -1
- package/scripts/gate/verification-gate.ts +65 -127
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +24 -2
- package/scripts/gh/git-pr.ts +19 -3
- package/scripts/git/git-common-directory.ts +3 -3
- package/scripts/git/git-diff-reads.ts +16 -9
- package/scripts/git/git-execa-error.ts +1 -2
- package/scripts/git/main-merge-regenerate.ts +78 -0
- package/scripts/git/main-merge.ts +14 -5
- package/scripts/hooks/batch-guard.ts +5 -8
- package/scripts/hooks/format-edited-file.ts +10 -4
- package/scripts/hooks/pre-commit-lint.ts +91 -0
- package/scripts/hooks/pre-push-unit.ts +15 -4
- package/scripts/hooks/pretool-guard.ts +48 -79
- package/scripts/hooks/reserved-run.ts +1 -0
- package/scripts/hooks/run-hook.sh +10 -12
- package/scripts/hooks/step-zero-notice.ts +3 -2
- package/scripts/hooks/stop-actions.ts +204 -0
- package/scripts/hooks/stop-guard.ts +62 -21
- package/scripts/init/hook-command-rewrite.ts +6 -2
- package/scripts/init/hook-launch.ts +12 -7
- package/scripts/init/init-logic-deploy-vps.ts +4 -2
- package/scripts/issue/behavior-change-lint.ts +12 -6
- package/scripts/issue/expected-effect-lint.ts +59 -0
- package/scripts/issue/issue-cite-cli.ts +35 -15
- package/scripts/issue/issue-classification.ts +1 -1
- package/scripts/issue/issue-lint-cli.ts +5 -3
- package/scripts/issue/issue-scout-cli.ts +22 -16
- package/scripts/issue/markdown-section.ts +19 -5
- package/scripts/josh/command-usage.ts +110 -0
- package/scripts/josh/file-map-stamp.ts +1 -3
- package/scripts/josh/hook-decision.ts +17 -2
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +55 -9
- package/scripts/josh/josh-commands-ai.ts +13 -0
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-hooks.ts +6 -0
- package/scripts/josh/josh-commands-lane.ts +10 -1
- package/scripts/josh/josh-commands-workflow.ts +5 -0
- package/scripts/josh/josh-logic.ts +6 -15
- package/scripts/josh/josh-usage-automation.ts +121 -0
- package/scripts/josh/josh-usage-backlog.ts +180 -0
- package/scripts/josh/josh-usage-developer.ts +230 -0
- package/scripts/josh/josh-usage-hooks.ts +38 -0
- package/scripts/josh/josh-usage-issue.ts +205 -0
- package/scripts/josh/josh-usage-review.ts +101 -0
- package/scripts/josh/josh-usage-run.ts +252 -0
- package/scripts/josh/josh-usage-session.ts +238 -0
- package/scripts/josh/josh-usage-workflow.ts +212 -0
- package/scripts/josh/josh.ts +14 -5
- package/scripts/josh/package-directory.ts +20 -0
- package/scripts/lane/lane-await.ts +3 -2
- package/scripts/lane/lane-capacity.ts +8 -2
- package/scripts/lane/lane-check-stats.ts +56 -0
- package/scripts/lane/lane-child-invocation.ts +49 -5
- package/scripts/lane/lane-dispatch.ts +3 -3
- package/scripts/lane/lane-guard-policy.ts +5 -5
- package/scripts/lane/lane-handoff.ts +8 -1
- package/scripts/lane/lane-install.ts +12 -4
- package/scripts/lane/lane-ledger.ts +52 -1
- package/scripts/lane/lane-limit-cli.ts +8 -9
- package/scripts/lane/lane-load.ts +9 -2
- package/scripts/lane/lane-measure-cli.ts +4 -0
- package/scripts/lane/lane-parked.ts +18 -0
- package/scripts/lane/lane-preserve.ts +29 -1
- package/scripts/lane/lane-process-load.ts +125 -0
- package/scripts/lane/lane-process-stats.ts +93 -0
- package/scripts/lane/lane-registry.ts +17 -10
- package/scripts/lane/lane-restart.ts +5 -26
- package/scripts/lane/lane-session-action.ts +146 -0
- package/scripts/lane/lane-session-broker.ts +68 -125
- package/scripts/lane/lane-spent.ts +14 -3
- package/scripts/lane/lane-stats.ts +1 -0
- package/scripts/lane/lane-withdraw-cli.ts +71 -0
- package/scripts/lane/lane-withdraw.ts +182 -0
- package/scripts/lint/format.ts +4 -4
- package/scripts/lint/lint-parallel.ts +10 -3
- package/scripts/lint/lint-related-scope.ts +4 -1
- package/scripts/managed-marker/managed-marker-logic.ts +1 -2
- package/scripts/notify/telegram-test.ts +10 -5
- package/scripts/overrides/lockfile-overrides.ts +1 -3
- package/scripts/propagate/propagate.ts +1 -2
- package/scripts/release/release-progress.ts +3 -2
- package/scripts/report/report-format-reference.ts +2 -0
- package/scripts/report/report-lint.ts +7 -4
- package/scripts/review/acceptance-map-check.ts +61 -0
- package/scripts/review/acceptance-map.ts +205 -0
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-record-cli.ts +6 -2
- package/scripts/rules/agent-model.ts +59 -0
- package/scripts/rules/delivered-rules.ts +11 -2
- package/scripts/rules/destructive-command.ts +6 -9
- package/scripts/rules/direct-filing.ts +3 -7
- package/scripts/rules/early-heartbeat.ts +7 -18
- package/scripts/rules/file-body.ts +6 -13
- package/scripts/rules/filing-cap.ts +4 -6
- package/scripts/rules/firing-point.ts +6 -5
- package/scripts/rules/git-force.ts +5 -9
- package/scripts/rules/guard-remedy.ts +117 -0
- package/scripts/rules/implementation-cut.ts +37 -35
- package/scripts/rules/index-guard.ts +4 -7
- package/scripts/rules/issue-comments.ts +67 -16
- package/scripts/rules/josh-git-bare.ts +3 -6
- package/scripts/rules/lane-background.ts +10 -21
- package/scripts/rules/lane-carry-conflict.ts +7 -17
- package/scripts/rules/lane-interactive-ask.ts +6 -11
- package/scripts/rules/lane-park.ts +5 -13
- package/scripts/rules/lane-split-park.ts +2 -7
- package/scripts/rules/lane-switch-main.ts +9 -12
- package/scripts/rules/last-prompt.ts +50 -5
- package/scripts/rules/oracle-consulted.ts +4 -5
- package/scripts/rules/oracle-list-cli.ts +7 -0
- package/scripts/rules/piped-verification.ts +84 -32
- package/scripts/rules/poll-loop.ts +7 -17
- package/scripts/rules/pre-gate-cut.ts +7 -21
- package/scripts/rules/prior-comment-read.ts +7 -5
- package/scripts/rules/protected-files.ts +5 -9
- package/scripts/rules/raw-field-body.ts +3 -7
- package/scripts/rules/rule-body-guard.ts +4 -11
- package/scripts/rules/rule-list.ts +4 -1
- package/scripts/rules/rule-registry-rows.ts +10 -0
- package/scripts/rules/rule-registry.ts +7 -4
- package/scripts/rules/rule-rewrite.ts +114 -0
- package/scripts/rules/rule-value.ts +7 -1
- package/scripts/rules/run-tail-rule.ts +6 -22
- package/scripts/rules/shell-body-trigger.ts +4 -6
- package/scripts/rules/shell-segments.ts +22 -0
- package/scripts/rules/stop-decision.ts +77 -0
- package/scripts/rules/stop-rules.ts +164 -77
- package/scripts/rules/tail-commands.ts +15 -1
- package/scripts/rules/test-declared-commit.ts +4 -10
- package/scripts/rules/third-party-write.ts +5 -10
- package/scripts/rules/worktree-guard.ts +5 -12
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-closed.ts +8 -2
- package/scripts/run/board/run-board-github.ts +19 -3
- package/scripts/run/board/run-board-header.ts +4 -1
- package/scripts/run/board/run-board-idle.ts +31 -0
- package/scripts/run/board/run-board-labels.ts +2 -0
- package/scripts/run/board/run-board-layout.ts +1 -1
- package/scripts/run/board/run-board-read.ts +25 -12
- package/scripts/run/board/run-board-reason.ts +17 -4
- package/scripts/run/board/run-board-render-legend.ts +1 -0
- package/scripts/run/board/run-board-render-slot.ts +1 -1
- package/scripts/run/board/run-board-render-waits.ts +41 -0
- package/scripts/run/board/run-board-render.ts +6 -1
- package/scripts/run/board/run-board-row-yield.ts +1 -0
- package/scripts/run/board/run-board-state.ts +9 -0
- package/scripts/run/board/run-board-status.ts +89 -23
- package/scripts/run/board/run-board-thanks.ts +7 -2
- package/scripts/run/board/run-board-tick.ts +37 -8
- package/scripts/run/carry/run-carry-args.ts +1 -3
- package/scripts/run/carry/run-carry-conversation.ts +7 -2
- package/scripts/run/cut/run-cut-cli.ts +18 -22
- package/scripts/run/cut/run-cut-decline.ts +72 -0
- package/scripts/run/cut/run-cut-ready.ts +74 -0
- package/scripts/run/cut/run-cut-report.ts +29 -0
- package/scripts/run/cut/run-cut.ts +10 -1
- package/scripts/run/detached-launch.ts +23 -4
- package/scripts/run/entry/run-entry-cli.ts +8 -1
- package/scripts/run/event/run-event-render.ts +1 -0
- package/scripts/run/event/run-event-scope.ts +19 -1
- package/scripts/run/event/run-event-stream-emit.ts +13 -6
- package/scripts/run/event/run-event-stream.ts +4 -0
- package/scripts/run/hold/run-hold-cli.ts +14 -3
- package/scripts/run/hold/run-hold-session.ts +46 -0
- package/scripts/run/hold/run-hold.ts +13 -4
- package/scripts/run/launch-priority.ts +32 -0
- package/scripts/run/merge/run-merge-cli.ts +16 -42
- package/scripts/run/merge/run-merge-ship.ts +59 -1
- package/scripts/run/merge/run-merge-signals.ts +99 -0
- package/scripts/run/merge/run-merge-steps.ts +11 -1
- package/scripts/run/merge/run-merge.ts +22 -9
- package/scripts/run/run-ending.ts +31 -3
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +5 -10
- package/scripts/run/run-poll-hook.ts +157 -0
- package/scripts/run/run-preflight.ts +1 -2
- package/scripts/run/run-step-cli.ts +22 -15
- package/scripts/run/run-step-section.ts +85 -0
- package/scripts/run/run-stop-notify.ts +1 -2
- package/scripts/run/run-watcher-guard.ts +6 -3
- package/scripts/run/ship/run-ship-cli.ts +5 -12
- package/scripts/run/ship/run-ship-detach.ts +9 -0
- package/scripts/run/ship/run-ship-next.ts +10 -2
- package/scripts/run/ship/run-ship-overlap.ts +195 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
- package/scripts/run/ship/run-ship-preflight.ts +28 -2
- package/scripts/run/ship/run-ship-return.ts +31 -7
- package/scripts/run/ship/run-ship-review-steps.ts +25 -14
- package/scripts/run/ship/run-ship-review.ts +15 -2
- package/scripts/run/ship/run-ship-steps.ts +1 -1
- package/scripts/run/ship/run-ship-stop-context.ts +206 -0
- package/scripts/run/ship/run-ship-sync.ts +14 -6
- package/scripts/run/tidy/run-tidy-cli.ts +3 -3
- package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
- package/scripts/safe-chain/project-config.ts +7 -2
- package/scripts/sync/sync-hook-safety.ts +8 -2
- package/scripts/test/test-e2e-guard.ts +30 -5
- package/scripts/test/test-unit-guard.ts +16 -1
- package/scripts/test/unit-worker-share.ts +26 -3
- package/scripts/time-runtime/time-batch-guard.ts +3 -3
- package/scripts/time-runtime/time-hook-transcript.ts +3 -2
- package/scripts/time-runtime/time-shell.ts +39 -4
- package/scripts/time-runtime/time-spans.ts +1 -0
- package/scripts/time-runtime/time-tool-call.ts +7 -0
- package/scripts/version/effective-upstream.ts +3 -4
- package/scripts/version/kit-version-config.ts +8 -2
- package/scripts/version/latest-corepack.ts +1 -2
- package/scripts/version/release-hold.ts +11 -4
- package/scripts/version/version-commands.ts +12 -4
- package/templates/gitignore +6 -2
- package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
- package/dist/hooks/chunk-UT7YC3N2.js +0 -56
|
@@ -17,11 +17,17 @@ import { run_board_setup, type SetupRead } from './run-board-setup'
|
|
|
17
17
|
// statuses are laid over is the slow GitHub half, and so is what a child that left the open listing
|
|
18
18
|
// answers when read closed.
|
|
19
19
|
|
|
20
|
-
type ItemState =
|
|
20
|
+
type ItemState =
|
|
21
|
+
'running' | 'merged' | 'parked' | 'done' | 'waiting' | 'human' | 'stopped' | 'withdrawn'
|
|
21
22
|
|
|
22
23
|
interface ItemStatus {
|
|
23
24
|
state: ItemState
|
|
25
|
+
// When the child first launched in this run — a re-dispatch or a restart does not move it, so the
|
|
26
|
+
// row's time is the lane's whole time.
|
|
24
27
|
started_ms?: number | undefined
|
|
28
|
+
// The child's newest launch, present only where it launched again after `started_ms` — what the
|
|
29
|
+
// GitHub read is weighed against.
|
|
30
|
+
launched_ms?: number | undefined
|
|
25
31
|
ended_ms?: number | undefined
|
|
26
32
|
// The seat-free lane label (`lane <N>`) of a running child, when it holds one — the running test reads
|
|
27
33
|
// it; the board does not draw it.
|
|
@@ -61,22 +67,40 @@ const ENDING_STATES: Readonly<Partial<Record<string, ItemState>>> = {
|
|
|
61
67
|
[KIND.PARK]: 'parked',
|
|
62
68
|
[KIND.SPLIT]: 'done',
|
|
63
69
|
[KIND.STOP]: 'done',
|
|
70
|
+
[KIND.WITHDRAW]: 'withdrawn',
|
|
64
71
|
}
|
|
65
72
|
// A cut or a resume says the child is still running, on whatever phase it had reached.
|
|
66
73
|
const CONTINUING_KINDS: ReadonlySet<string> = new Set([KIND.CUT, KIND.RESUME])
|
|
67
|
-
// A person's restart
|
|
74
|
+
// A person's restart, and a re-dispatch after an outage, start the child over in a fresh session, so
|
|
75
|
+
// its track starts over too; its time does not (`launched`).
|
|
68
76
|
const LAUNCH_KINDS: ReadonlySet<string> = new Set([KIND.CHILD_LAUNCH, KIND.RESTART])
|
|
77
|
+
// What the GitHub read may still settle: a running child, and one whose lane has no child left in it —
|
|
78
|
+
// a child that merged and then died is drawn merged, not stopped.
|
|
79
|
+
const UNSETTLED_STATES: ReadonlySet<ItemState> = new Set(['running', 'stopped'])
|
|
80
|
+
// A parked child waits on a person, who may close its issue after the park; only a closed read settles
|
|
81
|
+
// it again, so one still open stays parked whatever its labels say (joshuafolkken/kit#3958).
|
|
82
|
+
const PARKED_STATE: ItemState = 'parked'
|
|
69
83
|
|
|
70
84
|
function moment(event: RunEvent): number {
|
|
71
85
|
return Date.parse(event.at)
|
|
72
86
|
}
|
|
73
87
|
|
|
74
|
-
|
|
88
|
+
// The newest launch carried on, where the child launched more than once.
|
|
89
|
+
function relaunch_of(status: ItemStatus | undefined): Pick<ItemStatus, 'launched_ms'> {
|
|
90
|
+
const launched_ms = status?.launched_ms
|
|
91
|
+
|
|
92
|
+
return launched_ms === undefined ? {} : { launched_ms }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// joshuafolkken/kit#3971: a launch keeps the start of the child's first one in this run.
|
|
96
|
+
function launched(previous: ItemStatus | undefined, event: RunEvent): ItemStatus {
|
|
75
97
|
const at_ms = moment(event)
|
|
98
|
+
const started_ms = previous?.started_ms ?? at_ms
|
|
76
99
|
|
|
77
100
|
return {
|
|
78
101
|
state: 'running',
|
|
79
|
-
started_ms
|
|
102
|
+
started_ms,
|
|
103
|
+
...(started_ms !== at_ms && { launched_ms: at_ms }),
|
|
80
104
|
track: [run_board_phase.LAUNCHED_PHASE],
|
|
81
105
|
phase_ms: at_ms,
|
|
82
106
|
}
|
|
@@ -95,7 +119,12 @@ function moved(previous: ItemStatus, track: ReadonlyArray<Phase>, event: RunEven
|
|
|
95
119
|
function settled_as(previous: ItemStatus | undefined, state: ItemState): ItemStatus {
|
|
96
120
|
const track = previous?.track
|
|
97
121
|
|
|
98
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
state,
|
|
124
|
+
started_ms: previous?.started_ms,
|
|
125
|
+
...relaunch_of(previous),
|
|
126
|
+
...(track !== undefined && { track }),
|
|
127
|
+
}
|
|
99
128
|
}
|
|
100
129
|
|
|
101
130
|
function ended(previous: ItemStatus | undefined, state: ItemState, event: RunEvent): ItemStatus {
|
|
@@ -105,7 +134,7 @@ function ended(previous: ItemStatus | undefined, state: ItemState, event: RunEve
|
|
|
105
134
|
function continued(previous: ItemStatus | undefined): ItemStatus {
|
|
106
135
|
const { started_ms, track, phase_ms } = previous ?? {}
|
|
107
136
|
|
|
108
|
-
return { state: 'running', started_ms, track, phase_ms }
|
|
137
|
+
return { state: 'running', started_ms, ...relaunch_of(previous), track, phase_ms }
|
|
109
138
|
}
|
|
110
139
|
|
|
111
140
|
// A stage line carrying its attempt redraws the track of any child launched on this stream — a settled
|
|
@@ -140,7 +169,7 @@ function advanced(previous: ItemStatus | undefined, event: RunEvent): ItemStatus
|
|
|
140
169
|
|
|
141
170
|
// One event folded into the status of the child it names; an event the board does not track keeps it.
|
|
142
171
|
function next_status(previous: ItemStatus | undefined, event: RunEvent): ItemStatus | undefined {
|
|
143
|
-
if (LAUNCH_KINDS.has(event.kind)) return launched(event)
|
|
172
|
+
if (LAUNCH_KINDS.has(event.kind)) return launched(previous, event)
|
|
144
173
|
|
|
145
174
|
const ending = ENDING_STATES[event.kind]
|
|
146
175
|
|
|
@@ -162,8 +191,9 @@ function fold_events(events: ReadonlyArray<RunEvent>): Map<number, ItemStatus> {
|
|
|
162
191
|
return statuses
|
|
163
192
|
}
|
|
164
193
|
|
|
165
|
-
// A lane is this run's only when its issue is on the run's stream or in
|
|
166
|
-
//
|
|
194
|
+
// A lane is this run's only when its issue is on the run's stream or in `in_run`, so a lane left over
|
|
195
|
+
// from an earlier day is not drawn as this run's child. `in_run` leaves out a planned issue whose lane
|
|
196
|
+
// an earlier run dispatched (`run-board-github.ts`).
|
|
167
197
|
function is_running_lane(
|
|
168
198
|
issue: number,
|
|
169
199
|
status: ItemStatus | undefined,
|
|
@@ -175,13 +205,25 @@ function is_running_lane(
|
|
|
175
205
|
return in_run.has(issue) && !run_board_setup.is_lane_pending(events, issue)
|
|
176
206
|
}
|
|
177
207
|
|
|
208
|
+
// A lane no child is working in is stopped whatever the stream last said: a child that died writes no
|
|
209
|
+
// ending, so its row would otherwise run — and its phase time grow — for as long as the lane stays.
|
|
210
|
+
function lane_status(status: ItemStatus | undefined, lane: string, is_idle: boolean): ItemStatus {
|
|
211
|
+
const held = { lane: `lane ${lane}` }
|
|
212
|
+
|
|
213
|
+
return is_idle
|
|
214
|
+
? { ...settled_as(status, 'stopped'), ...held }
|
|
215
|
+
: { state: 'running', ...status, ...held }
|
|
216
|
+
}
|
|
217
|
+
|
|
178
218
|
// Every child the run has touched, with the lane each running child holds. A lane the stream never saw
|
|
179
219
|
// launched — a run restored after the stream rolled — still reads as running while its issue is in the
|
|
180
|
-
// run's plan (`in_run`); one the run is still setting up for stays waiting until its launch.
|
|
220
|
+
// run's plan (`in_run`); one the run is still setting up for stays waiting until its launch. A lane in
|
|
221
|
+
// `idle` has no child working in it (`lane-spent.ts`).
|
|
181
222
|
function statuses_of(
|
|
182
223
|
events: ReadonlyArray<RunEvent>,
|
|
183
224
|
lanes: ReadonlyArray<string>,
|
|
184
225
|
in_run: ReadonlySet<number>,
|
|
226
|
+
idle: ReadonlyArray<string> = [],
|
|
185
227
|
): ReadonlyMap<number, ItemStatus> {
|
|
186
228
|
const statuses = fold_events(events)
|
|
187
229
|
|
|
@@ -190,7 +232,7 @@ function statuses_of(
|
|
|
190
232
|
const status = statuses.get(issue)
|
|
191
233
|
|
|
192
234
|
if (is_running_lane(issue, status, events, in_run)) {
|
|
193
|
-
statuses.set(issue,
|
|
235
|
+
statuses.set(issue, lane_status(status, lane, idle.includes(lane)))
|
|
194
236
|
}
|
|
195
237
|
}
|
|
196
238
|
|
|
@@ -212,9 +254,14 @@ function is_listed_closed(issue: number, read: OpenRead): boolean {
|
|
|
212
254
|
return read.open_numbers !== undefined && !read.open_numbers.has(issue)
|
|
213
255
|
}
|
|
214
256
|
|
|
215
|
-
//
|
|
257
|
+
// When the child last launched — what a GitHub read is weighed against.
|
|
258
|
+
function launch_ms(status: ItemStatus): number | undefined {
|
|
259
|
+
return status.launched_ms ?? status.started_ms
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The listing was read after the child last launched, so what it says about the child is current.
|
|
216
263
|
function is_read_after_launch(status: ItemStatus, read: OpenRead): boolean {
|
|
217
|
-
return read.read_ms !== undefined && (status
|
|
264
|
+
return read.read_ms !== undefined && (launch_ms(status) ?? 0) <= read.read_ms
|
|
218
265
|
}
|
|
219
266
|
|
|
220
267
|
// A child launched before the listing was read and missing from it has closed since.
|
|
@@ -242,26 +289,30 @@ function labelled(issue: number, status: ItemStatus, read: OpenRead): ItemStatus
|
|
|
242
289
|
|
|
243
290
|
// A close dated before the launch is an earlier life of the issue, not how this run's child ended.
|
|
244
291
|
function is_closed_before_launch(status: ItemStatus, closed: ClosedIssue): boolean {
|
|
245
|
-
|
|
292
|
+
const launch = launch_ms(status)
|
|
293
|
+
|
|
294
|
+
if (launch === undefined || closed.closed_ms === undefined) return false
|
|
295
|
+
|
|
296
|
+
return closed.closed_ms < launch
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function closed_state(closed: ClosedIssue): ItemState {
|
|
300
|
+
if (closed.is_merged) return 'merged'
|
|
246
301
|
|
|
247
|
-
return closed.
|
|
302
|
+
return closed.is_not_planned ? 'withdrawn' : 'done'
|
|
248
303
|
}
|
|
249
304
|
|
|
250
|
-
// A
|
|
305
|
+
// A child read closed ends when GitHub says it closed — merged, withdrawn as not planned, or done.
|
|
251
306
|
function closed_status(
|
|
252
307
|
status: ItemStatus,
|
|
253
308
|
closed: ClosedIssue | undefined,
|
|
254
309
|
): ItemStatus | undefined {
|
|
255
310
|
if (closed === undefined || is_closed_before_launch(status, closed)) return undefined
|
|
256
311
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return { ...settled_as(status, state), ended_ms: closed.closed_ms }
|
|
312
|
+
return { ...settled_as(status, closed_state(closed)), ended_ms: closed.closed_ms }
|
|
260
313
|
}
|
|
261
314
|
|
|
262
|
-
function
|
|
263
|
-
if (status.state !== 'running') return status
|
|
264
|
-
|
|
315
|
+
function unsettled(issue: number, status: ItemStatus, read: OpenRead): ItemStatus {
|
|
265
316
|
const from_closed = closed_status(status, read.closed.get(issue))
|
|
266
317
|
|
|
267
318
|
if (from_closed !== undefined) return from_closed
|
|
@@ -271,11 +322,26 @@ function settled(issue: number, status: ItemStatus, read: OpenRead): ItemStatus
|
|
|
271
322
|
return labelled(issue, status, read)
|
|
272
323
|
}
|
|
273
324
|
|
|
325
|
+
// The closed read is kept for the run, so a parked child a person reopened since is told apart by the
|
|
326
|
+
// listing holding it again.
|
|
327
|
+
function parked_settled(issue: number, status: ItemStatus, read: OpenRead): ItemStatus {
|
|
328
|
+
if (read.open_numbers?.has(issue) === true) return status
|
|
329
|
+
|
|
330
|
+
return closed_status(status, read.closed.get(issue)) ?? status
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function settled(issue: number, status: ItemStatus, read: OpenRead): ItemStatus {
|
|
334
|
+
if (status.state === PARKED_STATE) return parked_settled(issue, status, read)
|
|
335
|
+
|
|
336
|
+
return UNSETTLED_STATES.has(status.state) ? unsettled(issue, status, read) : status
|
|
337
|
+
}
|
|
338
|
+
|
|
274
339
|
// **The stream stays the source of truth; the listing only unsticks it.** A child whose merge never
|
|
275
340
|
// reached the stream would read as running forever, so a running child the plan's open listing no
|
|
276
341
|
// longer holds is drawn finished instead — merged and timed once GitHub was read for it, finished
|
|
277
342
|
// without a time while that read has not answered. One the listing still holds is drawn as its labels
|
|
278
|
-
// say, so a child that stopped with no parent to record it does not read as running.
|
|
343
|
+
// say, so a child that stopped with no parent to record it does not read as running. A parked child is
|
|
344
|
+
// settled again only by a closed read.
|
|
279
345
|
function settle_closed(
|
|
280
346
|
statuses: ReadonlyMap<number, ItemStatus>,
|
|
281
347
|
read: OpenRead,
|
|
@@ -17,8 +17,13 @@ type ThanksState = Pick<BoardState, 'thanks' | 'thanks_fetch' | 'thanks_attempte
|
|
|
17
17
|
function launch_thanks(state: BoardState, ports: BoardPorts, now_ms: number): BoardState {
|
|
18
18
|
const { read_thanks } = ports
|
|
19
19
|
|
|
20
|
-
if (
|
|
21
|
-
|
|
20
|
+
if (
|
|
21
|
+
read_thanks === undefined ||
|
|
22
|
+
state.thanks_fetch !== undefined ||
|
|
23
|
+
!is_due(state.thanks_attempted_ms, TTL_MS, now_ms)
|
|
24
|
+
) {
|
|
25
|
+
return state
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
const thanks_fetch: Fetch<Thanks> = run_board_fetch.launch(read_thanks, state.thanks)
|
|
24
29
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { lane_await } from '#scripts/lane/lane-await'
|
|
1
2
|
import { run_progress } from '#scripts/run/progress/run-progress'
|
|
2
3
|
import type { ClosedIssue } from './run-board-closed'
|
|
3
4
|
import { run_board_github } from './run-board-github'
|
|
4
5
|
import { run_board_header, type BoardHeader } from './run-board-header'
|
|
6
|
+
import { run_board_idle } from './run-board-idle'
|
|
5
7
|
import { run_board_labels } from './run-board-labels'
|
|
6
8
|
import { run_board_layout, type BoardLayout, type BoardPlan } from './run-board-layout'
|
|
7
9
|
import { run_board_link } from './run-board-link'
|
|
@@ -34,10 +36,30 @@ const { FRESH_STATE, is_due } = run_board_state
|
|
|
34
36
|
const { PLAN_RETRY_MS, gather_github, statuses_for } = run_board_github
|
|
35
37
|
const { gather_thanks } = run_board_thanks
|
|
36
38
|
|
|
37
|
-
async function
|
|
39
|
+
async function read_at(state: BoardState, ports: BoardPorts, now_ms: number): Promise<BoardState> {
|
|
40
|
+
const local = await ports.read_local()
|
|
41
|
+
const idle_since = run_board_idle.since_of(state.idle_since, local?.idle_lanes ?? [], now_ms)
|
|
42
|
+
|
|
43
|
+
return { ...state, local, read_ms: now_ms, idle_since }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A single frame has no earlier read to confirm an idle lane against (`run-board-idle.ts`).
|
|
47
|
+
function is_unconfirmed(before: BoardState, read: BoardState, pace: Pace): boolean {
|
|
48
|
+
return pace === 'settled' && before.read_ms === undefined && read.idle_since.size > 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function reread(state: BoardState, ports: BoardPorts, pace: Pace): Promise<BoardState> {
|
|
52
|
+
const now_ms = ports.now()
|
|
53
|
+
|
|
38
54
|
if (!is_due(state.read_ms, LOCAL_READ_MS, now_ms)) return state
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
const read = await read_at(state, ports, now_ms)
|
|
57
|
+
|
|
58
|
+
if (!is_unconfirmed(state, read, pace)) return read
|
|
59
|
+
|
|
60
|
+
await ports.sleep(lane_await.RECONFIRM_MS)
|
|
61
|
+
|
|
62
|
+
return await read_at(read, ports, ports.now())
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
// Each lane's usage, read on the machine's second; a reading that failed draws no column.
|
|
@@ -60,8 +82,10 @@ async function resample(state: BoardState, ports: BoardPorts, now_ms: number): P
|
|
|
60
82
|
const at_ms = ports.now()
|
|
61
83
|
const machine = { sample: await ports.read_machine(), at_ms }
|
|
62
84
|
const gauges = run_board_machine.gauges_of(state.machine, machine)
|
|
85
|
+
const budget_waits = ports.read_waits?.() ?? []
|
|
86
|
+
const usages = await read_usages(state, ports)
|
|
63
87
|
|
|
64
|
-
return { ...state, ...
|
|
88
|
+
return { ...state, ...usages, machine, gauges, budget_waits, sampled_ms: now_ms }
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
// The state of the run on screen; a different run starts its plan state from nothing, so no row, count
|
|
@@ -69,9 +93,9 @@ async function resample(state: BoardState, ports: BoardPorts, now_ms: number): P
|
|
|
69
93
|
function state_for(state: BoardState, local: LocalRead): BoardState {
|
|
70
94
|
if (state.run_started_ms === local.started_ms) return state
|
|
71
95
|
|
|
72
|
-
const { local: kept, read_ms } = state
|
|
96
|
+
const { local: kept, read_ms, idle_since } = state
|
|
73
97
|
|
|
74
|
-
return { ...FRESH_STATE, local: kept, read_ms, run_started_ms: local.started_ms }
|
|
98
|
+
return { ...FRESH_STATE, local: kept, read_ms, idle_since, run_started_ms: local.started_ms }
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
// The plan with a closed child's title and labels under the listing's own.
|
|
@@ -108,12 +132,13 @@ function planned_layout(state: BoardState, plan: BoardPlan, local: LocalRead): B
|
|
|
108
132
|
closed,
|
|
109
133
|
labels: plan.labels,
|
|
110
134
|
}
|
|
111
|
-
const statuses = run_board_status.settle_closed(statuses_for(plan, local), read)
|
|
135
|
+
const statuses = run_board_status.settle_closed(statuses_for(state, plan, local), read)
|
|
112
136
|
const reason_of = run_board_reason.reader({
|
|
113
137
|
plan,
|
|
114
138
|
statuses,
|
|
115
139
|
events: local.events,
|
|
116
140
|
free_lanes: local.free_lanes,
|
|
141
|
+
occupied_lanes: local.occupied_lanes,
|
|
117
142
|
is_stopped: activity_here(local).is_stopped,
|
|
118
143
|
})
|
|
119
144
|
|
|
@@ -125,8 +150,10 @@ function planned_layout(state: BoardState, plan: BoardPlan, local: LocalRead): B
|
|
|
125
150
|
function layout_for(state: BoardState, local: LocalRead): BoardLayout {
|
|
126
151
|
if (state.plan !== undefined) return planned_layout(state, state.plan, local)
|
|
127
152
|
|
|
153
|
+
const stopped = run_board_idle.stopped_of(state.idle_since, state.read_ms)
|
|
154
|
+
|
|
128
155
|
return run_board_layout.local_layout_of(
|
|
129
|
-
run_board_status.statuses_of(local.events, local.lanes, new Set()),
|
|
156
|
+
run_board_status.statuses_of(local.events, local.lanes, new Set(), stopped),
|
|
130
157
|
)
|
|
131
158
|
}
|
|
132
159
|
|
|
@@ -168,6 +195,7 @@ function header_of(
|
|
|
168
195
|
is_plan_loading: state.plan_fetch !== undefined,
|
|
169
196
|
machine: state.gauges,
|
|
170
197
|
usages: state.usages,
|
|
198
|
+
budget_waits: state.budget_waits,
|
|
171
199
|
form: redraw.ports.form,
|
|
172
200
|
repo: state.plan?.context.repo,
|
|
173
201
|
spinner: redraw.ports.is_tty ? SPOT : undefined,
|
|
@@ -220,8 +248,9 @@ async function tick(
|
|
|
220
248
|
ports: BoardPorts,
|
|
221
249
|
pace: Pace = 'settled',
|
|
222
250
|
): Promise<BoardState> {
|
|
251
|
+
const read = await reread(state, ports, pace)
|
|
252
|
+
// Taken after the read, which a single frame may have waited on to confirm an idle lane.
|
|
223
253
|
const now_ms = ports.now()
|
|
224
|
-
const read = await reread(state, ports, now_ms)
|
|
225
254
|
const { local } = read
|
|
226
255
|
|
|
227
256
|
if (local === undefined) {
|
|
@@ -269,9 +269,7 @@ function to_named_request(values: ParsedValues, owner: CarryOwner): Request | un
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
function to_request(values: ParsedValues): Request | undefined {
|
|
272
|
-
if (group_count(values) > ONE_GROUP) return undefined
|
|
273
|
-
|
|
274
|
-
if (!is_summary_paired(values)) return undefined
|
|
272
|
+
if (group_count(values) > ONE_GROUP || !is_summary_paired(values)) return undefined
|
|
275
273
|
|
|
276
274
|
const owner = to_owner(values.owner)
|
|
277
275
|
|
|
@@ -76,8 +76,13 @@ function is_same_conversation(carry: RunCarry, owner: CarryOwner): boolean {
|
|
|
76
76
|
// a new one, so the record goes on naming a live process once the conversation falls quiet. A record a
|
|
77
77
|
// cut handed off is the successor's to take, never the cutting conversation's to take back.
|
|
78
78
|
function reclaim_owner(carry: RunCarry, owner: CarryOwner): RunCarry {
|
|
79
|
-
if (
|
|
80
|
-
|
|
79
|
+
if (
|
|
80
|
+
carry.is_handed_off === true ||
|
|
81
|
+
!is_same_conversation(carry, owner) ||
|
|
82
|
+
owner.pid === carry.owner_pid
|
|
83
|
+
) {
|
|
84
|
+
return carry
|
|
85
|
+
}
|
|
81
86
|
|
|
82
87
|
return { ...carry, owner_pid: owner.pid, owner_start: owner.start }
|
|
83
88
|
}
|
|
@@ -3,7 +3,6 @@ import { fileURLToPath } from 'node:url'
|
|
|
3
3
|
import { agent_session_role } from '#scripts/agent/agent-session-role'
|
|
4
4
|
import { cost_cli } from '#scripts/cost-runtime/cost-cli'
|
|
5
5
|
import { cost_verdict } from '#scripts/cost-runtime/cost-verdict'
|
|
6
|
-
import { git_command } from '#scripts/git/git-command'
|
|
7
6
|
import { issue_cite } from '#scripts/issue/issue-cite'
|
|
8
7
|
import { session_cite } from '#scripts/issue/session-cite'
|
|
9
8
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
@@ -12,9 +11,11 @@ import { openai_lane_supervisor } from '#scripts/lane/openai-lane-supervisor'
|
|
|
12
11
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
13
12
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
14
13
|
import { run_cli_fault } from '#scripts/run/run-cli-fault'
|
|
15
|
-
import { run_cut, type
|
|
14
|
+
import { run_cut, type RunCut } from './run-cut'
|
|
16
15
|
import { run_cut_args, type Request } from './run-cut-args'
|
|
16
|
+
import { run_cut_decline } from './run-cut-decline'
|
|
17
17
|
import { run_cut_handoff } from './run-cut-handoff'
|
|
18
|
+
import { run_cut_ready } from './run-cut-ready'
|
|
18
19
|
import { run_cut_report } from './run-cut-report'
|
|
19
20
|
|
|
20
21
|
// `josh run:cut` — the record that lets a lane child end its process before the gate and a fresh one
|
|
@@ -57,8 +58,10 @@ const {
|
|
|
57
58
|
report_busy,
|
|
58
59
|
report_fault,
|
|
59
60
|
report_handed_off,
|
|
61
|
+
report_held_cut,
|
|
60
62
|
report_incomplete,
|
|
61
63
|
report_over,
|
|
64
|
+
report_relaunched_cut,
|
|
62
65
|
report_reviewer,
|
|
63
66
|
report_stale,
|
|
64
67
|
report_under_threshold,
|
|
@@ -122,14 +125,11 @@ function relaunch(target: string, lane: LaneInfo, phase: string): number {
|
|
|
122
125
|
|
|
123
126
|
if (notes.length > 0) console.error(notes.join('\n'))
|
|
124
127
|
|
|
125
|
-
return
|
|
128
|
+
return report_relaunched_cut()
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return state.branch !== default_branch && state.is_dirty
|
|
132
|
-
}
|
|
131
|
+
const { is_lane_branch } = run_cut_ready
|
|
132
|
+
const { declined } = run_cut_decline
|
|
133
133
|
|
|
134
134
|
// An expired record is a fresh process that never started; it is cleared so the next cut can begin
|
|
135
135
|
// rather than being wedged behind a stale file the exclusive create would refuse forever.
|
|
@@ -200,20 +200,10 @@ async function finish_cut(target: string, lane: LaneInfo, request: CutRequest):
|
|
|
200
200
|
if (refused !== undefined) return refused
|
|
201
201
|
|
|
202
202
|
return lane_relaunch.is_openai_lane(lane)
|
|
203
|
-
?
|
|
203
|
+
? report_relaunched_cut()
|
|
204
204
|
: relaunch(target, lane, request.phase)
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// The cut outside a lane is taken, and nothing is relaunched: the session that took it is the one a
|
|
208
|
-
// person is watching, so the run is handed on the way `fullrun`'s entry `over` stop hands it on.
|
|
209
|
-
function report_held_cut(issue: string): number {
|
|
210
|
-
console.error(
|
|
211
|
-
`The cut is recorded for ${session_cite.issue(issue)} outside a lane, so nothing was relaunched. Keep the hold, send a \`confirmation\` Telegram whose body names the resume command \`fullrun ${issue_cite.plain(issue)}\`, and end the turn; the fresh session's \`pnpm josh run:cut --resume ${issue}\` answers \`resume-impl\` with the handoff.`,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
return report(CUT_VERDICT, SUCCESS_EXIT_CODE)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
207
|
// **A `fullrun` held in its own checkout takes the implementation cut too**, so a run a person started
|
|
218
208
|
// has a bound on its context mid-implementation. The hold naming this issue is what marks the run — a
|
|
219
209
|
// tree nobody holds for it, and the pre-gate phase (which a held run's gate reaches in the same
|
|
@@ -228,8 +218,11 @@ async function cut_outside_lane(
|
|
|
228
218
|
|
|
229
219
|
const state = await run_cut.current_state()
|
|
230
220
|
|
|
231
|
-
if (state.held_issue !== request.issue)
|
|
232
|
-
|
|
221
|
+
if (state.held_issue !== request.issue) {
|
|
222
|
+
return await declined(request, state, report(NOT_A_LANE_VERDICT, SUCCESS_EXIT_CODE))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!(await is_lane_branch(state))) return await declined(request, state, refuse_unready(state))
|
|
233
226
|
|
|
234
227
|
clear_expired(target)
|
|
235
228
|
|
|
@@ -250,7 +243,10 @@ async function cut(
|
|
|
250
243
|
|
|
251
244
|
const state = await run_cut.current_state()
|
|
252
245
|
|
|
253
|
-
if (!(await is_lane_branch(state)))
|
|
246
|
+
if (!(await is_lane_branch(state))) {
|
|
247
|
+
return await declined({ issue, phase }, state, refuse_unready(state))
|
|
248
|
+
}
|
|
249
|
+
|
|
254
250
|
if (skips_pre_gate_cut(phase)) return report_under_threshold()
|
|
255
251
|
|
|
256
252
|
clear_expired(target)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
2
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { run_cut, type CutState } from './run-cut'
|
|
5
|
+
|
|
6
|
+
// The implementation cut a run asked for and was refused — `not-a-lane` or `unready` — recorded so the
|
|
7
|
+
// implementation-cut guard stops asking for it (joshuafolkken/kit#3881).
|
|
8
|
+
//
|
|
9
|
+
// **The reissue window alone re-asked every 90 seconds.** A run whose `run:cut --impl` can only answer
|
|
10
|
+
// `unready` was refused edit after edit, each refusal a round trip that cut nothing. Neither answer
|
|
11
|
+
// changes while the same hold stands over the tree, so the record names the issue and the hold it
|
|
12
|
+
// answered, and the guard stays silent while both still match; a fresh hold — a new run — asks again.
|
|
13
|
+
|
|
14
|
+
const DECLINE_PREFIX = 'josh-run-cut-decline-'
|
|
15
|
+
|
|
16
|
+
const decline_schema = z.object({ issue: z.string(), hold_taken_at: z.string().optional() })
|
|
17
|
+
|
|
18
|
+
type CutDecline = z.infer<typeof decline_schema>
|
|
19
|
+
|
|
20
|
+
function decline_path(git_directory: string): string {
|
|
21
|
+
return stamp_file.stamp_path(DECLINE_PREFIX, git_directory)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function record(git_directory: string, decline: CutDecline): void {
|
|
25
|
+
stamp_file.replace_stamp(decline_path(git_directory), decline)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function read(git_directory: string): CutDecline | undefined {
|
|
29
|
+
const raw = stamp_file.read_stamp_text(decline_path(git_directory))
|
|
30
|
+
|
|
31
|
+
return raw === undefined ? undefined : json_value.parse_with(raw, decline_schema)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function matches(recorded: CutDecline | undefined, current: CutDecline): boolean {
|
|
35
|
+
return recorded?.issue === current.issue && recorded.hold_taken_at === current.hold_taken_at
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The same read taken synchronously, for the `PreToolUse` guard. A checkout git cannot describe has no
|
|
39
|
+
// record, so the guard keeps speaking there.
|
|
40
|
+
function is_declined_sync(
|
|
41
|
+
issue: string,
|
|
42
|
+
directory: string | undefined = run_cut.worktree_git_directory_sync(),
|
|
43
|
+
): boolean {
|
|
44
|
+
if (directory === undefined) return false
|
|
45
|
+
|
|
46
|
+
const hold_taken_at = run_cut.hold_of(directory)?.taken_at
|
|
47
|
+
|
|
48
|
+
return matches(read(directory), { issue, hold_taken_at })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// **An implementation cut `run:cut` refuses is recorded for the guard that asked for it**, then its
|
|
52
|
+
// exit code is passed back unchanged. The pre-gate phase has its own guard and records nothing.
|
|
53
|
+
async function declined(
|
|
54
|
+
request: { issue: string; phase: string },
|
|
55
|
+
state: CutState,
|
|
56
|
+
code: number,
|
|
57
|
+
): Promise<number> {
|
|
58
|
+
if (request.phase !== run_cut.IMPLEMENTATION_PHASE) return code
|
|
59
|
+
|
|
60
|
+
const directory = await run_cut.worktree_directory()
|
|
61
|
+
|
|
62
|
+
if (directory !== undefined) {
|
|
63
|
+
record(directory, { issue: request.issue, hold_taken_at: state.hold_taken_at })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return code
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const run_cut_decline = { decline_path, declined, is_declined_sync, read, record }
|
|
70
|
+
|
|
71
|
+
export type { CutDecline }
|
|
72
|
+
export { run_cut_decline }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { UNTRACKED_FILES_FLAG } from '#scripts/git/constants'
|
|
2
|
+
import { git_command } from '#scripts/git/git-command'
|
|
3
|
+
import { git_diff_reads } from '#scripts/git/git-diff-reads'
|
|
4
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
5
|
+
import type { CutState } from './run-cut'
|
|
6
|
+
|
|
7
|
+
// Whether a cut can be taken on this tree — the one predicate `run:cut` refuses `unready` on and the
|
|
8
|
+
// implementation-cut guard asks before it refuses an edit (joshuafolkken/kit#3881).
|
|
9
|
+
//
|
|
10
|
+
// **The guard and the command must not disagree.** The guard once fired on every over-threshold edit
|
|
11
|
+
// of a run held on the default branch, where `run:cut --impl` can only ever answer `unready`; each
|
|
12
|
+
// refusal cost a round trip that cut nothing. Reading the same predicate keeps the guard silent
|
|
13
|
+
// exactly where the command would refuse.
|
|
14
|
+
|
|
15
|
+
interface CutTree {
|
|
16
|
+
branch: string
|
|
17
|
+
default_branch: string
|
|
18
|
+
is_dirty: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A cut needs an uncommitted implementation on a branch other than the default one: the resume
|
|
22
|
+
// verifies both, so a tree missing either has nothing a fresh process could resume.
|
|
23
|
+
function is_ready(tree: CutTree): boolean {
|
|
24
|
+
return tree.branch !== tree.default_branch && tree.is_dirty
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The same predicate over the state `run:cut` reads, against the default branch asked asynchronously.
|
|
28
|
+
async function is_lane_branch(state: CutState): Promise<boolean> {
|
|
29
|
+
const default_branch = await git_command.get_default_branch()
|
|
30
|
+
|
|
31
|
+
return is_ready({ branch: state.branch, default_branch, is_dirty: state.is_dirty })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// `--branch` puts the branch on a `# branch.head` header line, so one call answers both halves; every
|
|
35
|
+
// other line is a change.
|
|
36
|
+
const STATUS_ARGUMENTS = ['status', '--porcelain=v2', '--branch', UNTRACKED_FILES_FLAG] as const
|
|
37
|
+
const HEADER_PREFIX = '#'
|
|
38
|
+
const BRANCH_HEADER = '# branch.head '
|
|
39
|
+
|
|
40
|
+
function tree_of(status: string, default_branch: string): CutTree | undefined {
|
|
41
|
+
const lines = status.split('\n').filter((line) => line !== '')
|
|
42
|
+
const header = lines.find((line) => line.startsWith(BRANCH_HEADER))
|
|
43
|
+
|
|
44
|
+
if (header === undefined) return undefined
|
|
45
|
+
|
|
46
|
+
const branch = header.slice(BRANCH_HEADER.length)
|
|
47
|
+
const is_dirty = lines.some((line) => !line.startsWith(HEADER_PREFIX))
|
|
48
|
+
|
|
49
|
+
return { branch, default_branch, is_dirty }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The tree read synchronously, for the `PreToolUse` guard — `undefined` where git cannot answer.
|
|
53
|
+
function read_tree_sync(): CutTree | undefined {
|
|
54
|
+
const status = git_spawn_sync.read(STATUS_ARGUMENTS)
|
|
55
|
+
|
|
56
|
+
if (status === undefined) return undefined
|
|
57
|
+
|
|
58
|
+
const reference = git_spawn_sync.read(git_diff_reads.DEFAULT_BRANCH_ARGUMENTS)
|
|
59
|
+
|
|
60
|
+
return tree_of(status, git_diff_reads.default_branch_of(reference))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// **A tree git cannot describe reads as ready**, so the guard keeps speaking rather than going quiet
|
|
64
|
+
// on a read fault — the same direction `run_cut.carried_cut_sync` takes for an unreadable record.
|
|
65
|
+
function is_ready_sync(read: () => CutTree | undefined = read_tree_sync): boolean {
|
|
66
|
+
const tree = read()
|
|
67
|
+
|
|
68
|
+
return tree === undefined || is_ready(tree)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const run_cut_ready = { is_lane_branch, is_ready, is_ready_sync, tree_of }
|
|
72
|
+
|
|
73
|
+
export type { CutTree }
|
|
74
|
+
export { run_cut_ready }
|