@joshuafolkken/kit 1.1471.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/epic-commands/SKILL.md +2 -14
- package/.claude/skills/epic-commands/epic-bundle.md +9 -5
- package/.claude/skills/epic-commands/execution-waves.md +1 -2
- package/.claude/skills/verify-ui/SKILL.md +1 -2
- package/.claude/skills/workflow-commands/SKILL.md +7 -9
- package/.claude/skills/workflow-commands/background-commands.md +3 -7
- package/.claude/skills/workflow-commands/backlogrun-child.md +3 -10
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -15
- package/.claude/skills/workflow-commands/backlogrun-park.md +2 -8
- package/.claude/skills/workflow-commands/backlogrun-progress.md +10 -19
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +8 -8
- package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -10
- package/.claude/skills/workflow-commands/backlogrun.md +31 -50
- package/.claude/skills/workflow-commands/chain-rule.md +7 -6
- 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 +3 -6
- 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 +3 -3
- package/.claude/skills/workflow-commands/issue-scout.md +3 -2
- 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 +0 -14
- package/.claude/skills/workflow-commands/observation-ledger.md +4 -9
- package/.claude/skills/workflow-commands/pre-gate-cut.md +11 -16
- package/.claude/skills/workflow-commands/prerequisite.md +1 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +2 -7
- package/.claude/skills/workflow-commands/prrun.md +3 -5
- package/.claude/skills/workflow-commands/retrospective.md +4 -4
- package/.claude/skills/workflow-commands/split-assessment.md +1 -3
- package/.claude/skills/workflow-commands/target-repository.md +1 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +2 -4
- 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 +1 -1
- package/cspell/index.yaml +7 -0
- package/dist/CLAUDE.md +1 -1
- 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-J47HU5H5.js → chunk-6TS2JD6D.js} +3 -3
- package/dist/hooks/chunk-7DSBJVFH.js +50 -0
- package/dist/hooks/{chunk-YHBZAJSV.js → chunk-A65Y4YOR.js} +3 -3
- package/dist/hooks/{chunk-E7OIKX5Z.js → chunk-APUO6U4J.js} +85 -7
- package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
- package/dist/hooks/chunk-GP4IEC4O.js +20 -0
- package/dist/hooks/{chunk-SHGPANFB.js → chunk-JP45SOCJ.js} +3 -3
- package/dist/hooks/{chunk-K4EOONYM.js → chunk-LAOV7NLM.js} +1 -1
- package/dist/hooks/chunk-M4HGZV7U.js +83 -0
- package/dist/hooks/{chunk-KC5AK2GF.js → chunk-O6GDRNJB.js} +3 -3
- package/dist/hooks/{chunk-YYPI7SJZ.js → chunk-PF2NZL2A.js} +5 -5
- package/dist/hooks/{chunk-H5HVWYXK.js → chunk-PTICX4OL.js} +3 -4
- package/dist/hooks/{chunk-IIGFYXKC.js → chunk-RT27QRJW.js} +581 -1438
- package/dist/hooks/{chunk-43ZHT22P.js → chunk-SYOTDYCI.js} +1127 -1089
- package/dist/hooks/{chunk-AS74OU3D.js → chunk-TNUMY6NL.js} +1507 -1336
- package/dist/hooks/chunk-UPUZF3AN.js +279 -0
- package/dist/hooks/{chunk-EUMACUK2.js → chunk-WK6SMBIC.js} +1948 -806
- 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 +317 -264
- package/dist/hooks/investigation-guard.js +8 -11
- package/dist/hooks/pretool-guard.js +16 -13
- package/dist/hooks/session-lang.js +370 -25
- package/dist/hooks/stop-guard.js +2576 -176
- package/dist/josh.js +2124 -277
- package/lefthook/base.yml +5 -3
- package/package.json +12 -10
- package/prompts/collaboration-workflow/file-edits.md +1 -3
- package/prompts/collaboration-workflow/gh-rest.md +1 -2
- package/prompts/collaboration-workflow/issue-citation.md +2 -4
- package/prompts/collaboration-workflow/issue-template.md +10 -2
- package/prompts/collaboration-workflow/lane-instructions.md +35 -0
- package/prompts/collaboration-workflow/operating-rules.md +2 -6
- package/prompts/collaboration-workflow/output-bounds.md +3 -3
- package/prompts/collaboration-workflow/overview.md +1 -1
- package/prompts/collaboration-workflow/principles.md +14 -8
- package/prompts/collaboration-workflow/report-format.md +9 -0
- package/prompts/collaboration-workflow/residency.md +12 -8
- package/prompts/collaboration-workflow/rule-delivery.md +3 -11
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +5 -5
- package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
- package/prompts/collaboration-workflow/wip-cap.md +6 -6
- package/prompts/collaboration-workflow.md +1 -0
- package/prompts/review.md +4 -5
- package/prompts/testing-guide.md +2 -2
- package/scripts/agent/agent-argv.ts +20 -4
- 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 +2 -0
- package/scripts/agent/lane-instructions.ts +144 -0
- package/scripts/backlog/backlog-budget.ts +4 -4
- package/scripts/backlog/backlog-drive-named.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +9 -1
- package/scripts/backlog/backlog-occupied.ts +56 -0
- package/scripts/backlog/backlog-ready.ts +47 -10
- package/scripts/delegation/duplicate-read-guard.ts +2 -2
- package/scripts/delegation/investigation-guard.ts +15 -65
- package/scripts/delegation/investigation-reads.ts +0 -1
- package/scripts/document/document-byte-budget.ts +6 -5
- package/scripts/document/document-section-cli.ts +17 -5
- package/scripts/document/entry-read-budget.ts +2 -2
- package/scripts/document/read-set-cli.ts +4 -1
- package/scripts/epic/epic-audit-orphans.ts +5 -8
- package/scripts/epic/epic-audit.ts +52 -1
- 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/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 +58 -125
- 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 +13 -1
- 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/pre-commit-lint.ts +91 -0
- package/scripts/hooks/pre-push-unit.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +27 -52
- package/scripts/hooks/reserved-run.ts +1 -0
- package/scripts/hooks/run-hook.sh +10 -12
- package/scripts/hooks/stop-actions.ts +204 -0
- package/scripts/hooks/stop-guard.ts +62 -21
- package/scripts/init/hook-launch.ts +12 -7
- 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 +19 -14
- package/scripts/issue/markdown-section.ts +19 -5
- package/scripts/josh/command-usage.ts +110 -0
- 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-capacity.ts +8 -2
- package/scripts/lane/lane-check-stats.ts +56 -0
- package/scripts/lane/lane-child-invocation.ts +35 -4
- package/scripts/lane/lane-dispatch.ts +3 -3
- package/scripts/lane/lane-guard-policy.ts +2 -2
- package/scripts/lane/lane-handoff.ts +8 -1
- package/scripts/lane/lane-install.ts +12 -4
- package/scripts/lane/lane-ledger.ts +43 -1
- package/scripts/lane/lane-limit-cli.ts +8 -9
- package/scripts/lane/lane-measure-cli.ts +2 -0
- package/scripts/lane/lane-preserve.ts +29 -1
- package/scripts/lane/lane-registry.ts +9 -7
- package/scripts/lane/lane-restart.ts +5 -26
- package/scripts/lane/lane-session-action.ts +146 -0
- package/scripts/lane/lane-session-broker.ts +57 -138
- 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/notify/telegram-test.ts +10 -5
- package/scripts/report/report-format-reference.ts +2 -0
- 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/rules/agent-model.ts +59 -0
- package/scripts/rules/delivered-rules.ts +11 -2
- package/scripts/rules/filing-cap.ts +1 -0
- package/scripts/rules/firing-point.ts +6 -5
- package/scripts/rules/guard-remedy.ts +117 -0
- package/scripts/rules/issue-comments.ts +67 -6
- package/scripts/rules/last-prompt.ts +50 -5
- package/scripts/rules/oracle-list-cli.ts +7 -0
- package/scripts/rules/piped-verification.ts +3 -1
- 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/shell-segments.ts +15 -0
- package/scripts/rules/stop-decision.ts +77 -0
- package/scripts/rules/stop-rules.ts +147 -47
- package/scripts/rules/tail-commands.ts +15 -1
- 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 +9 -1
- package/scripts/run/board/run-board-header.ts +4 -1
- 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 +16 -0
- package/scripts/run/board/run-board-reason.ts +10 -1
- 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 +5 -0
- package/scripts/run/board/run-board-status.ts +72 -21
- package/scripts/run/board/run-board-tick.ts +5 -1
- package/scripts/run/cut/run-cut-cli.ts +3 -2
- package/scripts/run/cut/run-cut-report.ts +16 -0
- 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-session.ts +46 -0
- package/scripts/run/hold/run-hold.ts +7 -1
- package/scripts/run/launch-priority.ts +32 -0
- package/scripts/run/merge/run-merge-cli.ts +16 -65
- 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 +21 -9
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-poll-hook.ts +157 -0
- package/scripts/run/run-step-cli.ts +22 -15
- package/scripts/run/run-step-section.ts +85 -0
- 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 +12 -10
- 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/test/test-e2e-guard.ts +30 -5
- package/scripts/test/test-unit-guard.ts +16 -1
- package/scripts/test/unit-worker-share.ts +26 -4
- package/scripts/time-runtime/time-shell.ts +29 -4
- package/scripts/time-runtime/time-spans.ts +1 -0
- package/scripts/time-runtime/time-tool-call.ts +7 -0
- package/templates/gitignore +6 -2
- package/dist/hooks/chunk-K4LMBQC2.js +0 -68
|
@@ -45,9 +45,9 @@ interface LaneGuardEntry {
|
|
|
45
45
|
const LANE_GUARD_POLICY: ReadonlyArray<LaneGuardEntry> = [
|
|
46
46
|
{
|
|
47
47
|
id: 'investigation',
|
|
48
|
-
mode_in_lane_child: '
|
|
48
|
+
mode_in_lane_child: 'refuse',
|
|
49
49
|
because:
|
|
50
|
-
'
|
|
50
|
+
'the notice was measured not to move the reading — 52% of lane cost stayed in main-line exploration — and the refusal fires once per accumulation, a round trip the child survives as it survives the rule row, so the child is refused like the main line',
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
id: 'batching',
|
|
@@ -24,6 +24,13 @@ function is_ship_running(issue: string, directory: string): boolean {
|
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// When the detached ship for `issue` that is still live was launched, or `undefined` where none is.
|
|
28
|
+
function ship_running_since(issue: string, directory: string): number | undefined {
|
|
29
|
+
const repository = git_common_directory.repository(directory)
|
|
30
|
+
|
|
31
|
+
return repository === undefined ? undefined : run_ship_detach.running_since(repository, issue)
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
// This session is the dispatched lane child for `directory` and its own ship is running.
|
|
28
35
|
function is_handed_off(directory: string, source: MarkerSource = process.env): boolean {
|
|
29
36
|
const issue = lane_child_marker.marked_issue(source)
|
|
@@ -33,6 +40,6 @@ function is_handed_off(directory: string, source: MarkerSource = process.env): b
|
|
|
33
40
|
return is_ship_running(issue, directory)
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
const lane_handoff = { is_handed_off, is_ship_running }
|
|
43
|
+
const lane_handoff = { is_handed_off, is_ship_running, ship_running_since }
|
|
37
44
|
|
|
38
45
|
export { lane_handoff }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { core_budget } from '#scripts/gate/core-budget'
|
|
3
4
|
import { buffered_process } from '#scripts/lib/buffered-process'
|
|
4
5
|
import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
5
6
|
|
|
@@ -68,10 +69,17 @@ function has_manifest(directory: string): boolean {
|
|
|
68
69
|
async function install_dependencies(directory: string): Promise<InstallResult> {
|
|
69
70
|
if (!has_manifest(directory)) return { is_installed: true, output: NO_MANIFEST_OUTPUT }
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
// The install claims its place in the core budget (joshuafolkken/kit#3944): a batch opens its lanes
|
|
73
|
+
// together, and each install ran beside the other lanes' gates off the ledger.
|
|
74
|
+
const result = await core_budget.with_command_reservation(
|
|
75
|
+
core_budget.CORE_WEIGHTS.install,
|
|
76
|
+
async () =>
|
|
77
|
+
await buffered_process.run_buffered_process(INSTALL_ARGUMENTS, {
|
|
78
|
+
cwd: directory,
|
|
79
|
+
timeout_ms: INSTALL_TIMEOUT_MS,
|
|
80
|
+
}),
|
|
81
|
+
{ memory_mb: core_budget.MEMORY_MB.install, command: 'lane install' },
|
|
82
|
+
)
|
|
75
83
|
|
|
76
84
|
return { is_installed: !buffered_process.is_process_failed(result), output: result.output }
|
|
77
85
|
}
|
|
@@ -36,9 +36,16 @@ const KIND = {
|
|
|
36
36
|
DISPATCH: 'dispatch',
|
|
37
37
|
STAGE: 'stage',
|
|
38
38
|
STOP: 'stop',
|
|
39
|
+
OVERLAP: 'overlap',
|
|
39
40
|
} as const
|
|
40
41
|
|
|
41
42
|
const merge_schema = z.object({ kind: z.literal(KIND.MERGE), at: z.string(), issue: z.number() })
|
|
43
|
+
const check_schema = z.object({
|
|
44
|
+
label: z.string(),
|
|
45
|
+
elapsed_ms: z.number(),
|
|
46
|
+
is_passed: z.boolean(),
|
|
47
|
+
is_reused: z.boolean().optional(),
|
|
48
|
+
})
|
|
42
49
|
const gate_schema = z.object({
|
|
43
50
|
kind: z.literal(KIND.GATE),
|
|
44
51
|
at: z.string(),
|
|
@@ -50,6 +57,9 @@ const gate_schema = z.object({
|
|
|
50
57
|
// The whole cores busy outside this gate, beyond the machine's baseline, at its start or its end —
|
|
51
58
|
// whichever was higher. Absent when the machine could not be read.
|
|
52
59
|
external_cores: z.number().optional(),
|
|
60
|
+
// Each check's own duration and verdict, so `josh lane:stats` can name the check a slow gate waited
|
|
61
|
+
// on. A reused check started no process: its row says so, and its zero is no timing.
|
|
62
|
+
checks: z.array(check_schema).optional(),
|
|
53
63
|
})
|
|
54
64
|
// `available_mb`, `swapped_mb` and `lanes` are optional because each read can fail on its own — a
|
|
55
65
|
// platform with no memory reading, a `git worktree` call that did not answer — and a sample is still
|
|
@@ -98,6 +108,18 @@ const stop_schema = z.object({
|
|
|
98
108
|
issue: z.number(),
|
|
99
109
|
reason: z.string(),
|
|
100
110
|
})
|
|
111
|
+
// A `josh ship` on `issue` whose actual change overlapped the open pull requests of `lanes` on `paths`
|
|
112
|
+
// before its sync: it waited `waited_ms` for their ships to end, and `is_cleared` is false where the
|
|
113
|
+
// wait ran out first (joshuafolkken/kit#3901).
|
|
114
|
+
const overlap_schema = z.object({
|
|
115
|
+
kind: z.literal(KIND.OVERLAP),
|
|
116
|
+
at: z.string(),
|
|
117
|
+
issue: z.number(),
|
|
118
|
+
lanes: z.array(z.number()),
|
|
119
|
+
paths: z.array(z.string()),
|
|
120
|
+
waited_ms: z.number(),
|
|
121
|
+
is_cleared: z.boolean(),
|
|
122
|
+
})
|
|
101
123
|
const entry_schema = z.discriminatedUnion('kind', [
|
|
102
124
|
merge_schema,
|
|
103
125
|
gate_schema,
|
|
@@ -105,6 +127,7 @@ const entry_schema = z.discriminatedUnion('kind', [
|
|
|
105
127
|
dispatch_schema,
|
|
106
128
|
stage_schema,
|
|
107
129
|
stop_schema,
|
|
130
|
+
overlap_schema,
|
|
108
131
|
])
|
|
109
132
|
|
|
110
133
|
type LedgerEntry = z.infer<typeof entry_schema>
|
|
@@ -114,10 +137,13 @@ type LoadEntry = z.infer<typeof load_schema>
|
|
|
114
137
|
type DispatchEntry = z.infer<typeof dispatch_schema>
|
|
115
138
|
type StageEntry = z.infer<typeof stage_schema>
|
|
116
139
|
type StopEntry = z.infer<typeof stop_schema>
|
|
140
|
+
type OverlapEntry = z.infer<typeof overlap_schema>
|
|
117
141
|
// What a finished gate reports; the ledger stamps the kind and the time.
|
|
118
142
|
type GateRecord = Omit<GateEntry, 'kind' | 'at'>
|
|
143
|
+
type GateCheckRecord = z.infer<typeof check_schema>
|
|
119
144
|
type StageRecord = Omit<StageEntry, 'kind' | 'at'>
|
|
120
145
|
type StopRecord = Omit<StopEntry, 'kind' | 'at'>
|
|
146
|
+
type OverlapRecord = Omit<OverlapEntry, 'kind' | 'at'>
|
|
121
147
|
|
|
122
148
|
function target_of(repository: string): string {
|
|
123
149
|
return stamp_file.stamp_path(LEDGER_PREFIX, repository, LEDGER_SUFFIX)
|
|
@@ -194,6 +220,10 @@ async function record_stop(stop: StopRecord, path?: string): Promise<void> {
|
|
|
194
220
|
await record({ kind: KIND.STOP, at: now_iso(), ...stop }, path)
|
|
195
221
|
}
|
|
196
222
|
|
|
223
|
+
async function record_overlap(overlap: OverlapRecord, path?: string): Promise<void> {
|
|
224
|
+
await record({ kind: KIND.OVERLAP, at: now_iso(), ...overlap }, path)
|
|
225
|
+
}
|
|
226
|
+
|
|
197
227
|
function parse_line(line: string): LedgerEntry | undefined {
|
|
198
228
|
return entry_schema.safeParse(json_value.parse_or_undefined(line)).data
|
|
199
229
|
}
|
|
@@ -224,11 +254,23 @@ const lane_ledger = {
|
|
|
224
254
|
record_dispatch,
|
|
225
255
|
record_gate,
|
|
226
256
|
record_merge,
|
|
257
|
+
record_overlap,
|
|
227
258
|
record_stage,
|
|
228
259
|
record_stop,
|
|
229
260
|
target,
|
|
230
261
|
target_of,
|
|
231
262
|
}
|
|
232
263
|
|
|
233
|
-
export type {
|
|
264
|
+
export type {
|
|
265
|
+
DispatchEntry,
|
|
266
|
+
GateCheckRecord,
|
|
267
|
+
GateEntry,
|
|
268
|
+
LedgerEntry,
|
|
269
|
+
LoadEntry,
|
|
270
|
+
MergeEntry,
|
|
271
|
+
OverlapEntry,
|
|
272
|
+
OverlapRecord,
|
|
273
|
+
StageEntry,
|
|
274
|
+
StopEntry,
|
|
275
|
+
}
|
|
234
276
|
export { lane_ledger }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { backlog_ready } from '#scripts/backlog/backlog-ready'
|
|
3
|
+
import { backlog_ready, type LaneUse } from '#scripts/backlog/backlog-ready'
|
|
4
4
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { lane_capacity, type LimitChoice } from './lane-capacity'
|
|
6
6
|
import { lane_limit_override } from './lane-limit-override'
|
|
@@ -37,13 +37,13 @@ interface LimitPorts {
|
|
|
37
37
|
read_override: () => Promise<number | undefined>
|
|
38
38
|
write_override: (limit: number | undefined) => Promise<boolean>
|
|
39
39
|
emit_raise: (text: string) => Promise<void>
|
|
40
|
-
|
|
40
|
+
lane_use: () => Promise<LaneUse>
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
interface LimitStatus {
|
|
44
44
|
limit: number
|
|
45
45
|
override: number | undefined
|
|
46
|
-
|
|
46
|
+
use: LaneUse
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const SHOW: LimitRequest = { kind: 'show' }
|
|
@@ -55,7 +55,7 @@ const DEFAULT_PORTS: LimitPorts = {
|
|
|
55
55
|
read_override: lane_limit_override.read_override,
|
|
56
56
|
write_override: lane_limit_override.write_override,
|
|
57
57
|
emit_raise: lane_limit_override.emit_raise,
|
|
58
|
-
|
|
58
|
+
lane_use: backlog_ready.lane_use,
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function refuse(message: string): number {
|
|
@@ -98,9 +98,10 @@ function limit_text(limit: number): string {
|
|
|
98
98
|
|
|
99
99
|
function status_line(status: LimitStatus): string {
|
|
100
100
|
const source = status.override === undefined ? lane_capacity.LANE_LIMIT_KEY : OVERRIDE_SOURCE
|
|
101
|
-
const
|
|
101
|
+
const { occupied, seated } = status.use
|
|
102
|
+
const free = lane_capacity.free_lanes(status.limit, occupied, seated)
|
|
102
103
|
|
|
103
|
-
return `lane limit ${limit_text(status.limit)} (${source}) · in use ${String(
|
|
104
|
+
return `lane limit ${limit_text(status.limit)} (${source}) · in use ${String(occupied)} · free ${String(free)}`
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
async function show(ports: LimitPorts): Promise<number> {
|
|
@@ -110,9 +111,7 @@ async function show(ports: LimitPorts): Promise<number> {
|
|
|
110
111
|
|
|
111
112
|
const override = await ports.read_override()
|
|
112
113
|
|
|
113
|
-
console.info(
|
|
114
|
-
status_line({ limit: choice.limit, override, in_use: await ports.live_lane_count() }),
|
|
115
|
-
)
|
|
114
|
+
console.info(status_line({ limit: choice.limit, override, use: await ports.lane_use() }))
|
|
116
115
|
|
|
117
116
|
return SUCCESS_EXIT_CODE
|
|
118
117
|
}
|
|
@@ -3,6 +3,7 @@ import { setInterval as every } from 'node:timers/promises'
|
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { lane_capacity } from './lane-capacity'
|
|
6
|
+
import { lane_check_stats } from './lane-check-stats'
|
|
6
7
|
import { lane_ledger } from './lane-ledger'
|
|
7
8
|
import { lane_process_stats } from './lane-process-stats'
|
|
8
9
|
import { lane_sampler, type MeasureContext } from './lane-sampler'
|
|
@@ -121,6 +122,7 @@ async function stats_command(
|
|
|
121
122
|
lane_stage_stats.table(entries, window),
|
|
122
123
|
lane_stop_stats.table(entries, window),
|
|
123
124
|
lane_process_stats.table(entries, window),
|
|
125
|
+
lane_check_stats.table(entries, window),
|
|
124
126
|
].join('\n\n'),
|
|
125
127
|
)
|
|
126
128
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
|
|
2
2
|
import { git_stash } from '#scripts/git/stash/git-stash'
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
|
|
4
5
|
// Keeping a lane's uncommitted work before something discards or restarts it — `run:merge` closing a
|
|
5
6
|
// lane, `lane:restart` handing it to a fresh session. The stash stack outlives the tree, so the push is
|
|
@@ -30,6 +31,32 @@ async function announce(issue: string, message: string, cause: string): Promise<
|
|
|
30
31
|
await git_gh_issue_write.issue_try_comment(issue, preserved_comment(message, cause))
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// The tree's work kept before a stopped child's lane is reused or closed: `saved` with the stash, or
|
|
35
|
+
// none for a clean tree, and `unsaved` with the reason when the tree could not be read or pushed — the
|
|
36
|
+
// caller then leaves the tree as it is rather than handing it on or deleting it.
|
|
37
|
+
type KeptWork =
|
|
38
|
+
| { kind: 'saved'; stash: string | undefined }
|
|
39
|
+
| { kind: 'unsaved'; directory: string; reason: string }
|
|
40
|
+
|
|
41
|
+
async function stash_of(
|
|
42
|
+
issue: string,
|
|
43
|
+
directory: string,
|
|
44
|
+
occasion: string,
|
|
45
|
+
): Promise<string | undefined> {
|
|
46
|
+
if (!(await git_stash.has_changes(directory))) return undefined
|
|
47
|
+
|
|
48
|
+
return await push_work(issue, directory, occasion)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Stash the tree's uncommitted work, if any, answering `unsaved` rather than throwing. */
|
|
52
|
+
async function keep_work(issue: string, directory: string, occasion: string): Promise<KeptWork> {
|
|
53
|
+
try {
|
|
54
|
+
return { kind: 'saved', stash: await stash_of(issue, directory, occasion) }
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return { kind: 'unsaved', directory, reason: error_text.message_of(error) }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
/** `push_work`, then `announce` — for a caller with no reason to separate the two. */
|
|
34
61
|
async function stash_work(
|
|
35
62
|
issue: string,
|
|
@@ -44,6 +71,7 @@ async function stash_work(
|
|
|
44
71
|
return message
|
|
45
72
|
}
|
|
46
73
|
|
|
47
|
-
const lane_preserve = { announce,
|
|
74
|
+
const lane_preserve = { announce, keep_work, stash_work }
|
|
48
75
|
|
|
76
|
+
export type { KeptWork }
|
|
49
77
|
export { lane_preserve }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { ENV_FILE_NAME } from '#ports'
|
|
4
4
|
import type { AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
@@ -38,6 +38,9 @@ interface LaneInfo {
|
|
|
38
38
|
// instead of waiting for the pool to drain before it can be cut.
|
|
39
39
|
output: string | undefined
|
|
40
40
|
profile?: AgentProfile | undefined
|
|
41
|
+
// When the lane last took a child: `lane:open` writes its `.env`, and every dispatch records its
|
|
42
|
+
// unit there (`lane-output.ts`). `undefined` where the `.env` cannot be read.
|
|
43
|
+
dispatched_ms?: number | undefined
|
|
41
44
|
is_stranded: boolean
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -106,6 +109,10 @@ function read_environment(directory: string): string | undefined {
|
|
|
106
109
|
return file_reader.read_if_readable(path.join(directory, ENV_FILE_NAME))
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
function dispatched_at(directory: string): number | undefined {
|
|
113
|
+
return statSync(path.join(directory, ENV_FILE_NAME), { throwIfNoEntry: false })?.mtimeMs
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
// A lane is identified by its branch rather than by where it sits, so nothing here compares two
|
|
110
117
|
// spellings of one path — git prints the resolved one, and a lane root reached through a symlink
|
|
111
118
|
// would otherwise read as no lane at all.
|
|
@@ -151,6 +158,7 @@ function build_lane(
|
|
|
151
158
|
preview_port: held.preview_port,
|
|
152
159
|
output,
|
|
153
160
|
profile,
|
|
161
|
+
dispatched_ms: is_stranded ? undefined : dispatched_at(directory),
|
|
154
162
|
is_stranded,
|
|
155
163
|
}
|
|
156
164
|
}
|
|
@@ -241,11 +249,6 @@ async function has_lanes_in_flight(): Promise<boolean> {
|
|
|
241
249
|
return lanes.some((lane) => !lane.is_stranded)
|
|
242
250
|
}
|
|
243
251
|
|
|
244
|
-
/** The lanes in use: the open work trees an interruption has not stranded. */
|
|
245
|
-
function live_count(lanes: ReadonlyArray<LaneInfo>): number {
|
|
246
|
-
return lanes.filter((lane) => !lane.is_stranded).length
|
|
247
|
-
}
|
|
248
|
-
|
|
249
252
|
/** The seats live lanes hold — what a new lane's seat has to avoid. */
|
|
250
253
|
function used_seats(lanes: ReadonlyArray<LaneInfo>): Array<number> {
|
|
251
254
|
return lanes.map((lane) => lane.seat).filter((seat): seat is number => seat !== undefined)
|
|
@@ -277,7 +280,6 @@ const lane_registry = {
|
|
|
277
280
|
find_open_lane,
|
|
278
281
|
has_lanes_in_flight,
|
|
279
282
|
list_lanes,
|
|
280
|
-
live_count,
|
|
281
283
|
main_repository_root,
|
|
282
284
|
parse_block,
|
|
283
285
|
read_environment,
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { git_stash } from '#scripts/git/stash/git-stash'
|
|
2
1
|
import { issue_cite } from '#scripts/issue/issue-cite'
|
|
3
2
|
import { IN_PROGRESS_LABEL } from '#scripts/issue/issue-labels'
|
|
4
3
|
import { issue_number_shape } from '#scripts/issue/issue-number-shape'
|
|
5
4
|
import { session_cite } from '#scripts/issue/session-cite'
|
|
6
|
-
import { error_text } from '#scripts/lib/error-message'
|
|
7
5
|
import { run_cut } from '#scripts/run/cut/run-cut'
|
|
8
6
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
9
7
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
10
8
|
import { lane_dispatch, type DispatchOutcome } from './lane-dispatch'
|
|
11
|
-
import { lane_preserve } from './lane-preserve'
|
|
9
|
+
import { lane_preserve, type KeptWork } from './lane-preserve'
|
|
12
10
|
import { lane_reap } from './lane-reap'
|
|
13
11
|
import { lane_registry, type LaneInfo } from './lane-registry'
|
|
14
12
|
|
|
@@ -38,11 +36,7 @@ const NO_LANE_KIND = 'no-lane'
|
|
|
38
36
|
const NOT_RUNNING_KIND = 'not-running'
|
|
39
37
|
const UNCLAIMED_KIND = 'unclaimed'
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
kind: 'unsaved'
|
|
43
|
-
directory: string
|
|
44
|
-
reason: string
|
|
45
|
-
}
|
|
39
|
+
type Unsaved = Extract<KeptWork, { kind: 'unsaved' }>
|
|
46
40
|
|
|
47
41
|
interface Restarted {
|
|
48
42
|
kind: 'restarted'
|
|
@@ -55,7 +49,6 @@ type Refused =
|
|
|
55
49
|
| { kind: typeof NOT_RUNNING_KIND }
|
|
56
50
|
| { kind: typeof UNCLAIMED_KIND; reason: string }
|
|
57
51
|
type RestartOutcome = Restarted | Unsaved | Refused
|
|
58
|
-
type Saved = Unsaved | { kind: 'saved'; stash: string | undefined }
|
|
59
52
|
|
|
60
53
|
// A cut the stopped child carried would hand its successor the old session's hand-off at
|
|
61
54
|
// `run:cut --resume`; the fresh session has to start from the Issue instead.
|
|
@@ -65,22 +58,6 @@ function end_carried_cut(lane: LaneInfo): void {
|
|
|
65
58
|
if (carried !== undefined) run_cut.end_cut(carried.target)
|
|
66
59
|
}
|
|
67
60
|
|
|
68
|
-
async function stash_of(lane: LaneInfo): Promise<string | undefined> {
|
|
69
|
-
if (!(await git_stash.has_changes(lane.directory))) return undefined
|
|
70
|
-
|
|
71
|
-
return await lane_preserve.push_work(lane.issue, lane.directory, RESTART_OCCASION)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// A tree that could not be read or pushed is left as it is: re-dispatching into it would hand the fresh
|
|
75
|
-
// session the stopped one's half-done work as though it were its own.
|
|
76
|
-
async function save_work(lane: LaneInfo): Promise<Saved> {
|
|
77
|
-
try {
|
|
78
|
-
return { kind: 'saved', stash: await stash_of(lane) }
|
|
79
|
-
} catch (error) {
|
|
80
|
-
return { kind: 'unsaved', directory: lane.directory, reason: error_text.message_of(error) }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
61
|
async function record_restart(issue: string): Promise<void> {
|
|
85
62
|
await run_event_stream_emit.emit(
|
|
86
63
|
run_event_stream.EVENT_KIND.RESTART,
|
|
@@ -88,9 +65,11 @@ async function record_restart(issue: string): Promise<void> {
|
|
|
88
65
|
)
|
|
89
66
|
}
|
|
90
67
|
|
|
68
|
+
// A tree that could not be read or pushed is left as it is: re-dispatching into it would hand the fresh
|
|
69
|
+
// session the stopped one's half-done work as though it were its own.
|
|
91
70
|
async function relaunch(lane: LaneInfo): Promise<RestartOutcome> {
|
|
92
71
|
end_carried_cut(lane)
|
|
93
|
-
const saved = await
|
|
72
|
+
const saved = await lane_preserve.keep_work(lane.issue, lane.directory, RESTART_OCCASION)
|
|
94
73
|
|
|
95
74
|
if (saved.kind === 'unsaved') return saved
|
|
96
75
|
const dispatch = await lane_dispatch.dispatch_child(lane.issue, 'restart')
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { agent_argv } from '#scripts/agent/agent-argv'
|
|
2
|
+
import { agent_role_profile, type AgentProvider } from '#scripts/agent/agent-role-profile'
|
|
3
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
5
|
+
import { detached_launch, type AttachedLaunchResult } from '#scripts/run/detached-launch'
|
|
6
|
+
import { run_ship_review } from '#scripts/run/ship/run-ship-review'
|
|
7
|
+
import { run_ship_stage } from '#scripts/run/ship/run-ship-stage'
|
|
8
|
+
import { run_ship_stop_context } from '#scripts/run/ship/run-ship-stop-context'
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
import { lane_child_invocation, type ShipStop } from './lane-child-invocation'
|
|
11
|
+
import { lane_child_marker } from './lane-child-marker'
|
|
12
|
+
import { lane_parked } from './lane-parked'
|
|
13
|
+
import type { LaneInfo } from './lane-registry'
|
|
14
|
+
import { lane_relaunch } from './lane-relaunch'
|
|
15
|
+
|
|
16
|
+
// The actions a lane's session broker performs for the ship that asked (`lane-session-broker.ts`):
|
|
17
|
+
// starting the round-1 or round-2 reviewer, or relaunching the lane's child after a stopped ship.
|
|
18
|
+
// **A failure names its reason**: a bare `false` left every relaunch a stale supervisor refused
|
|
19
|
+
// unexplained in the ship log (joshuafolkken/kit#3974).
|
|
20
|
+
|
|
21
|
+
const BRIEF_PREFIX = 'josh-ship-review-brief-'
|
|
22
|
+
const FINDINGS_PREFIX = 'josh-ship-review-findings-'
|
|
23
|
+
const LOG_PREFIX = 'josh-ship-review-log-'
|
|
24
|
+
const ANTHROPIC_PROVIDER: AgentProvider = 'anthropic'
|
|
25
|
+
const stop_schema = z
|
|
26
|
+
.object({
|
|
27
|
+
stage: z.enum(run_ship_stage.STAGE),
|
|
28
|
+
title: z.string(),
|
|
29
|
+
flags: z.array(z.string()).readonly(),
|
|
30
|
+
is_review: z.boolean(),
|
|
31
|
+
conflicts: z.array(z.string()).readonly().default([]),
|
|
32
|
+
context: run_ship_stop_context.stop_context_schema.exactOptional(),
|
|
33
|
+
})
|
|
34
|
+
.strict()
|
|
35
|
+
const action_schema = z.discriminatedUnion('kind', [
|
|
36
|
+
z.object({ kind: z.literal('review'), round: z.enum(['1', '2']) }).strict(),
|
|
37
|
+
z.object({ kind: z.literal('relaunch'), stop: stop_schema }).strict(),
|
|
38
|
+
])
|
|
39
|
+
type ReviewRound = Extract<z.infer<typeof action_schema>, { kind: 'review' }>['round']
|
|
40
|
+
type BrokerAction = { kind: 'review'; round: ReviewRound } | { kind: 'relaunch'; stop: ShipStop }
|
|
41
|
+
type BrokerAnswer = { is_success: true } | { is_success: false; reason: string }
|
|
42
|
+
|
|
43
|
+
const SUCCESS: BrokerAnswer = { is_success: true }
|
|
44
|
+
|
|
45
|
+
function failure(reason: string): BrokerAnswer {
|
|
46
|
+
return { is_success: false, reason }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function review_paths(directory: string): { brief: string; findings: string; log: string } {
|
|
50
|
+
return {
|
|
51
|
+
brief: stamp_file.stamp_path(BRIEF_PREFIX, directory, '.md'),
|
|
52
|
+
findings: stamp_file.stamp_path(FINDINGS_PREFIX, directory, '.txt'),
|
|
53
|
+
log: stamp_file.stamp_path(LOG_PREFIX, directory, '.log'),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The reviewer runs on the lane's own provider, so a Claude lane's server never starts a Codex reviewer
|
|
58
|
+
// and an OpenAI lane's never starts a Claude one.
|
|
59
|
+
function provider_of(lane: LaneInfo): AgentProvider {
|
|
60
|
+
return lane.profile?.provider ?? ANTHROPIC_PROVIDER
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// **The reviewer carries the lane's mark explicitly**: the supervisor serving a Claude lane is no lane child,
|
|
64
|
+
// so a reviewer inheriting its environment would descend from the carry record's owner unmarked and be
|
|
65
|
+
// read as the `backlogrun` parent by every hook. **Its prompt ends with the review invocation**, so the
|
|
66
|
+
// lane's liveness read and the park's reap find it as they find the child (joshuafolkken/kit#3878).
|
|
67
|
+
|
|
68
|
+
function review_prompt(lane: LaneInfo, round: ReviewRound): string {
|
|
69
|
+
const target = review_paths(lane.directory)
|
|
70
|
+
const prompt_of =
|
|
71
|
+
round === '1' ? run_ship_review.reviewer_prompt : run_ship_review.verification_prompt
|
|
72
|
+
const tail = lane_child_invocation.review_invocation(lane.issue)
|
|
73
|
+
|
|
74
|
+
return `${prompt_of(target.brief, target.findings, lane.issue)} This is ${tail}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reviewer_answer(result: AttachedLaunchResult): BrokerAnswer {
|
|
78
|
+
if (result.kind === 'failed') return failure(`the reviewer could not start: ${result.note}`)
|
|
79
|
+
|
|
80
|
+
return result.exit_code === 0
|
|
81
|
+
? SUCCESS
|
|
82
|
+
: failure(`the reviewer exited with code ${String(result.exit_code)}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function launch_review(lane: LaneInfo, round: ReviewRound): Promise<BrokerAnswer> {
|
|
86
|
+
const target = review_paths(lane.directory)
|
|
87
|
+
const built = agent_argv.review_in(review_prompt(lane, round), Number(round), lane.directory)
|
|
88
|
+
if (built.kind === 'rejected') return failure(`the reviewer profile was rejected: ${built.note}`)
|
|
89
|
+
|
|
90
|
+
if (built.profile.provider !== provider_of(lane)) {
|
|
91
|
+
return failure(`the reviewer resolved to ${built.profile.provider}, not the lane's provider`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.error(agent_role_profile.describe_review(Number(round), built.profile))
|
|
95
|
+
const result = await detached_launch.launch_attached(
|
|
96
|
+
{
|
|
97
|
+
argv: built.argv,
|
|
98
|
+
cwd: lane.directory,
|
|
99
|
+
log_path: target.log,
|
|
100
|
+
profile: built.profile,
|
|
101
|
+
env: lane_child_marker.env_for(lane.issue),
|
|
102
|
+
},
|
|
103
|
+
console.error,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return reviewer_answer(result)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// An OpenAI lane's successor is its supervisor's to start, never a relaunch's.
|
|
110
|
+
function relaunch(lane: LaneInfo, stop: ShipStop): BrokerAnswer {
|
|
111
|
+
if (lane_relaunch.is_openai_lane(lane)) {
|
|
112
|
+
return failure('an OpenAI lane is relaunched by its own supervisor')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const invocation = lane_child_invocation.ship_stop_invocation(lane.issue, stop)
|
|
116
|
+
const phase = agent_role_profile.IMPLEMENTATION_PHASE
|
|
117
|
+
const result = lane_relaunch.relaunch(lane, invocation, phase, console.error)
|
|
118
|
+
|
|
119
|
+
return result.kind === 'launched'
|
|
120
|
+
? SUCCESS
|
|
121
|
+
: failure(`the relaunch did not start: ${result.note}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// **A parked issue gets no session**: a request can be read long after it was written, so the park is
|
|
125
|
+
// read when the action runs, never when it was asked for (joshuafolkken/kit#3878).
|
|
126
|
+
async function perform(lane: LaneInfo, action: BrokerAction): Promise<BrokerAnswer> {
|
|
127
|
+
if (await lane_parked.is_parked(lane.issue)) return failure('the issue is parked')
|
|
128
|
+
|
|
129
|
+
if (action.kind === 'relaunch') return relaunch(lane, action.stop)
|
|
130
|
+
|
|
131
|
+
return await launch_review(lane, action.round)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// What the server answers: a thrown action is a failure naming its error, never a dead server.
|
|
135
|
+
async function answer(lane: LaneInfo, action: BrokerAction): Promise<BrokerAnswer> {
|
|
136
|
+
try {
|
|
137
|
+
return await perform(lane, action)
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return failure(`the broker failed: ${error_text.message_of(error)}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const lane_session_action = { SUCCESS, action_schema, answer, failure, review_paths }
|
|
144
|
+
|
|
145
|
+
export type { BrokerAction, BrokerAnswer, ReviewRound }
|
|
146
|
+
export { lane_session_action }
|