@joshuafolkken/kit 1.1387.0 → 1.1453.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/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +2 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
- package/.claude/skills/workflow-commands/backlogrun.md +3 -2
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/kickoff.md +12 -13
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/.claude/skills/workflow-commands/prerequisite.md +6 -0
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/README.md +27 -4
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +261 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +95 -19
- package/package.json +4 -3
- package/prompts/collaboration-workflow/issue-template.md +10 -8
- package/prompts/review.md +30 -4
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/auto-ok/auto-ok-judge-cli.ts +125 -0
- package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
- package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
- package/scripts/auto-ok/auto-ok-judge.ts +67 -0
- package/scripts/backlog/backlog-drive-cli.ts +14 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +38 -11
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +24 -0
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +175 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +18 -4
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +13 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +7 -4
- package/scripts/init/init-ai-copy.ts +34 -11
- package/scripts/init/init-basic.ts +9 -4
- package/scripts/init/init-bootstrap.ts +6 -2
- package/scripts/init/init-dry-run-report.ts +150 -0
- package/scripts/init/init-dry-run.ts +189 -0
- package/scripts/init/init-install.ts +8 -1
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +62 -42
- package/scripts/init/kit-setup-state.ts +11 -17
- package/scripts/init/own-formatter.ts +21 -0
- package/scripts/init/project-profile.ts +60 -12
- package/scripts/init/start-dry-run.ts +70 -0
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +23 -3
- package/scripts/init/start-setup-pr.ts +13 -24
- package/scripts/init/start-steps.ts +62 -1
- package/scripts/init/start.ts +38 -17
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/init/working-tree.ts +116 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-auto-ok.ts +68 -99
- package/scripts/issue/issue-file-cli.ts +17 -17
- package/scripts/issue/issue-file.ts +1 -6
- package/scripts/issue/issue-labels.ts +46 -10
- package/scripts/issue/issue-release-cli.ts +69 -23
- package/scripts/issue/issue-release.ts +3 -1
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-ai.ts +11 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +10 -3
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +26 -2
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/josh/process-identity.ts +32 -6
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-broker-server.ts +69 -0
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/lane-session-broker.ts +363 -0
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/observations/observation-ledger-migrate.ts +3 -15
- package/scripts/release/release-issue-close.ts +133 -0
- package/scripts/release/release-publish.ts +12 -1
- package/scripts/repo/repository-labels.ts +11 -1
- package/scripts/review/comment-ranges.ts +199 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +24 -7
- package/scripts/review/review-code-tree.ts +119 -0
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/review/review-round2-cli.ts +16 -5
- package/scripts/review/review-round2.ts +30 -10
- package/scripts/review/review-stamps.ts +22 -1
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +129 -53
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +49 -3
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/lane/openai-review-broker.ts +0 -262
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { issue_cite } from '#scripts/issue/issue-cite'
|
|
2
|
+
|
|
3
|
+
// The words of a `pr-checks` event (joshuafolkken/kit#3770): `josh followup` writes them and `run:board`
|
|
4
|
+
// reads them back, so the one spelling lives here. A line reads
|
|
5
|
+
// `#3754 PR 3765 pass 6 pending 2 fail 0 coderabbit running` — the issue first, as every event names
|
|
6
|
+
// it, so the stream's readers key it to the child as they key any other.
|
|
7
|
+
|
|
8
|
+
const CODERABBIT_STATES = ['running', 'done', 'rate-limited', 'failed', 'none'] as const
|
|
9
|
+
|
|
10
|
+
type CodeRabbitState = (typeof CODERABBIT_STATES)[number]
|
|
11
|
+
|
|
12
|
+
interface PrChecks {
|
|
13
|
+
pr: number
|
|
14
|
+
pass: number
|
|
15
|
+
pending: number
|
|
16
|
+
fail: number
|
|
17
|
+
coderabbit: CodeRabbitState
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CHECKS_PATTERN =
|
|
21
|
+
/^#\d+ PR (?<pr>\d+) pass (?<pass>\d+) pending (?<pending>\d+) fail (?<fail>\d+) coderabbit (?<coderabbit>\S+)$/u
|
|
22
|
+
|
|
23
|
+
function text_of(issue: string, checks: PrChecks): string {
|
|
24
|
+
const { pr, pass, pending, fail, coderabbit } = checks
|
|
25
|
+
|
|
26
|
+
return `${issue_cite.plain(issue)} PR ${String(pr)} pass ${String(pass)} pending ${String(pending)} fail ${String(fail)} coderabbit ${coderabbit}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const STATE_NAMES: ReadonlySet<string> = new Set(CODERABBIT_STATES)
|
|
30
|
+
|
|
31
|
+
function is_coderabbit_state(value = ''): value is CodeRabbitState {
|
|
32
|
+
return STATE_NAMES.has(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The checks a line carries, `undefined` for a line written in some other shape.
|
|
36
|
+
function checks_of(text: string): PrChecks | undefined {
|
|
37
|
+
const groups = CHECKS_PATTERN.exec(text)?.groups
|
|
38
|
+
const coderabbit = groups?.['coderabbit']
|
|
39
|
+
|
|
40
|
+
if (groups === undefined || !is_coderabbit_state(coderabbit)) return undefined
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
pr: Number(groups['pr']),
|
|
44
|
+
pass: Number(groups['pass']),
|
|
45
|
+
pending: Number(groups['pending']),
|
|
46
|
+
fail: Number(groups['fail']),
|
|
47
|
+
coderabbit,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const run_event_checks = { checks_of, text_of }
|
|
52
|
+
|
|
53
|
+
export { run_event_checks }
|
|
54
|
+
export type { CodeRabbitState, PrChecks }
|
|
@@ -28,6 +28,7 @@ const KIND_LABELS: Readonly<Record<EventKind, KindLabel>> = {
|
|
|
28
28
|
outage: { ja: '障害', en: 'outage' },
|
|
29
29
|
cut: { ja: 'セッション交代', en: 'session cut' },
|
|
30
30
|
resume: { ja: '実装を再開', en: 'implementation resumed' },
|
|
31
|
+
restart: { ja: '止めて出し直し', en: 'restarted' },
|
|
31
32
|
drain: { ja: 'バックログが空', en: 'backlog drained' },
|
|
32
33
|
stop: { ja: '停止', en: 'stopped' },
|
|
33
34
|
'pr-opened': { ja: 'PR 作成', en: 'PR opened' },
|
|
@@ -45,6 +46,7 @@ const KIND_LABELS: Readonly<Record<EventKind, KindLabel>> = {
|
|
|
45
46
|
add: { ja: '割り込み', en: 'added' },
|
|
46
47
|
'lane-limit': { ja: 'レーン上限', en: 'lane limit' },
|
|
47
48
|
setup: { ja: '準備', en: 'setup' },
|
|
49
|
+
'pr-checks': { ja: 'PR のチェック', en: 'PR checks' },
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const LABELS_BY_KIND: ReadonlyMap<string, KindLabel> = new Map(Object.entries(KIND_LABELS))
|
|
@@ -3,10 +3,12 @@ import { error_text } from '#scripts/lib/error-message'
|
|
|
3
3
|
import { run_event_stream } from './run-event-stream'
|
|
4
4
|
import { run_event_stream_emit } from './run-event-stream-emit'
|
|
5
5
|
|
|
6
|
-
// The stages a run passes between `josh backlogrun` and its first child,
|
|
7
|
-
// one `setup` event by the step that enters it. **The text
|
|
8
|
-
// it as written, so the message a person reads and the
|
|
9
|
-
//
|
|
6
|
+
// The stages a run passes between `josh backlogrun` and its first child, and those of a later hand-back
|
|
7
|
+
// to a session, each written to the stream as one `setup` event by the step that enters it. **The text
|
|
8
|
+
// is the stage's own words**: `run:board` draws it as written, so the message a person reads and the
|
|
9
|
+
// `💓` beside it come from one event and cannot disagree. Only the stages of a lane's launch — opening
|
|
10
|
+
// it, installing into it, dispatching its child — name an issue, which is how a reader tells them from
|
|
11
|
+
// the stages before them.
|
|
10
12
|
|
|
11
13
|
const STAGE = {
|
|
12
14
|
// `josh backlogrun` started the parent session, which is reading its procedure.
|
|
@@ -15,33 +17,75 @@ const STAGE = {
|
|
|
15
17
|
PLANNING: 'planning',
|
|
16
18
|
// The supervisor took the run over from the parent and is asking for the first offer.
|
|
17
19
|
HANDING_OFF: 'handing off',
|
|
20
|
+
// The driver answered something only a session can act on — an untriaged child, an epic left to a
|
|
21
|
+
// person — and the supervisor is waking one. The next hand-off replaces it.
|
|
22
|
+
JUDGING: 'waiting on judgment',
|
|
23
|
+
// The supervisor started that session, which is now acting on the driver's answer. It is detached, so
|
|
24
|
+
// its end is the supervisor's next hand-off, which replaces this.
|
|
25
|
+
SESSION: 'judgment session running',
|
|
26
|
+
// The driver's launch of a lane returned without a child: no lane was free to open, or a step after
|
|
27
|
+
// the open failed. It names no issue, so the lane's own stage ends here whatever the driver does next.
|
|
28
|
+
NO_CHILD: 'no child launched',
|
|
18
29
|
} as const
|
|
19
30
|
|
|
20
31
|
const OPENING = 'opening lane'
|
|
32
|
+
const INSTALLING = 'installing dependencies'
|
|
33
|
+
const DISPATCHING = 'dispatching'
|
|
34
|
+
// The stages of one lane's launch, in the order a lane passes them.
|
|
35
|
+
const LANE_STAGE = { OPENING, INSTALLING, DISPATCHING } as const
|
|
21
36
|
|
|
22
37
|
// The driver is about to open a lane for `issue` — for every child, not only the first.
|
|
23
38
|
function opening_lane(issue: string): string {
|
|
24
39
|
return `${OPENING} ${issue_cite.plain(issue)}`
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
// The lane's work tree exists and its dependencies are about to be installed into it.
|
|
43
|
+
function installing(issue: string): string {
|
|
44
|
+
return `${INSTALLING} ${issue_cite.plain(issue)}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The lane is ready and its child is about to be dispatched; the child's own `child-launch` follows.
|
|
48
|
+
function dispatching(issue: string): string {
|
|
49
|
+
return `${DISPATCHING} ${issue_cite.plain(issue)}`
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
// Best-effort, as every append to this stream is: a stage that could not be written is a board that
|
|
28
53
|
// says less, never a run that failed.
|
|
29
54
|
async function emit(text: string): Promise<void> {
|
|
30
55
|
await run_event_stream_emit.emit(run_event_stream.EVENT_KIND.SETUP, text)
|
|
31
56
|
}
|
|
32
57
|
|
|
33
|
-
// The same append
|
|
34
|
-
|
|
35
|
-
function emit_in(repository: string, text: string, at: Date): void {
|
|
58
|
+
// The same append to a stream the caller resolves, dated the moment it hands over.
|
|
59
|
+
function append_stage(resolve_target: () => string, text: string, at: Date): void {
|
|
36
60
|
try {
|
|
37
|
-
const
|
|
61
|
+
const { SETUP } = run_event_stream.EVENT_KIND
|
|
38
62
|
|
|
39
|
-
run_event_stream.append(
|
|
63
|
+
run_event_stream.append(resolve_target(), SETUP, text, at.toISOString())
|
|
40
64
|
} catch (error) {
|
|
41
|
-
error_text.trace_swallowed('run_event_setup.
|
|
65
|
+
error_text.trace_swallowed('run_event_setup.append_stage', error)
|
|
42
66
|
}
|
|
43
67
|
}
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
// For a caller that already holds the repository's common git directory and the moment it wants the
|
|
70
|
+
// stage dated — the launcher, whose record and first stage carry one time.
|
|
71
|
+
function emit_in(repository: string, text: string, at: Date): void {
|
|
72
|
+
append_stage(() => run_event_stream.target_of(repository), text, at)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// For a caller that already holds the stream's own path — the supervisor, which resolved it once.
|
|
76
|
+
function emit_to(target: string, text: string, at: Date): void {
|
|
77
|
+
append_stage(() => target, text, at)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const run_event_setup = {
|
|
81
|
+
LANE_STAGE,
|
|
82
|
+
STAGE,
|
|
83
|
+
dispatching,
|
|
84
|
+
emit,
|
|
85
|
+
emit_in,
|
|
86
|
+
emit_to,
|
|
87
|
+
installing,
|
|
88
|
+
opening_lane,
|
|
89
|
+
}
|
|
46
90
|
|
|
47
91
|
export { run_event_setup }
|
|
@@ -60,6 +60,10 @@ const EVENT_KIND = {
|
|
|
60
60
|
// resume clears the cut record, so without this the `cut` would stay the newest position and
|
|
61
61
|
// `run:step` would keep answering the resume command it had just run.
|
|
62
62
|
RESUME: 'resume',
|
|
63
|
+
// A person stopped a running lane child and re-dispatched its issue in a fresh session
|
|
64
|
+
// (`lane:restart`). It follows the fresh dispatch's `CHILD_LAUNCH`, so it is a launch to the parent and
|
|
65
|
+
// the board, and — like that launch — a batch record a lane child never reads as its own position.
|
|
66
|
+
RESTART: 'restart',
|
|
63
67
|
// The backlog emptied while nothing of the run's own was in flight — the drain. It marks the position
|
|
64
68
|
// at which the end-of-run retrospective is owed, *before* the idle watch opens, so `run:step` fires
|
|
65
69
|
// the retrospective at the drain rather than after the watch runs its course. It is not itself a
|
|
@@ -126,10 +130,15 @@ const EVENT_KIND = {
|
|
|
126
130
|
// already holds. A trace: it says the run gained room, not where the run is.
|
|
127
131
|
LANE_LIMIT: 'lane-limit',
|
|
128
132
|
// One stage of the run's start — the session starting, the plan, the hand-off to the supervisor, a
|
|
129
|
-
// lane opening (`run-event-setup.ts`). `run:board` draws it
|
|
133
|
+
// lane opening (`run-event-setup.ts`). `run:board` draws it while the run stands at it, so the
|
|
130
134
|
// minutes before one read as work rather than as a run that has stopped. A trace: it says the run is
|
|
131
135
|
// getting ready, not where the run is.
|
|
132
136
|
SETUP: 'setup',
|
|
137
|
+
// A child's pull request checks as `josh followup` last polled them — the CI counts and CodeRabbit's
|
|
138
|
+
// state (`run-event-checks.ts`), written only when they change, so `run:board` draws a child waiting
|
|
139
|
+
// in followup without asking GitHub (joshuafolkken/kit#3770). A trace: it says how the checks stand,
|
|
140
|
+
// not where the run is.
|
|
141
|
+
PR_CHECKS: 'pr-checks',
|
|
133
142
|
} as const
|
|
134
143
|
|
|
135
144
|
type EventKind = (typeof EVENT_KIND)[keyof typeof EVENT_KIND]
|
|
@@ -148,17 +157,19 @@ const TRACE_KINDS: ReadonlySet<string> = new Set([
|
|
|
148
157
|
EVENT_KIND.ADD,
|
|
149
158
|
EVENT_KIND.LANE_LIMIT,
|
|
150
159
|
EVENT_KIND.SETUP,
|
|
160
|
+
EVENT_KIND.PR_CHECKS,
|
|
151
161
|
])
|
|
152
162
|
|
|
153
163
|
// The trace kinds that say only that the run was alive, so the bound rolls them off first. A `filed` or
|
|
154
164
|
// `note` line is a trace to the position readers but is the board's record of what the run found, so
|
|
155
165
|
// it rolls off only with the positions. A ship-stage `start` is disposable but goes last of them:
|
|
156
|
-
// `run:board` draws from that line.
|
|
157
|
-
// so every one but the newest goes.
|
|
166
|
+
// `run:board` draws from that line. Only the newest `setup` line is the stage the run stands at,
|
|
167
|
+
// so every one but the newest goes; likewise only each child's newest `pr-checks` is drawn.
|
|
158
168
|
const DISPOSABLE_KINDS: ReadonlySet<string> = new Set([
|
|
159
169
|
EVENT_KIND.SHIP_STAGE,
|
|
160
170
|
EVENT_KIND.HEARTBEAT,
|
|
161
171
|
EVENT_KIND.SETUP,
|
|
172
|
+
EVENT_KIND.PR_CHECKS,
|
|
162
173
|
])
|
|
163
174
|
|
|
164
175
|
const event_schema = z.object({
|
|
@@ -229,17 +240,18 @@ function last_position(events: ReadonlyArray<RunEvent>): number {
|
|
|
229
240
|
return events[events.length - POSITION_INCREMENT]?.pos ?? FIRST_POSITION
|
|
230
241
|
}
|
|
231
242
|
|
|
232
|
-
// The issue a ship-stage names — `#<N>
|
|
243
|
+
// The issue a ship-stage or a `pr-checks` line names — `#<N> …` carries it first.
|
|
233
244
|
function issue_of(event: RunEvent): string {
|
|
234
245
|
return event.text.split(WORD_SEPARATOR)[FIRST_POSITION] ?? ''
|
|
235
246
|
}
|
|
236
247
|
|
|
237
|
-
// Each issue's newest ship-stage
|
|
238
|
-
|
|
248
|
+
// Each issue's newest line of one kind: the ship-stage the board reads its furthest phase from, and the
|
|
249
|
+
// `pr-checks` it draws a followup row's end from.
|
|
250
|
+
function newest_by_issue(events: ReadonlyArray<RunEvent>, kind: string): ReadonlySet<RunEvent> {
|
|
239
251
|
const newest = new Map<string, RunEvent>()
|
|
240
252
|
|
|
241
253
|
for (const event of events) {
|
|
242
|
-
if (event.kind ===
|
|
254
|
+
if (event.kind === kind) newest.set(issue_of(event), event)
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
return new Set(newest.values())
|
|
@@ -253,12 +265,16 @@ function is_stage_start(event: RunEvent): boolean {
|
|
|
253
265
|
return event.text.split(WORD_SEPARATOR)[STAGE_PHASE_INDEX] === run_ship_stage.PHASE.START
|
|
254
266
|
}
|
|
255
267
|
|
|
256
|
-
// The disposable lines the bound never drops: each issue's newest ship-stage and
|
|
257
|
-
// `setup` stage, the ones `run:board` draws from now.
|
|
268
|
+
// The disposable lines the bound never drops: each issue's newest ship-stage and `pr-checks` and the
|
|
269
|
+
// run's newest `setup` stage, the ones `run:board` draws from now.
|
|
258
270
|
function drawn_lines(events: ReadonlyArray<RunEvent>): ReadonlySet<RunEvent> {
|
|
259
271
|
const setup = events.findLast((event) => event.kind === EVENT_KIND.SETUP)
|
|
260
272
|
|
|
261
|
-
return new Set([
|
|
273
|
+
return new Set([
|
|
274
|
+
...newest_by_issue(events, EVENT_KIND.SHIP_STAGE),
|
|
275
|
+
...newest_by_issue(events, EVENT_KIND.PR_CHECKS),
|
|
276
|
+
...(setup === undefined ? [] : [setup]),
|
|
277
|
+
])
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
// The lines the bound may drop, in the order it drops them: the ones that draw nothing first, then the
|
|
@@ -282,7 +298,7 @@ function droppable(events: ReadonlyArray<RunEvent>): ReadonlyArray<RunEvent> {
|
|
|
282
298
|
// **Each issue's newest ship-stage is not disposable**: it is the only record `run:board` draws a lane's
|
|
283
299
|
// review-and-later phase from, and on a stream full of positions it would otherwise roll off in its own
|
|
284
300
|
// append. **The newest `setup` stage is kept for the same reason**: it is the stage `run:board` draws
|
|
285
|
-
//
|
|
301
|
+
// while the run stands at it, and a run that starts on a full stream would otherwise show none.
|
|
286
302
|
//
|
|
287
303
|
// **A ship-stage `start` outlasts the other disposable lines**: each one is a phase on the lane's track,
|
|
288
304
|
// so it is kept past the `done` lines and the heartbeats.
|
|
@@ -25,7 +25,7 @@ import { run_merge_token, type MergeToken } from './run-merge-token'
|
|
|
25
25
|
// The contract is `run:carry`'s: **standard output carries one token** (or the newline-separated
|
|
26
26
|
// offer `epic:next --lanes` prints), and everything else is a captured subprocess or a quiet
|
|
27
27
|
// in-process call. The verdicts this command adds to the offer are `over` (hand off), `human-review`
|
|
28
|
-
// (stop — the child's own ending), `stop` (the consecutive-failure guard tripped) and `retry` (the
|
|
28
|
+
// (stop — the child's own ending), `stop` (the consecutive-failure guard tripped, or a failed child could not be parked) and `retry` (the
|
|
29
29
|
// child's state could not be read; re-read before deciding).
|
|
30
30
|
|
|
31
31
|
const ARGV_OFFSET = 2
|
|
@@ -163,13 +163,16 @@ function parse(argv: ReadonlyArray<string>): MergeContext | undefined {
|
|
|
163
163
|
interface ControlVerdict {
|
|
164
164
|
token: MergeToken
|
|
165
165
|
code: number
|
|
166
|
+
is_offer: false
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
// The next child offered in the collected one's place: the text `backlog:next` or `epic:next --lanes`
|
|
169
|
-
// printed, passed through as it was read, so no narrower type holds it.
|
|
170
|
+
// printed, passed through as it was read, so no narrower type holds it. `is_offer` keeps its `stop`
|
|
171
|
+
// apart from the control one (`run_merge_token.is_control_stop`).
|
|
170
172
|
interface OfferVerdict {
|
|
171
173
|
token: string
|
|
172
174
|
code: number
|
|
175
|
+
is_offer: true
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
type MergeVerdict = ControlVerdict | OfferVerdict
|
|
@@ -179,11 +182,11 @@ type MergeVerdict = ControlVerdict | OfferVerdict
|
|
|
179
182
|
type MergeResult = MergeVerdict & { outcome: ChildOutcome }
|
|
180
183
|
|
|
181
184
|
function emit(token: MergeToken, code: number): ControlVerdict {
|
|
182
|
-
return { token, code }
|
|
185
|
+
return { token, code, is_offer: false }
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
async function offered(ctx: MergeContext): Promise<OfferVerdict> {
|
|
186
|
-
return { token: await run_merge_steps.ask_next(ctx), code: SUCCESS_EXIT_CODE }
|
|
189
|
+
return { token: await run_merge_steps.ask_next(ctx), code: SUCCESS_EXIT_CODE, is_offer: true }
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
// Whether the child's exit record shows it could not reach the API — read only when `--output` named
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { git_common_directory } from '#scripts/git/git-common-directory'
|
|
2
|
+
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
3
|
+
import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
|
|
4
|
+
import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
|
|
5
|
+
|
|
6
|
+
// Why `run:merge` parks a child whose detached ship has already ended (joshuafolkken/kit#3808).
|
|
7
|
+
//
|
|
8
|
+
// **A child that handed its region to `josh ship --detach` is parked for what its ship did.** A running
|
|
9
|
+
// ship is never parked (`merge_child` answers `shipping` first), so a park after a hand-off means the
|
|
10
|
+
// ship ended unmerged — and the session the ship relaunched to fix it ended too. The generic "session
|
|
11
|
+
// ended with its issue open" then hides the one fact a person needs: the stage the ship stopped at. The
|
|
12
|
+
// cause is that stage's `ship-stop` line, written by the supervisor since its own launch; a ship that
|
|
13
|
+
// ended without one (killed, or failed before any stage) is named by its result and its log instead.
|
|
14
|
+
|
|
15
|
+
type ShipEnding = ReturnType<typeof run_ship_detach.read_result>
|
|
16
|
+
|
|
17
|
+
const ENDED_RESULTS: ReadonlySet<string> = new Set(['failed', 'abnormal'])
|
|
18
|
+
|
|
19
|
+
type Events = ReadonlyArray<RunEvent>
|
|
20
|
+
|
|
21
|
+
// `child`'s events after its newest `ship-launch`, so an earlier launch's stop the relaunched session
|
|
22
|
+
// already fixed is never blamed for this ending. The record and the stream outlive a dispatch, so only a
|
|
23
|
+
// ship launched since the child's own newest dispatch is this attempt's: an earlier attempt's ended ship
|
|
24
|
+
// answers `undefined`, never this park.
|
|
25
|
+
function since_ship_launch(events: Events, child: string): Events | undefined {
|
|
26
|
+
const attempt = run_event_scope.since_own_launch(events, child)
|
|
27
|
+
const own = attempt.filter((event) => run_event_scope.issue_named(event) === child)
|
|
28
|
+
const launched_at = own.findLastIndex(
|
|
29
|
+
(event) => event.kind === run_event_stream.EVENT_KIND.SHIP_LAUNCH,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return launched_at === -1 ? undefined : own.slice(launched_at + 1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cause_of(ending: ShipEnding, events: Events, child: string): string | undefined {
|
|
36
|
+
if (ending === undefined || !ENDED_RESULTS.has(ending.result)) return undefined
|
|
37
|
+
|
|
38
|
+
const attempt = since_ship_launch(events, child)
|
|
39
|
+
if (attempt === undefined) return undefined
|
|
40
|
+
|
|
41
|
+
const stop = attempt.findLast((event) => event.kind === run_event_stream.EVENT_KIND.SHIP_STOP)
|
|
42
|
+
|
|
43
|
+
if (stop !== undefined) return `its detached ship stopped and was not fixed: ${stop.text}.`
|
|
44
|
+
|
|
45
|
+
return `its detached ship ended ${ending.result} before recording a stopped stage — pnpm josh ship --log ${child}.`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The ship's cause for `child`'s park, read from the record and the stream this checkout's repository
|
|
49
|
+
// keys on, or `undefined` when the child handed nothing to a ship or its ship succeeded.
|
|
50
|
+
function cause(child: string, directory: string = process.cwd()): string | undefined {
|
|
51
|
+
const repository = git_common_directory.repository(directory)
|
|
52
|
+
if (repository === undefined) return undefined
|
|
53
|
+
|
|
54
|
+
const events = run_event_stream.read_events(run_event_stream.target_of(repository))
|
|
55
|
+
|
|
56
|
+
return cause_of(run_ship_detach.read_result(repository, child), events, child)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const run_merge_ship = { cause, cause_of }
|
|
60
|
+
|
|
61
|
+
export { run_merge_ship }
|
|
@@ -9,6 +9,7 @@ import { NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
|
|
|
9
9
|
import { session_cite } from '#scripts/issue/session-cite'
|
|
10
10
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
11
11
|
import { lane_close } from '#scripts/lane/lane-close'
|
|
12
|
+
import { lane_preserve } from '#scripts/lane/lane-preserve'
|
|
12
13
|
import { lane_reap } from '#scripts/lane/lane-reap'
|
|
13
14
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
14
15
|
import { lane_relaunch } from '#scripts/lane/lane-relaunch'
|
|
@@ -24,6 +25,7 @@ import { run_carry_conversation } from '#scripts/run/carry/run-carry-conversatio
|
|
|
24
25
|
import { run_cut, type RunCut } from '#scripts/run/cut/run-cut'
|
|
25
26
|
import { run_label } from '#scripts/run/run-label'
|
|
26
27
|
import { run_merge } from './run-merge'
|
|
28
|
+
import { run_merge_ship } from './run-merge-ship'
|
|
27
29
|
|
|
28
30
|
// The result of attempting to apply a carry change. Distinguishing `refused` from `applied` lets
|
|
29
31
|
// callers surface the refusal as a hard failure rather than silently continuing on a carry that was
|
|
@@ -167,18 +169,8 @@ async function close_lane(child: string): Promise<void> {
|
|
|
167
169
|
await josh(['lane:close', child])
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
function preserved_comment(message: string): string {
|
|
171
|
-
return [
|
|
172
|
-
'The lane held uncommitted work when `run:merge` closed it, so it was stashed before the close:',
|
|
173
|
-
`- recover with \`pnpm josh stash:pop "${message}"\``,
|
|
174
|
-
].join('\n')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
172
|
async function stash_work(child: string, directory: string): Promise<void> {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
await git_stash.push(message, directory)
|
|
181
|
-
await git_gh_issue_write.issue_try_comment(child, preserved_comment(message))
|
|
173
|
+
await lane_preserve.stash_work(child, directory, LANE_CLOSE_OCCASION, '`run:merge` closed it')
|
|
182
174
|
}
|
|
183
175
|
|
|
184
176
|
// Stash whatever the lane still holds uncommitted before its tree is removed: `lane:close` deletes by
|
|
@@ -344,13 +336,14 @@ async function park_failed(ctx: MergeContext, cause: string): Promise<FailedResu
|
|
|
344
336
|
}
|
|
345
337
|
|
|
346
338
|
// A child that ended unfinished: waiting when it records an open blocker (`do_waiting`), parked
|
|
347
|
-
// otherwise (`park_failed`) — an order already recorded is never a decision for a person.
|
|
348
|
-
|
|
339
|
+
// otherwise (`park_failed`) — an order already recorded is never a decision for a person. Without a
|
|
340
|
+
// cause of the caller's, a child whose detached ship ended is parked for the ship's stop.
|
|
341
|
+
async function do_failed(ctx: MergeContext, cause?: string): Promise<FailedResult> {
|
|
349
342
|
const blockers = await open_blockers(ctx.child)
|
|
350
343
|
|
|
351
344
|
if (blockers.length > 0) return await do_waiting(ctx, blockers)
|
|
352
345
|
|
|
353
|
-
return await park_failed(ctx, cause)
|
|
346
|
+
return await park_failed(ctx, cause ?? run_merge_ship.cause(ctx.child) ?? FAILED_CAUSE)
|
|
354
347
|
}
|
|
355
348
|
|
|
356
349
|
// The outcome of counting an outage: the record to read the streak against, and whether the count was
|
|
@@ -24,7 +24,21 @@ const MERGE_TOKEN = {
|
|
|
24
24
|
|
|
25
25
|
type MergeToken = (typeof MERGE_TOKEN)[keyof typeof MERGE_TOKEN]
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// Where the token came from: `run:merge`'s own control verdict, or the next offer (`backlog:next` /
|
|
28
|
+
// `epic:next`) passed through as it was read. The offer has a `stop` of its own — nothing ready to start
|
|
29
|
+
// is left — spelled like the control `stop`, so the source travels beside the token.
|
|
30
|
+
interface MergeTokenRead {
|
|
31
|
+
token: string
|
|
32
|
+
is_offer: boolean
|
|
33
|
+
}
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
// The single reading of "this merge stopped the run": the control `stop` alone. An offer's `stop` is a
|
|
36
|
+
// parked backlog, which `backlog:budget` decides — a watch while the idle budget lasts — never the merge.
|
|
37
|
+
function is_control_stop(read: MergeTokenRead): boolean {
|
|
38
|
+
return !read.is_offer && read.token === MERGE_TOKEN.STOP
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const run_merge_token = { MERGE_TOKEN, is_control_stop }
|
|
42
|
+
|
|
43
|
+
export type { MergeToken, MergeTokenRead }
|
|
30
44
|
export { run_merge_token }
|
package/scripts/run/run-step.ts
CHANGED
|
@@ -222,6 +222,8 @@ const FULL_INPUT_ACTIONS: Record<string, (input: StepInput) => StepAction> = {
|
|
|
222
222
|
[KIND.STOP]: stop_action,
|
|
223
223
|
[KIND.DRAIN]: drain_action,
|
|
224
224
|
[KIND.CHILD_LAUNCH]: child_launch_action,
|
|
225
|
+
// A person's restart re-dispatched the child, so the parent's position is a launch's.
|
|
226
|
+
[KIND.RESTART]: child_launch_action,
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
// An event the table does not name leaves the position unknown rather than guessing a next step. A
|
|
@@ -274,6 +274,18 @@ async function open_context(args: ShipArguments): Promise<ShipContext> {
|
|
|
274
274
|
return { target, done, state, started }
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// Every stop goes to the lane ledger — supervised, in an agent's own turn, or a lane child's preflight
|
|
278
|
+
// before the hand-off — so `lane:stats` can say how long a ship stayed stopped for each reason.
|
|
279
|
+
async function record_stop(
|
|
280
|
+
args: ShipArguments,
|
|
281
|
+
stage: Stage,
|
|
282
|
+
conflicts?: ReadonlyArray<string>,
|
|
283
|
+
): Promise<void> {
|
|
284
|
+
const reason = run_ship_stage.stop_reason(stage, conflicts)
|
|
285
|
+
|
|
286
|
+
await lane_ledger.record_stop({ issue: Number(args.number), reason })
|
|
287
|
+
}
|
|
288
|
+
|
|
277
289
|
// A detached supervisor hands the stopped stage back (`run-ship-return.ts`); a
|
|
278
290
|
// ship in an agent's own turn has its report in front of that agent already. The `ship-stop` ends the
|
|
279
291
|
// attempt, so the record's started stages go with it.
|
|
@@ -283,8 +295,11 @@ async function stopped(
|
|
|
283
295
|
step: Step,
|
|
284
296
|
context: ShipContext,
|
|
285
297
|
): Promise<ReadonlyArray<ShipSection>> {
|
|
298
|
+
const conflicts = sections.at(-1)?.conflicts
|
|
299
|
+
|
|
300
|
+
await record_stop(args, step.stage, conflicts)
|
|
301
|
+
|
|
286
302
|
if (run_ship_detach.is_supervised()) {
|
|
287
|
-
const conflicts = sections.at(-1)?.conflicts
|
|
288
303
|
const resume = run_ship_next.resume_of(args)
|
|
289
304
|
|
|
290
305
|
record(context.target, run_ship_stage.end_attempt)
|
|
@@ -371,7 +386,12 @@ function should_detach(args: ShipArguments): boolean {
|
|
|
371
386
|
// supervisor's gate runs them again rather than passing over them.
|
|
372
387
|
async function pre_detach_sections(args: ShipArguments): Promise<ReadonlyArray<ShipSection>> {
|
|
373
388
|
const preflight = await run_stage(run_ship_steps.PREFLIGHT_STEP, args, await open_context(args))
|
|
374
|
-
|
|
389
|
+
|
|
390
|
+
if (preflight.code !== SUCCESS_EXIT_CODE) {
|
|
391
|
+
await record_stop(args, run_ship_steps.PREFLIGHT_STEP.stage)
|
|
392
|
+
|
|
393
|
+
return [preflight]
|
|
394
|
+
}
|
|
375
395
|
|
|
376
396
|
const result = await run_ship_pre_detach.checks()
|
|
377
397
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
2
|
-
import { lane_child_invocation } from '#scripts/lane/lane-child-invocation'
|
|
2
|
+
import { lane_child_invocation, type ShipStop } from '#scripts/lane/lane-child-invocation'
|
|
3
3
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
4
4
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
5
5
|
import { lane_relaunch } from '#scripts/lane/lane-relaunch'
|
|
6
|
+
import { lane_session_broker } from '#scripts/lane/lane-session-broker'
|
|
6
7
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
7
8
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
8
9
|
import type { ShipResume } from './run-ship-next'
|
|
@@ -49,6 +50,31 @@ function note(text: string): void {
|
|
|
49
50
|
process.stderr.write(`${text}\n`)
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function relaunched(lane: LaneInfo, stop: ShipStop): ReturnOutcome {
|
|
54
|
+
const invocation = lane_child_invocation.ship_stop_invocation(lane.issue, stop)
|
|
55
|
+
const phase = agent_role_profile.IMPLEMENTATION_PHASE
|
|
56
|
+
const result = lane_relaunch.relaunch(lane, invocation, phase, note)
|
|
57
|
+
|
|
58
|
+
if (result.kind === 'launched') return 'relaunched'
|
|
59
|
+
|
|
60
|
+
note(`the lane child could not be relaunched: ${result.note}`)
|
|
61
|
+
|
|
62
|
+
return 'recorded'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// **A served lane's child is relaunched by its broker, not from here**: this supervisor descends from the
|
|
66
|
+
// child's Bash tool, which Claude Code starts without the token, so a child started here could not sign
|
|
67
|
+
// in where the token is the only credential (joshuafolkken/kit#3817).
|
|
68
|
+
async function brokered(lane: LaneInfo, stop: ShipStop): Promise<ReturnOutcome> {
|
|
69
|
+
if (await lane_session_broker.request(lane.directory, lane.issue, { kind: 'relaunch', stop })) {
|
|
70
|
+
return 'relaunched'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
note('the lane child could not be relaunched: the lane broker did not start it')
|
|
74
|
+
|
|
75
|
+
return 'recorded'
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
async function return_control(
|
|
53
79
|
issue: string,
|
|
54
80
|
stage: Stage,
|
|
@@ -63,19 +89,11 @@ async function return_control(
|
|
|
63
89
|
|
|
64
90
|
if (lane === undefined) return 'recorded'
|
|
65
91
|
|
|
66
|
-
const
|
|
67
|
-
const result = lane_relaunch.relaunch(
|
|
68
|
-
lane,
|
|
69
|
-
invocation,
|
|
70
|
-
agent_role_profile.IMPLEMENTATION_PHASE,
|
|
71
|
-
note,
|
|
72
|
-
)
|
|
92
|
+
const stop = { stage, ...resume }
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return 'recorded'
|
|
94
|
+
return lane_session_broker.is_serving(lane.directory, issue)
|
|
95
|
+
? await brokered(lane, stop)
|
|
96
|
+
: relaunched(lane, stop)
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
const run_ship_return = { return_control }
|
|
@@ -4,7 +4,7 @@ import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
|
4
4
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
5
5
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
6
6
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
7
|
-
import {
|
|
7
|
+
import { lane_session_broker } from '#scripts/lane/lane-session-broker'
|
|
8
8
|
import { review_record } from '#scripts/review/review-record'
|
|
9
9
|
import { detached_launch } from '#scripts/run/detached-launch'
|
|
10
10
|
import { run_ship_review, type RoundOutcome, type ScoredVerdict } from './run-ship-review'
|
|
@@ -66,7 +66,7 @@ function failure(out: string): JoshResult {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function paths(): { brief: string; findings: string; log: string } {
|
|
69
|
-
return
|
|
69
|
+
return lane_session_broker.review_paths(PROJECT_ROOT)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
|
|
@@ -94,6 +94,16 @@ async function run_direct_session(
|
|
|
94
94
|
return { code: SUCCESS_EXIT_CODE, out: '' }
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// A lane child's reviewer is started by the lane's broker: an OpenAI lane's always, since a nested Codex
|
|
98
|
+
// cannot write outside its sandbox, and a Claude lane's whenever the `run:wake` supervisor serves it, since this
|
|
99
|
+
// ship was started from the child's Bash tool and so never received a token-only credential
|
|
100
|
+
// (joshuafolkken/kit#3817). A Claude lane nobody serves starts the reviewer itself, as before.
|
|
101
|
+
function is_brokered(profile: AgentProfile, issue: string): boolean {
|
|
102
|
+
if (!lane_child_marker.is_child_of(PROJECT_ROOT)) return false
|
|
103
|
+
|
|
104
|
+
return profile.provider === 'openai' || lane_session_broker.is_serving(PROJECT_ROOT, issue)
|
|
105
|
+
}
|
|
106
|
+
|
|
97
107
|
async function run_session(
|
|
98
108
|
prompt: string,
|
|
99
109
|
log_path: string,
|
|
@@ -103,11 +113,14 @@ async function run_session(
|
|
|
103
113
|
const built = agent_argv.resolve_in(prompt, agent_role_profile.REVIEWER, PROJECT_ROOT)
|
|
104
114
|
if (built.kind === 'rejected') return failure(`reviewer profile rejected: ${built.note}`)
|
|
105
115
|
|
|
106
|
-
if (built.profile
|
|
116
|
+
if (!is_brokered(built.profile, issue)) {
|
|
107
117
|
return await run_direct_session(built.argv, built.profile, log_path)
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
const is_success = await
|
|
120
|
+
const is_success = await lane_session_broker.request(PROJECT_ROOT, issue, {
|
|
121
|
+
kind: 'review',
|
|
122
|
+
round,
|
|
123
|
+
})
|
|
111
124
|
|
|
112
125
|
return is_success ? { code: SUCCESS_EXIT_CODE, out: '' } : failure('isolated reviewer failed')
|
|
113
126
|
}
|