@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The text an agent launch may carry: a model id, an effort and a prompt, refused when empty, over
|
|
2
|
+
// length or carrying a control character, so nothing a value holds can inject a further argument.
|
|
3
|
+
|
|
4
|
+
const FIRST_PRINTABLE_CODE = 0x20
|
|
5
|
+
const DELETE_CODE = 0x7f
|
|
6
|
+
const FIRST_CODE_POINT = 0
|
|
7
|
+
const EMPTY_LENGTH = 0
|
|
8
|
+
const MAX_VALUE_LENGTH = 4096
|
|
9
|
+
const LINE_FEED = '\n'
|
|
10
|
+
const SPACE = ' '
|
|
11
|
+
|
|
12
|
+
function is_control_character(character: string): boolean {
|
|
13
|
+
const code = character.codePointAt(FIRST_CODE_POINT) ?? FIRST_PRINTABLE_CODE
|
|
14
|
+
|
|
15
|
+
return code < FIRST_PRINTABLE_CODE || code === DELETE_CODE
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function is_line_feed(character: string): boolean {
|
|
19
|
+
return character === LINE_FEED
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function is_never_allowed(): boolean {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function is_refused(character: string, is_allowed: (character: string) => boolean): boolean {
|
|
27
|
+
return is_control_character(character) && !is_allowed(character)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function is_safe_text(value: string, is_allowed: (character: string) => boolean): boolean {
|
|
31
|
+
if (value.length === EMPTY_LENGTH || value.length > MAX_VALUE_LENGTH) return false
|
|
32
|
+
|
|
33
|
+
for (const character of value) if (is_refused(character, is_allowed)) return false
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function is_safe_value(value: string): boolean {
|
|
39
|
+
return is_safe_text(value, is_never_allowed)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// **An agent's prompt is the one value a line break belongs in.** A ship-stop prompt carries the failed
|
|
43
|
+
// output's lines (joshuafolkken/kit#3964); every other control character is refused as in any value.
|
|
44
|
+
function is_safe_prompt(value: string): boolean {
|
|
45
|
+
return is_safe_text(value, is_line_feed)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function printable_character(character: string): string {
|
|
49
|
+
return is_refused(character, is_line_feed) ? SPACE : character
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Command output made fit for a prompt: every control character but a line break becomes a space, so
|
|
53
|
+
// the text passes `is_safe_prompt` whatever the command printed.
|
|
54
|
+
function printable(text: string): string {
|
|
55
|
+
return Array.from(text, printable_character).join('')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const agent_safe_text = { MAX_VALUE_LENGTH, is_safe_prompt, is_safe_value, printable }
|
|
59
|
+
|
|
60
|
+
export { agent_safe_text }
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type AgentProfile,
|
|
7
7
|
type AgentRole,
|
|
8
8
|
} from './agent-role-profile'
|
|
9
|
+
import { lane_instructions } from './lane-instructions'
|
|
9
10
|
|
|
10
11
|
const AGENT_COMMAND = 'claude'
|
|
11
12
|
// **The permission mode is passed, not left to the checkout's `.claude/settings.json`.** Claude Code
|
|
@@ -63,6 +64,7 @@ const PROJECT_MCP_FILE = '.mcp.json'
|
|
|
63
64
|
interface ClaudeArgv {
|
|
64
65
|
command: string
|
|
65
66
|
args: ReadonlyArray<string>
|
|
67
|
+
ends_with_prompt: true
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
type ClaudeArgvResult =
|
|
@@ -87,6 +89,14 @@ function tool_flags(cwd: string = process.cwd()): ReadonlyArray<string> {
|
|
|
87
89
|
return [TOOLS_FLAG, LAUNCHED_TOOLS.join(','), ...mcp_flags]
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
// **A lane child and a review session start from the lane instruction set** (`lane-instructions.ts`);
|
|
93
|
+
// the scheduler keeps `CLAUDE.md`, because it is the one that stops and asks a person.
|
|
94
|
+
function instruction_flags(profile: AgentProfile, cwd?: string): ReadonlyArray<string> {
|
|
95
|
+
if (profile.role === agent_role_profile.SCHEDULER) return []
|
|
96
|
+
|
|
97
|
+
return lane_instructions.flags(cwd)
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
function build(
|
|
91
101
|
invocation: string,
|
|
92
102
|
profile: AgentProfile,
|
|
@@ -98,6 +108,7 @@ function build(
|
|
|
98
108
|
args: [
|
|
99
109
|
...AGENT_FLAGS,
|
|
100
110
|
...tool_flags(cwd),
|
|
111
|
+
...instruction_flags(profile, cwd),
|
|
101
112
|
...session_id_flags(session_id),
|
|
102
113
|
MODEL_FLAG,
|
|
103
114
|
profile.model,
|
|
@@ -105,6 +116,7 @@ function build(
|
|
|
105
116
|
profile.effort,
|
|
106
117
|
invocation,
|
|
107
118
|
],
|
|
119
|
+
ends_with_prompt: true,
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
|
|
@@ -121,6 +133,7 @@ function build_resume(
|
|
|
121
133
|
args: [
|
|
122
134
|
...AGENT_FLAGS,
|
|
123
135
|
...tool_flags(cwd),
|
|
136
|
+
...instruction_flags(profile, cwd),
|
|
124
137
|
RESUME_FLAG,
|
|
125
138
|
session_id,
|
|
126
139
|
MODEL_FLAG,
|
|
@@ -129,6 +142,7 @@ function build_resume(
|
|
|
129
142
|
profile.effort,
|
|
130
143
|
invocation,
|
|
131
144
|
],
|
|
145
|
+
ends_with_prompt: true,
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
148
|
|
|
@@ -19,6 +19,7 @@ const LEDGER_DIRECTORY = path.dirname(OBSERVATION_LEDGER_DIRECTORY)
|
|
|
19
19
|
interface CodexArgv {
|
|
20
20
|
command: string
|
|
21
21
|
args: ReadonlyArray<string>
|
|
22
|
+
ends_with_prompt: true
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function effort_config(profile: AgentProfile): string {
|
|
@@ -47,8 +48,13 @@ function ledger_arguments(
|
|
|
47
48
|
common_directory: string | undefined,
|
|
48
49
|
profile: AgentProfile,
|
|
49
50
|
): ReadonlyArray<string> {
|
|
50
|
-
if (
|
|
51
|
-
|
|
51
|
+
if (
|
|
52
|
+
common_directory === undefined ||
|
|
53
|
+
profile.role !== agent_role_profile.WORKER ||
|
|
54
|
+
path.basename(common_directory) !== GIT_DIRECTORY
|
|
55
|
+
) {
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
52
58
|
|
|
53
59
|
const directory = path.join(path.dirname(common_directory), LEDGER_DIRECTORY)
|
|
54
60
|
|
|
@@ -86,6 +92,7 @@ function build(invocation: string, profile: AgentProfile, cwd?: string): CodexAr
|
|
|
86
92
|
JSON_FLAG,
|
|
87
93
|
invocation,
|
|
88
94
|
],
|
|
95
|
+
ends_with_prompt: true,
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { cost_tokens } from '#scripts/cost-runtime/cost-tokens'
|
|
4
|
+
import { document_section } from '#scripts/document/document-section'
|
|
5
|
+
import { distributed_paths } from '#scripts/init/distributed-paths'
|
|
6
|
+
import { find_package_directory } from '#scripts/josh/package-directory'
|
|
7
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
8
|
+
|
|
9
|
+
interface LaneSet {
|
|
10
|
+
text: string
|
|
11
|
+
rules_path: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// **A headless lane child or review session starts from the lane instruction set, not `CLAUDE.md`.**
|
|
15
|
+
// `CLAUDE.md` carries rules only an interactive session can act on — consultation versus execution,
|
|
16
|
+
// explicit workflow invocation, stop notifications — and every request re-reads it. The lane document
|
|
17
|
+
// names the rules a lane does need by `CLAUDE.md` heading or bold label, and each `include` line is
|
|
18
|
+
// expanded from the launch checkout's own `CLAUDE.md` here, so the rule text keeps one source (#3929).
|
|
19
|
+
const LANE_DOCUMENT = 'prompts/collaboration-workflow/lane-instructions.md'
|
|
20
|
+
const RULES_FILE = 'CLAUDE.md'
|
|
21
|
+
// `AGENTS.md` only points at `CLAUDE.md`, and Claude Code loads it once `CLAUDE.md` is excluded.
|
|
22
|
+
const POINTER_FILE = 'AGENTS.md'
|
|
23
|
+
const INCLUDE_PATTERN = /^<!-- include: CLAUDE\.md → "(?<wanted>.+)" -->$/u
|
|
24
|
+
const PROMPT_FILE_FLAG = '--append-system-prompt-file'
|
|
25
|
+
const SETTINGS_FLAG = '--settings'
|
|
26
|
+
const PROMPT_FILE_PREFIX = 'josh-lane-instructions-'
|
|
27
|
+
const PROMPT_FILE_SUFFIX = '.md'
|
|
28
|
+
const PACKAGE_DIRECTORY = find_package_directory(path.dirname(fileURLToPath(import.meta.url)))
|
|
29
|
+
|
|
30
|
+
// An include that resolves to nothing is `undefined`, never an empty string, so a renamed heading
|
|
31
|
+
// fails the whole composition rather than silently dropping a rule.
|
|
32
|
+
function expand_line(line: string, rules: string): string | undefined {
|
|
33
|
+
const wanted = INCLUDE_PATTERN.exec(line)?.groups?.['wanted']
|
|
34
|
+
|
|
35
|
+
if (wanted === undefined) return line
|
|
36
|
+
|
|
37
|
+
return document_section.section(rules, wanted)?.text
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function compose(lane_markdown: string, rules: string): string | undefined {
|
|
41
|
+
const expanded = lane_markdown.split('\n').map((line) => expand_line(line, rules))
|
|
42
|
+
|
|
43
|
+
if (expanded.includes(undefined)) return undefined
|
|
44
|
+
|
|
45
|
+
return expanded.join('\n')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// **Where the rules are: the checkout's `CLAUDE.md`, else the package copy it imports.** kit and a
|
|
49
|
+
// consumer holding a full copy keep them in `CLAUDE.md`; a consumer written by `josh init` imports
|
|
50
|
+
// them from the package, and its own `CLAUDE.md` — the bootstrap note and the project's additions —
|
|
51
|
+
// stays loaded, because only the file the set was composed from is excluded.
|
|
52
|
+
function rules_sources(cwd: string): ReadonlyArray<string> {
|
|
53
|
+
const imported = distributed_paths.CLAUDE_MD_IMPORT_LINE.replace(/^@/u, '')
|
|
54
|
+
|
|
55
|
+
return [path.join(cwd, RULES_FILE), path.join(cwd, imported)]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function composed_from(lane_markdown: string, rules_path: string): string | undefined {
|
|
59
|
+
const rules = document_section.read_optional(rules_path)
|
|
60
|
+
|
|
61
|
+
return rules === undefined ? undefined : compose(lane_markdown, rules)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function composed_set(cwd: string, package_directory: string): LaneSet | undefined {
|
|
65
|
+
const lane_markdown = document_section.read_optional(path.join(package_directory, LANE_DOCUMENT))
|
|
66
|
+
|
|
67
|
+
if (lane_markdown === undefined) return undefined
|
|
68
|
+
|
|
69
|
+
for (const rules_path of rules_sources(cwd)) {
|
|
70
|
+
const text = composed_from(lane_markdown, rules_path)
|
|
71
|
+
|
|
72
|
+
if (text !== undefined) return { text, rules_path }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function composed_in(
|
|
79
|
+
cwd: string,
|
|
80
|
+
package_directory: string = PACKAGE_DIRECTORY,
|
|
81
|
+
): string | undefined {
|
|
82
|
+
return composed_set(cwd, package_directory)?.text
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Named by its content and this account, so concurrent launches of the same text share one file and a
|
|
86
|
+
// changed `CLAUDE.md` never reuses a stale one. The name is predictable in a temp directory other
|
|
87
|
+
// accounts may write to, so an existing file is reused only when it is this account's own regular file
|
|
88
|
+
// holding exactly this text; anything else is replaced through `stamp_file`, which refuses another
|
|
89
|
+
// account's file and renames a new one into place whole. A file rather than the text itself, because
|
|
90
|
+
// the launcher refuses a line break in any argv element but the prompt.
|
|
91
|
+
function written(text: string): string | undefined {
|
|
92
|
+
const file_path = stamp_file.stamp_path(PROMPT_FILE_PREFIX, text, PROMPT_FILE_SUFFIX)
|
|
93
|
+
|
|
94
|
+
if (stamp_file.read_stamp_text(file_path) === text) return file_path
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
return stamp_file.replace_text_stamp(file_path, text)
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function excluded_settings(rules_path: string, cwd: string): string {
|
|
104
|
+
return JSON.stringify({ claudeMdExcludes: [rules_path, path.join(cwd, POINTER_FILE)] })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// No flags when the set cannot be composed or safely written: the child then loads `CLAUDE.md` as
|
|
108
|
+
// before, which costs tokens but never loses a rule.
|
|
109
|
+
function flags(cwd: string = process.cwd()): ReadonlyArray<string> {
|
|
110
|
+
const set = composed_set(cwd, PACKAGE_DIRECTORY)
|
|
111
|
+
const prompt_file = set === undefined ? undefined : written(set.text)
|
|
112
|
+
|
|
113
|
+
if (set === undefined || prompt_file === undefined) return []
|
|
114
|
+
|
|
115
|
+
return [PROMPT_FILE_FLAG, prompt_file, SETTINGS_FLAG, excluded_settings(set.rules_path, cwd)]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function tokens_of(text: string): string {
|
|
119
|
+
return cost_tokens.estimate(text).toLocaleString('en-US')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// `read:set lane-child`'s resident line: what each lane request carries in place of the rules file.
|
|
123
|
+
function rules_tokens(rules_path: string): string {
|
|
124
|
+
return tokens_of(document_section.read_optional(rules_path) ?? '')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resident_line(cwd: string = process.cwd()): string {
|
|
128
|
+
const set = composed_set(cwd, PACKAGE_DIRECTORY)
|
|
129
|
+
const rules = rules_tokens(set?.rules_path ?? path.join(cwd, RULES_FILE))
|
|
130
|
+
const lane_tokens = set === undefined ? 'not composed' : `${tokens_of(set.text)} tok`
|
|
131
|
+
|
|
132
|
+
return ` resident: lane instructions ${lane_tokens} in place of ${RULES_FILE} ${rules} tok`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const lane_instructions = {
|
|
136
|
+
PROMPT_FILE_FLAG,
|
|
137
|
+
SETTINGS_FLAG,
|
|
138
|
+
compose,
|
|
139
|
+
composed_in,
|
|
140
|
+
flags,
|
|
141
|
+
resident_line,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { lane_instructions }
|
|
@@ -157,8 +157,7 @@ function build_input(values: ParsedValues, now_ms: number): BudgetInput | undefi
|
|
|
157
157
|
|
|
158
158
|
const started_at_ms = to_time_ms(cli_flags.string_of(values.started))
|
|
159
159
|
|
|
160
|
-
if (started_at_ms === undefined) return undefined
|
|
161
|
-
if (!are_values_readable(values)) return undefined
|
|
160
|
+
if (started_at_ms === undefined || !are_values_readable(values)) return undefined
|
|
162
161
|
|
|
163
162
|
return input_of(values, { answer, now_ms, started_at_ms })
|
|
164
163
|
}
|
|
@@ -42,7 +42,7 @@ const WATCH_VERDICT = 'watch'
|
|
|
42
42
|
const STOP_VERDICT = 'stop'
|
|
43
43
|
const TRIAGE_VERDICT = 'triage'
|
|
44
44
|
|
|
45
|
-
// `run` — start what `backlog:next` offered. `watch` — sleep the
|
|
45
|
+
// `run` — start what `backlog:next` offered. `watch` — sleep the re-ask floor and ask again,
|
|
46
46
|
// except while something of the run's own is in flight, where the wake is the progress watcher's exit
|
|
47
47
|
// and the interval is only a floor (`docs/maintainers/backlogrun-driver.md` → "Waiting while
|
|
48
48
|
// something is in flight").
|
|
@@ -87,7 +87,7 @@ interface BudgetDecision {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const BLOCKED_REASON =
|
|
90
|
-
'Everything opted in is blocked or already running, so waiting can still change the answer.
|
|
90
|
+
'Everything opted in is blocked or already running, so waiting can still change the answer. Ask again at the re-ask floor (60 s).'
|
|
91
91
|
|
|
92
92
|
const TRIAGE_REASON =
|
|
93
93
|
'An opted-in candidate carries neither `run:solo` nor `run:lane`, so nothing starts until it is judged. Read each issue named above, record its order and one of the two labels with a comment, then ask again.'
|
|
@@ -121,7 +121,7 @@ function max_reached_reason(merged: number, max_issues: number): string {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function draining_reason(running: number, reason: string): string {
|
|
124
|
-
return `Start nothing more and
|
|
124
|
+
return `Start nothing more and wait on \`pnpm josh lane:await\` until the running children merge (running: ${String(running)}). Then report and finish — ${reason}`
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
function idle_expired_reason(answer: BacklogAnswer, idle_budget_ms: number): string {
|
|
@@ -140,7 +140,7 @@ function idle_watch_reason(answer: BacklogAnswer, left_ms: number): string {
|
|
|
140
140
|
// **The watch runs down while they drain, and restarts at each one collected.** A collected child is
|
|
141
141
|
// work, so it moves `active_at`; an ask that offered nothing never does.
|
|
142
142
|
function running_watch_reason(answer: BacklogAnswer, running: number, left_ms: number): string {
|
|
143
|
-
return `${waiting_on(answer)} and the idle watch has about ${to_minutes(left_ms)} minutes left, with ${String(running)} still running. Keep the working tree
|
|
143
|
+
return `${waiting_on(answer)} and the idle watch has about ${to_minutes(left_ms)} minutes left, with ${String(running)} still running. Keep the working tree and wait on \`pnpm josh lane:await\` until they merge — the watch is running down meanwhile.`
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function watching_reason(input: BudgetInput, left_ms: number): string {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { has_label_name, IN_PROGRESS_LABEL } from '#scripts/issue/issue-labels'
|
|
2
|
+
import { issue_state_cli } from '#scripts/issue/issue-state-cli'
|
|
3
|
+
import type { LaneInfo } from '#scripts/lane/lane-registry'
|
|
4
|
+
|
|
5
|
+
// The open lanes an earlier invocation dispatched that still claim their issue: `in-progress`, and not
|
|
6
|
+
// stopped for a person's review. Their unit may have died across the hand-off, so the drive collects
|
|
7
|
+
// them; a lane parked or held for review is a person's and is left alone.
|
|
8
|
+
|
|
9
|
+
async function is_claimed(issue: string): Promise<boolean> {
|
|
10
|
+
const read = await issue_state_cli.read_issue(issue)
|
|
11
|
+
|
|
12
|
+
if (read.kind !== 'state') return false
|
|
13
|
+
|
|
14
|
+
return has_label_name(read.state.labels, IN_PROGRESS_LABEL) && !read.state.is_human_review
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function carried(lanes: ReadonlyArray<LaneInfo>): Promise<ReadonlyArray<string>> {
|
|
18
|
+
const dispatched = lanes.filter((lane) => lane.output !== undefined).map((lane) => lane.issue)
|
|
19
|
+
const claims = await Promise.all(dispatched.map(async (issue) => await is_claimed(issue)))
|
|
20
|
+
|
|
21
|
+
return dispatched.filter((_issue, index) => claims[index] === true)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const backlog_drive_carried = { carried }
|
|
25
|
+
|
|
26
|
+
export { backlog_drive_carried }
|
|
@@ -6,7 +6,7 @@ import { issue_cite } from '#scripts/issue/issue-cite'
|
|
|
6
6
|
import { hook_decision } from '#scripts/josh/hook-decision'
|
|
7
7
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
8
8
|
import { lane_await, type AwaitState } from '#scripts/lane/lane-await'
|
|
9
|
-
import { lane_registry } from '#scripts/lane/lane-registry'
|
|
9
|
+
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
10
10
|
import { lane_sampler } from '#scripts/lane/lane-sampler'
|
|
11
11
|
import { error_text } from '#scripts/lib/error-message'
|
|
12
12
|
import { json_value } from '#scripts/lib/json-value'
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type OfferRead,
|
|
25
25
|
} from './backlog-drive'
|
|
26
26
|
import { backlog_drive_args, type DriveContext } from './backlog-drive-args'
|
|
27
|
+
import { backlog_drive_carried } from './backlog-drive-carried'
|
|
27
28
|
import { backlog_drive_finish } from './backlog-drive-finish'
|
|
28
29
|
import { backlog_drive_launch } from './backlog-drive-launch'
|
|
29
30
|
import { backlog_drive_named } from './backlog-drive-named'
|
|
@@ -32,6 +33,7 @@ import { backlog_drive_offer_argv } from './backlog-drive-offer-argv'
|
|
|
32
33
|
import { backlog_drive_owner } from './backlog-drive-owner'
|
|
33
34
|
import { backlog_drive_restore } from './backlog-drive-restore'
|
|
34
35
|
import { backlog_drive_retrospective } from './backlog-drive-retrospective'
|
|
36
|
+
import { backlog_drive_seats } from './backlog-drive-seats'
|
|
35
37
|
import { backlog_offer_cli } from './backlog-offer-cli'
|
|
36
38
|
import { backlog_ready } from './backlog-ready'
|
|
37
39
|
|
|
@@ -153,10 +155,10 @@ function finished_checker(seeded: ReadonlyArray<string>): (issue: string) => boo
|
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
async function open_lanes(): Promise<ReadonlyArray<
|
|
158
|
+
async function open_lanes(): Promise<ReadonlyArray<LaneInfo>> {
|
|
157
159
|
const lanes = await lane_registry.list_lanes()
|
|
158
160
|
|
|
159
|
-
return lanes.filter((lane) => !lane.is_stranded)
|
|
161
|
+
return lanes.filter((lane) => !lane.is_stranded)
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
function end_line(end: DriveEnd): string {
|
|
@@ -190,6 +192,7 @@ function ports_of(
|
|
|
190
192
|
offer: async (state) => await read_offer(state, context),
|
|
191
193
|
launch: async (issue) => await backlog_drive_launch.launch(issue, context.owner),
|
|
192
194
|
free_lanes: backlog_ready.drive_free_lane_count,
|
|
195
|
+
has_lane: backlog_drive_seats.has_kept_lane,
|
|
193
196
|
judgement_owed: judgement_reader(),
|
|
194
197
|
now: () => new Date(),
|
|
195
198
|
sleep: async (milliseconds) => {
|
|
@@ -211,9 +214,11 @@ async function seeded_lanes(context: DriveContext): Promise<ReadonlyArray<string
|
|
|
211
214
|
|
|
212
215
|
await backlog_drive_named.reconcile(carry, events, context.owner)
|
|
213
216
|
|
|
214
|
-
const
|
|
217
|
+
const open = await open_lanes()
|
|
218
|
+
const lanes = [...new Set([...open.map((lane) => lane.issue), ...context.awaited])]
|
|
219
|
+
const dispatched = await backlog_drive_carried.carried(open)
|
|
215
220
|
|
|
216
|
-
return backlog_drive_restore.restore(lanes, events, carry.merged_issues ?? [])
|
|
221
|
+
return backlog_drive_restore.restore(lanes, events, carry.merged_issues ?? [], dispatched)
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
// The interval between two asks is `backlog:budget`'s own, so the driver keeps the one its reason prints.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { issue_state_cli } from '#scripts/issue/issue-state-cli'
|
|
1
|
+
import { lane_parked } from '#scripts/lane/lane-parked'
|
|
3
2
|
import { run_carry, type RunCarry } from '#scripts/run/carry/run-carry'
|
|
4
3
|
import type { RunEvent } from '#scripts/run/event/run-event-stream'
|
|
5
4
|
import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
|
|
@@ -13,7 +12,7 @@ const { MERGE_TOKEN } = run_merge_token
|
|
|
13
12
|
|
|
14
13
|
const NO_RETRIES = 0
|
|
15
14
|
const FIRST = 0
|
|
16
|
-
const TERMINAL_OUTCOMES: ReadonlySet<string> = new Set(['merged', 'parked', 'split'])
|
|
15
|
+
const TERMINAL_OUTCOMES: ReadonlySet<string> = new Set(['merged', 'parked', 'split', 'withdrawn'])
|
|
17
16
|
|
|
18
17
|
// The skip follows the declared order alone: an issue `run:add` put in was
|
|
19
18
|
// asked for on its own, so its failure skips nothing and a declared failure does not skip it.
|
|
@@ -98,9 +97,8 @@ function skip_after_failure(target: string, issue: string, owner: string): void
|
|
|
98
97
|
async function should_skip(issue: string, outcome: MergeResult['outcome']): Promise<boolean> {
|
|
99
98
|
if (outcome === 'failed') return true
|
|
100
99
|
if (outcome !== 'parked') return false
|
|
101
|
-
const read = await issue_state_cli.read_issue(issue)
|
|
102
100
|
|
|
103
|
-
return
|
|
101
|
+
return await lane_parked.is_parked(issue)
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
async function mark_done(issue: string, result: MergeResult, owner: string): Promise<void> {
|
|
@@ -5,12 +5,14 @@ const SETTLED_KINDS: ReadonlySet<string> = new Set([
|
|
|
5
5
|
run_event_stream.EVENT_KIND.MERGE,
|
|
6
6
|
run_event_stream.EVENT_KIND.PARK,
|
|
7
7
|
run_event_stream.EVENT_KIND.SPLIT,
|
|
8
|
+
run_event_stream.EVENT_KIND.WITHDRAW,
|
|
8
9
|
run_event_stream.EVENT_KIND.OUTAGE,
|
|
9
10
|
])
|
|
10
11
|
const DONE_KINDS: ReadonlySet<string> = new Set([
|
|
11
12
|
run_event_stream.EVENT_KIND.MERGE,
|
|
12
13
|
run_event_stream.EVENT_KIND.PARK,
|
|
13
14
|
run_event_stream.EVENT_KIND.SPLIT,
|
|
15
|
+
run_event_stream.EVENT_KIND.WITHDRAW,
|
|
14
16
|
])
|
|
15
17
|
|
|
16
18
|
function issue_of(event: RunEvent): string | undefined {
|
|
@@ -54,14 +56,38 @@ function parked_issues(events: ReadonlyArray<RunEvent>): ReadonlySet<number> {
|
|
|
54
56
|
)
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
// The issues this invocation launched or settled — any other dispatched lane was carried in from an
|
|
60
|
+
// earlier invocation.
|
|
61
|
+
function mentioned_issues(events: ReadonlyArray<RunEvent>): ReadonlySet<string> {
|
|
62
|
+
const tracked = events.filter(
|
|
63
|
+
(event) =>
|
|
64
|
+
event.kind === run_event_stream.EVENT_KIND.CHILD_LAUNCH || SETTLED_KINDS.has(event.kind),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return new Set(tracked.map((event) => issue_of(event)).filter((issue) => issue !== undefined))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The lanes still in flight: launched by this invocation and not settled, or dispatched by an earlier
|
|
71
|
+
// one and never seen by this one — a lane nobody collects otherwise waits forever.
|
|
57
72
|
function restore(
|
|
58
73
|
lanes: ReadonlyArray<string>,
|
|
59
74
|
events: ReadonlyArray<RunEvent>,
|
|
60
75
|
merged_issues: ReadonlyArray<number>,
|
|
76
|
+
dispatched: ReadonlyArray<string> = [],
|
|
61
77
|
): ReadonlyArray<string> {
|
|
62
78
|
const active = active_issues(events)
|
|
79
|
+
const mentioned = mentioned_issues(events)
|
|
80
|
+
const carried = new Set(dispatched.filter((issue) => !mentioned.has(issue)))
|
|
63
81
|
|
|
64
|
-
return lanes.filter(
|
|
82
|
+
return lanes.filter(
|
|
83
|
+
(issue) => (active.has(issue) || carried.has(issue)) && !merged_issues.includes(Number(issue)),
|
|
84
|
+
)
|
|
65
85
|
}
|
|
66
86
|
|
|
67
|
-
export const backlog_drive_restore = {
|
|
87
|
+
export const backlog_drive_restore = {
|
|
88
|
+
active_issues,
|
|
89
|
+
mentioned_issues,
|
|
90
|
+
restore,
|
|
91
|
+
settled_issues,
|
|
92
|
+
parked_issues,
|
|
93
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { lane_registry } from '#scripts/lane/lane-registry'
|
|
2
|
+
import type { DrivePorts } from './backlog-drive'
|
|
3
|
+
|
|
4
|
+
type SeatPorts = Pick<DrivePorts, 'free_lanes' | 'has_lane'>
|
|
5
|
+
|
|
6
|
+
// The issues a pass launches: every one that already holds a kept lane, which it reuses, then as many
|
|
7
|
+
// of the rest as there are free lanes.
|
|
8
|
+
async function seated(issues: ReadonlyArray<string>, ports: SeatPorts): Promise<Array<string>> {
|
|
9
|
+
const kept = await Promise.all(issues.map(async (issue) => await ports.has_lane(issue)))
|
|
10
|
+
const fresh = issues.filter((_issue, index) => kept[index] !== true)
|
|
11
|
+
|
|
12
|
+
return [
|
|
13
|
+
...issues.filter((_issue, index) => kept[index] === true),
|
|
14
|
+
...fresh.slice(0, await ports.free_lanes()),
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// A kept lane is one a child was dispatched into — `lane:launch` reuses it rather than opening one.
|
|
19
|
+
async function has_kept_lane(issue: string): Promise<boolean> {
|
|
20
|
+
const lane = await lane_registry.find_open_lane(issue)
|
|
21
|
+
|
|
22
|
+
return lane?.output !== undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const backlog_drive_seats = { has_kept_lane, seated }
|
|
26
|
+
|
|
27
|
+
export { backlog_drive_seats }
|
|
@@ -20,6 +20,7 @@ import { issue_cite } from '#scripts/issue/issue-cite'
|
|
|
20
20
|
import type { LaunchOutcome } from '#scripts/lane/lane-launch-cli'
|
|
21
21
|
import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
|
|
22
22
|
import { run_merge_token, type MergeToken } from '#scripts/run/merge/run-merge-token'
|
|
23
|
+
import { backlog_drive_seats } from './backlog-drive-seats'
|
|
23
24
|
|
|
24
25
|
const { MERGE_TOKEN } = run_merge_token
|
|
25
26
|
const RUN_VERDICT = 'run'
|
|
@@ -124,6 +125,9 @@ interface DrivePorts {
|
|
|
124
125
|
// The lanes free right now — `backlog_ready.drive_free_lane_count` in production, the limit less the
|
|
125
126
|
// open lanes.
|
|
126
127
|
free_lanes: () => Promise<number>
|
|
128
|
+
// Whether the issue already holds a kept lane (one a child was dispatched into) — it is reused, so it
|
|
129
|
+
// takes no free seat.
|
|
130
|
+
has_lane: (issue: string) => Promise<boolean>
|
|
127
131
|
// How many open issues lacking `auto-ok` still owe a judgement — `auto_ok_judge_read.owed` in
|
|
128
132
|
// production, `0` when the read failed.
|
|
129
133
|
judgement_owed: () => Promise<number>
|
|
@@ -265,10 +269,10 @@ async function refused(
|
|
|
265
269
|
return ended('launch', state, undefined, issue)
|
|
266
270
|
}
|
|
267
271
|
|
|
268
|
-
// **No more
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
+
// **No more new lanes than free lanes**. `backlog:offer` lists every ready issue without reading the
|
|
273
|
+
// lanes, so launching all of them overran `JOSH_LANE_LIMIT` until `lane:open` ran out of seats. An issue
|
|
274
|
+
// that already holds a kept lane reuses it and takes no seat (`backlog-drive-seats.ts`); the rest launch
|
|
275
|
+
// up to the free count, read once before the first launch.
|
|
272
276
|
//
|
|
273
277
|
// **Only a launch is activity**: a pass that started nothing — every lane taken
|
|
274
278
|
// — keeps `active` where it was, or the once-a-minute offer would restart the idle budget for good.
|
|
@@ -284,9 +288,10 @@ async function launch_all(
|
|
|
284
288
|
ports: DrivePorts,
|
|
285
289
|
): Promise<PassResult> {
|
|
286
290
|
const in_flight = [...state.in_flight]
|
|
287
|
-
const free = await ports.free_lanes()
|
|
288
291
|
|
|
289
|
-
|
|
292
|
+
const launching = await backlog_drive_seats.seated(issues, ports)
|
|
293
|
+
|
|
294
|
+
for (const issue of launching) {
|
|
290
295
|
// eslint-disable-next-line no-await-in-loop -- lanes launch in order and the first refusal ends the pass
|
|
291
296
|
const outcome = await ports.launch(issue)
|
|
292
297
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { LaneInfo } from '#scripts/lane/lane-registry'
|
|
2
|
+
import type { RunEvent } from '#scripts/run/event/run-event-stream'
|
|
3
|
+
import { run_invocation } from '#scripts/run/run-invocation'
|
|
4
|
+
import { backlog_drive_restore } from './backlog-drive-restore'
|
|
5
|
+
|
|
6
|
+
// The open lanes that hold a place under the lane limit — the one count `lane:limit`, the drive and
|
|
7
|
+
// `run:board` all subtract from it.
|
|
8
|
+
//
|
|
9
|
+
// **A lane nobody will come back to holds no place.** A `--only` run works its named list and never
|
|
10
|
+
// touches another lane, so a lane an earlier run left with no child in it would fill the limit for the
|
|
11
|
+
// whole run, and the run's own issues would wait on it for good (joshuafolkken/kit#3967). Under a `--only`
|
|
12
|
+
// run such a lane is not counted; its tree and branch stay, for the next run that names its issue.
|
|
13
|
+
//
|
|
14
|
+
// **Everything else keeps its place, so the count errs toward fewer free lanes.** A run that drains
|
|
15
|
+
// the pool may offer any open lane's issue again, a lane whose child still works is busy whoever owns
|
|
16
|
+
// it, and with no run there is nobody to judge ownership for. The read stays local — no issue label is
|
|
17
|
+
// read — because the count gates the network read behind it (`backlog-ready.ts`).
|
|
18
|
+
|
|
19
|
+
interface OccupiedRead {
|
|
20
|
+
// The live run's invocation, `undefined` when no run is live here.
|
|
21
|
+
invocation: string | undefined
|
|
22
|
+
// The live run's own events, for the issues it launched or settled; `undefined` where its scope
|
|
23
|
+
// could not be told, which leaves every open lane counted.
|
|
24
|
+
events: ReadonlyArray<RunEvent> | undefined
|
|
25
|
+
// The issues whose lane has no child working in it; read only for a `--only` run.
|
|
26
|
+
idle: (lanes: ReadonlyArray<LaneInfo>) => ReadonlyArray<string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The issues a `--only` run owns — those it names and those it launched or settled — or `undefined`
|
|
30
|
+
// where there is no such run, or its events could not be scoped to it.
|
|
31
|
+
function owned_issues(read: OccupiedRead): ReadonlySet<string> | undefined {
|
|
32
|
+
const { invocation, events } = read
|
|
33
|
+
const is_only = invocation !== undefined && run_invocation.has_only(invocation)
|
|
34
|
+
|
|
35
|
+
if (!is_only || events === undefined) return undefined
|
|
36
|
+
|
|
37
|
+
const named = (run_invocation.issue_numbers(invocation) ?? []).map(String)
|
|
38
|
+
|
|
39
|
+
return new Set([...named, ...backlog_drive_restore.mentioned_issues(events)])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function occupied_lanes(lanes: ReadonlyArray<LaneInfo>, read: OccupiedRead): Array<LaneInfo> {
|
|
43
|
+
const live = lanes.filter((lane) => !lane.is_stranded)
|
|
44
|
+
const owned = owned_issues(read)
|
|
45
|
+
|
|
46
|
+
if (owned === undefined) return live
|
|
47
|
+
|
|
48
|
+
const idle = new Set(read.idle(live))
|
|
49
|
+
|
|
50
|
+
return live.filter((lane) => owned.has(lane.issue) || !idle.has(lane.issue))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const backlog_occupied = { occupied_lanes }
|
|
54
|
+
|
|
55
|
+
export { backlog_occupied }
|
|
56
|
+
export type { OccupiedRead }
|