@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
|
@@ -8,7 +8,6 @@ import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
|
8
8
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
9
9
|
import { run_ship, type ShipSection } from './run-ship'
|
|
10
10
|
import { run_ship_detach } from './run-ship-detach'
|
|
11
|
-
import { run_ship_next } from './run-ship-next'
|
|
12
11
|
import { run_ship_pre_detach } from './run-ship-pre-detach'
|
|
13
12
|
import { run_ship_probe } from './run-ship-probe'
|
|
14
13
|
import { run_ship_return } from './run-ship-return'
|
|
@@ -295,19 +294,13 @@ async function stopped(
|
|
|
295
294
|
step: Step,
|
|
296
295
|
context: ShipContext,
|
|
297
296
|
): Promise<ReadonlyArray<ShipSection>> {
|
|
298
|
-
const
|
|
297
|
+
const failed = sections.at(-1)
|
|
299
298
|
|
|
300
|
-
await record_stop(args, step.stage, conflicts)
|
|
299
|
+
await record_stop(args, step.stage, failed?.conflicts)
|
|
301
300
|
|
|
302
301
|
if (run_ship_detach.is_supervised()) {
|
|
303
|
-
const resume = run_ship_next.resume_of(args)
|
|
304
|
-
|
|
305
302
|
record(context.target, run_ship_stage.end_attempt)
|
|
306
|
-
await run_ship_return.
|
|
307
|
-
args.number,
|
|
308
|
-
step.stage,
|
|
309
|
-
conflicts === undefined ? resume : { ...resume, conflicts },
|
|
310
|
-
)
|
|
303
|
+
await run_ship_return.hand_back(args, step.stage, failed)
|
|
311
304
|
}
|
|
312
305
|
|
|
313
306
|
return sections
|
|
@@ -381,8 +374,8 @@ function should_detach(args: ShipArguments): boolean {
|
|
|
381
374
|
|
|
382
375
|
// The preflight is deterministic and fast, so it runs here, in the agent's own turn, before the
|
|
383
376
|
// hand-off — a stop it finds is fixed by the same session instead of relaunching one from the
|
|
384
|
-
// supervisor. Only the slow stages go to the supervisor, which re-asks the preflight itself. The
|
|
385
|
-
//
|
|
377
|
+
// supervisor. Only the slow stages go to the supervisor, which re-asks the preflight itself. The fast
|
|
378
|
+
// checks (`run-ship-pre-detach.ts`) follow it here for the same reason; they are not a stage, so the
|
|
386
379
|
// supervisor's gate runs them again rather than passing over them.
|
|
387
380
|
async function pre_detach_sections(args: ShipArguments): Promise<ReadonlyArray<ShipSection>> {
|
|
388
381
|
const preflight = await run_stage(run_ship_steps.PREFLIGHT_STEP, args, await open_context(args))
|
|
@@ -183,6 +183,14 @@ function current_record(repository: string, number: string): ShipRecord | undefi
|
|
|
183
183
|
return record?.launch_id === process.env[LAUNCH_ID_KEY] ? record : undefined
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// When the ship for `number` that is still running was launched — `undefined` where none is running or
|
|
187
|
+
// its record carries no launch time. `run-ship-overlap.ts` orders two running ships by it.
|
|
188
|
+
function running_since(repository: string, number: string): number | undefined {
|
|
189
|
+
const record = read_record(repository, number)
|
|
190
|
+
|
|
191
|
+
return record !== undefined && result_of(record) === 'running' ? record.launched_at : undefined
|
|
192
|
+
}
|
|
193
|
+
|
|
186
194
|
function is_pending(record: ShipRecord | undefined): boolean {
|
|
187
195
|
return record !== undefined && record.pid === undefined
|
|
188
196
|
}
|
|
@@ -362,6 +370,7 @@ const run_ship_detach = {
|
|
|
362
370
|
last_launch,
|
|
363
371
|
log_path,
|
|
364
372
|
read_log,
|
|
373
|
+
running_since,
|
|
365
374
|
supervisor_argv,
|
|
366
375
|
}
|
|
367
376
|
|
|
@@ -104,13 +104,21 @@ function conflict_step(conflicts: ReadonlyArray<string>): string {
|
|
|
104
104
|
return `Resolve the conflict with origin/main in ${conflicts.join(', ')} (the merge is left in progress: remove the conflict markers in those files), then`
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// A prompt that already carries the failed output points at it; only one without has the
|
|
108
|
+
// session read the stopped report first.
|
|
109
|
+
function fix_step(issue: string, stage: Stage, is_excerpted: boolean): string {
|
|
110
|
+
if (is_excerpted) return `Fix the ${stage} failure shown in the output below`
|
|
111
|
+
|
|
112
|
+
return `Read the stopped report with \`${COMMAND.LOG} ${issue}\`, fix the ${stage} failure`
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
// Two lines' worth: read the stopped report, then the one command that resumes after the fix.
|
|
108
|
-
function next_step(issue: string, stage: Stage, resume: ShipResume): string {
|
|
116
|
+
function next_step(issue: string, stage: Stage, resume: ShipResume, is_excerpted = false): string {
|
|
109
117
|
const conflicts = resume.conflicts ?? []
|
|
110
118
|
|
|
111
119
|
if (conflicts.length > 0) return `${conflict_step(conflicts)} ${AFTER_FIX[stage](resume)}`
|
|
112
120
|
|
|
113
|
-
return
|
|
121
|
+
return `${fix_step(issue, stage, is_excerpted)}, then ${AFTER_FIX[stage](resume)}`
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
const run_ship_next = { COMMAND, next_step, quoted, resume_of }
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { changed_paths } from '#scripts/git/changed-paths'
|
|
2
|
+
import { git_diff_reads } from '#scripts/git/git-diff-reads'
|
|
3
|
+
import { git_spawn } from '#scripts/git/git-spawn'
|
|
4
|
+
import { session_cite } from '#scripts/issue/session-cite'
|
|
5
|
+
import { lane_handoff } from '#scripts/lane/lane-handoff'
|
|
6
|
+
import { lane_ledger, type OverlapRecord } from '#scripts/lane/lane-ledger'
|
|
7
|
+
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
8
|
+
import { poll } from '#scripts/lib/poll'
|
|
9
|
+
|
|
10
|
+
// The wait in front of `josh ship`'s sync (joshuafolkken/kit#3901). `backlog-overlap.ts` keeps lanes
|
|
11
|
+
// apart only on the paths their issue bodies declare, so two lanes that both edit an undeclared file
|
|
12
|
+
// were found out only when the later pull request turned `DIRTY` — after its gate and its CI wait,
|
|
13
|
+
// often as a `conflict` stop its child no longer had the context for.
|
|
14
|
+
//
|
|
15
|
+
// **The later ship waits for the earlier one rather than anything being pushed into a lane.** A lane
|
|
16
|
+
// whose actual change overlaps this one's and whose pull request is already open (its branch pushed,
|
|
17
|
+
// its detached ship still running) is waited on until that ship ends, merged or stopped; the sync that
|
|
18
|
+
// follows then merges a default branch that already holds it, so a conflict surfaces before this
|
|
19
|
+
// lane's gate rather than after its CI. Merging into the other lane's tree instead could not work: a
|
|
20
|
+
// lane mid-gate would have its record moved under it, and a lane still implementing holds the
|
|
21
|
+
// overlapping paths uncommitted, which `main:merge`'s guard refuses.
|
|
22
|
+
//
|
|
23
|
+
// **Only a ship launched before this one is waited on**, so two ships never wait on each other — a
|
|
24
|
+
// re-ship after a stop already has a pushed branch, so "pushed" alone could not order two re-ships in
|
|
25
|
+
// their sync. A ship run in an agent's own turn has no launch record and counts as the latest; equal
|
|
26
|
+
// launch times fall to the lower issue number. The wait is bounded — a ship that hangs never holds
|
|
27
|
+
// another past `MAX_WAIT_ATTEMPTS`, after which the sync goes ahead as it always did.
|
|
28
|
+
|
|
29
|
+
const WAIT_INTERVAL_MS = 30_000
|
|
30
|
+
const MAX_WAIT_ATTEMPTS = 60
|
|
31
|
+
// Unquoted like `read_changed_paths`, so a non-ASCII path compares equal on both sides.
|
|
32
|
+
const NAME_ONLY_ARGUMENTS = [
|
|
33
|
+
'-c',
|
|
34
|
+
'core.quotePath=false',
|
|
35
|
+
'diff',
|
|
36
|
+
'--name-only',
|
|
37
|
+
'--no-relative',
|
|
38
|
+
'--no-renames',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
interface OverlapOptions {
|
|
42
|
+
sleeper?: (duration_ms: number) => Promise<void>
|
|
43
|
+
ledger?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface Overlap {
|
|
47
|
+
lanes: ReadonlyArray<LaneInfo>
|
|
48
|
+
paths: ReadonlyArray<string>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function to_paths(raw: string): Array<string> {
|
|
52
|
+
return changed_paths.to_paths(raw)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function has_upstream(directory: string): Promise<boolean> {
|
|
56
|
+
try {
|
|
57
|
+
await git_spawn.read(['-C', directory, 'rev-parse', '--verify', '--quiet', '@{upstream}'])
|
|
58
|
+
|
|
59
|
+
return true
|
|
60
|
+
} catch {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A lane's change as its pushed branch carries it: everything its commits changed since it left the
|
|
66
|
+
// default branch. Read in its own tree, so the other lane's uncommitted edits — not yet on its pull
|
|
67
|
+
// request — are not counted.
|
|
68
|
+
async function lane_paths(directory: string): Promise<ReadonlyArray<string>> {
|
|
69
|
+
const base = await git_diff_reads.default_branch_reference()
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
return to_paths(
|
|
73
|
+
await git_spawn.read(['-C', directory, ...NAME_ONLY_ARGUMENTS, `${base}...HEAD`, '--']),
|
|
74
|
+
)
|
|
75
|
+
} catch {
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface Launch {
|
|
81
|
+
issue: string
|
|
82
|
+
since: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function own_launch(issue: string): Launch {
|
|
86
|
+
const since = lane_handoff.ship_running_since(issue, process.cwd())
|
|
87
|
+
|
|
88
|
+
return { issue, since: since ?? Infinity }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function is_launched_before(lane: LaneInfo, own: Launch): boolean {
|
|
92
|
+
const since = lane_handoff.ship_running_since(lane.issue, lane.directory)
|
|
93
|
+
|
|
94
|
+
if (since === undefined) return false
|
|
95
|
+
|
|
96
|
+
return since < own.since || (since === own.since && Number(lane.issue) < Number(own.issue))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function is_open_ship(lane: LaneInfo, own: Launch): Promise<boolean> {
|
|
100
|
+
if (!is_launched_before(lane, own)) return false
|
|
101
|
+
|
|
102
|
+
return await has_upstream(lane.directory)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function shared_paths(
|
|
106
|
+
lane: LaneInfo,
|
|
107
|
+
launch: Launch,
|
|
108
|
+
own: ReadonlySet<string>,
|
|
109
|
+
): Promise<Array<string>> {
|
|
110
|
+
if (!(await is_open_ship(lane, launch))) return []
|
|
111
|
+
|
|
112
|
+
const paths = await lane_paths(lane.directory)
|
|
113
|
+
|
|
114
|
+
return paths.filter((file_path) => own.has(file_path))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function other_lanes(issue: string): Promise<ReadonlyArray<LaneInfo>> {
|
|
118
|
+
const lanes = await lane_registry.list_lanes()
|
|
119
|
+
|
|
120
|
+
return lanes.filter((lane) => lane.issue !== issue && !lane.is_stranded)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Every other lane whose open pull request, shipped before this one, touches a path this lane changed,
|
|
124
|
+
// with the shared paths.
|
|
125
|
+
async function find_overlap(issue: string): Promise<Overlap> {
|
|
126
|
+
const own = new Set(await changed_paths.read_changed_paths(false))
|
|
127
|
+
const launch = own_launch(issue)
|
|
128
|
+
const others = await other_lanes(issue)
|
|
129
|
+
const shared = await Promise.all(
|
|
130
|
+
others.map(async (lane) => await shared_paths(lane, launch, own)),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
lanes: others.filter((_lane, index) => (shared[index] ?? []).length > 0),
|
|
135
|
+
paths: [...new Set(shared.flat())].toSorted((left, right) => left.localeCompare(right)),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function read_overlap(issue: string): Promise<Overlap> {
|
|
140
|
+
try {
|
|
141
|
+
return await find_overlap(issue)
|
|
142
|
+
} catch {
|
|
143
|
+
return { lanes: [], paths: [] }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function is_settled(lanes: ReadonlyArray<LaneInfo>): boolean {
|
|
148
|
+
return lanes.every((lane) => !lane_handoff.is_ship_running(lane.issue, lane.directory))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function wait_for(lanes: ReadonlyArray<LaneInfo>, options: OverlapOptions): Promise<boolean> {
|
|
152
|
+
return await poll.poll_until(async () => is_settled(lanes), {
|
|
153
|
+
attempts: MAX_WAIT_ATTEMPTS,
|
|
154
|
+
interval_ms: WAIT_INTERVAL_MS,
|
|
155
|
+
sleeper: options.sleeper ?? poll.sleep,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Waits out every overlapping open ship and records the decision, returning what was recorded; a lane
|
|
160
|
+
// that overlaps nothing returns `undefined` at once and writes nothing. Best-effort: an overlap that
|
|
161
|
+
// could not be read is no overlap, so the sync is never blocked by the check that only exists to make
|
|
162
|
+
// it land cleaner.
|
|
163
|
+
async function await_overlap(
|
|
164
|
+
issue: string,
|
|
165
|
+
options: OverlapOptions = {},
|
|
166
|
+
): Promise<OverlapRecord | undefined> {
|
|
167
|
+
const overlap = await read_overlap(issue)
|
|
168
|
+
|
|
169
|
+
if (overlap.lanes.length === 0) return undefined
|
|
170
|
+
|
|
171
|
+
const started = Date.now()
|
|
172
|
+
const is_cleared = await wait_for(overlap.lanes, options)
|
|
173
|
+
const lanes = overlap.lanes.map((lane) => Number(lane.issue))
|
|
174
|
+
const waited_ms = Date.now() - started
|
|
175
|
+
const record = { issue: Number(issue), lanes, paths: [...overlap.paths], waited_ms, is_cleared }
|
|
176
|
+
|
|
177
|
+
await lane_ledger.record_overlap(record, options.ledger)
|
|
178
|
+
|
|
179
|
+
return record
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// The line the sync stage prints for a recorded wait, or `''` where nothing overlapped.
|
|
183
|
+
function note(record: OverlapRecord | undefined): string {
|
|
184
|
+
if (record === undefined) return ''
|
|
185
|
+
|
|
186
|
+
const lanes = record.lanes.map((lane) => session_cite.issue(lane)).join(', ')
|
|
187
|
+
const outcome = record.is_cleared ? 'waited for' : 'gave up waiting for'
|
|
188
|
+
|
|
189
|
+
return `${outcome} ${lanes}, whose open pull requests also change: ${record.paths.join(', ')}`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const run_ship_overlap = { MAX_WAIT_ATTEMPTS, await_overlap, note }
|
|
193
|
+
|
|
194
|
+
export type { OverlapOptions }
|
|
195
|
+
export { run_ship_overlap }
|
|
@@ -6,14 +6,19 @@ import type { JoshResult } from '#scripts/josh/josh-run'
|
|
|
6
6
|
import { buffered_process, type BufferedProcessResult } from '#scripts/lib/buffered-process'
|
|
7
7
|
|
|
8
8
|
// The fast checks a detached `josh ship` runs in the agent's own turn, after the preflight and before
|
|
9
|
-
// the hand-off: a type error in a file the related-only checks never touched,
|
|
10
|
-
// SKILL.md pointer, a byte budget, the command list
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// the hand-off: a type error in a file the related-only checks never touched, an export nothing reads,
|
|
10
|
+
// an unknown word, or a guard test (a SKILL.md pointer, a byte budget, the command list, a source with
|
|
11
|
+
// no colocated test, a bare issue citation) that only the full suite runs, would otherwise first
|
|
12
|
+
// surface after the detach and cost a relaunched session. They answer in seconds, so the same session
|
|
13
|
+
// fixes what they find and ships again. **Nothing moves out of the gate**: the supervised gate stage
|
|
14
|
+
// still runs every check, these included.
|
|
14
15
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
16
|
+
// joshuafolkken/kit#3924: nine of ten ship gate stops were `exports:unused`, `cspell:dot` or a
|
|
17
|
+
// repository-wide guard test failing on the child's own code — none a check `test:related` selects,
|
|
18
|
+
// since a guard test imports no changed file.
|
|
19
|
+
//
|
|
20
|
+
// The test targets are where such failures land. A consumer has none of them, so only the ones
|
|
21
|
+
// present run, and a consumer meets the static checks alone.
|
|
17
22
|
//
|
|
18
23
|
// In kit the metrics ratchet's totals join them: a feature grows a total almost every time, and here
|
|
19
24
|
// the session that grew the total writes the reason itself rather than a relaunched one. The
|
|
@@ -25,24 +30,31 @@ const FAILURE_EXIT_CODE = 1
|
|
|
25
30
|
const SECTION_SEPARATOR = '\n\n'
|
|
26
31
|
const PASSED = 'pre-detach checks passed'
|
|
27
32
|
const METRICS_TOTALS_COMMAND: ReadonlyArray<string> = ['josh', 'metrics', '--totals-only']
|
|
28
|
-
const
|
|
33
|
+
const STATIC_CHECKS: ReadonlyArray<ReadonlyArray<string>> = [
|
|
34
|
+
['josh', 'exports:unused'],
|
|
35
|
+
['josh', 'cspell:dot'],
|
|
36
|
+
]
|
|
37
|
+
const GUARD_TEST_TARGETS: ReadonlyArray<string> = [
|
|
29
38
|
'scripts/document',
|
|
30
39
|
'scripts/claude',
|
|
31
40
|
'scripts/rules',
|
|
32
41
|
'scripts/josh/josh-command-map.test.ts',
|
|
42
|
+
'scripts/issue/session-cite-scan.test.ts',
|
|
43
|
+
'scripts/test/untested-sources.test.ts',
|
|
44
|
+
'scripts/test/pure-files.test.ts',
|
|
33
45
|
]
|
|
34
46
|
// A present directory need not hold a test the project's Vitest includes — a consumer's own
|
|
35
47
|
// `scripts/claude` of shell helpers, say — and a filter that matches nothing would stop every ship.
|
|
36
|
-
const
|
|
48
|
+
const GUARD_TEST_COMMAND: ReadonlyArray<string> = ['josh', 'test:unit', '--passWithNoTests']
|
|
37
49
|
|
|
38
50
|
function present_targets(directory: string): ReadonlyArray<string> {
|
|
39
|
-
return
|
|
51
|
+
return GUARD_TEST_TARGETS.filter((target) => existsSync(path.join(directory, target)))
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
function
|
|
54
|
+
function guard_tests(directory: string): Array<ReadonlyArray<string>> {
|
|
43
55
|
const targets = present_targets(directory)
|
|
44
56
|
|
|
45
|
-
return targets.length === 0 ? [] : [[...
|
|
57
|
+
return targets.length === 0 ? [] : [[...GUARD_TEST_COMMAND, ...targets]]
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
// The same kit-only rule as the gate's `is_kit_only` step: a consumer's kit has no `josh metrics`.
|
|
@@ -53,7 +65,7 @@ function kit_only(directory: string): Array<ReadonlyArray<string>> {
|
|
|
53
65
|
async function command_lists(directory: string): Promise<Array<ReadonlyArray<string>>> {
|
|
54
66
|
const type_check = await type_check_step.resolve_type_check_args(directory)
|
|
55
67
|
|
|
56
|
-
return [type_check, ...
|
|
68
|
+
return [type_check, ...STATIC_CHECKS, ...guard_tests(directory), ...kit_only(directory)]
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
async function run_in(
|
|
@@ -63,7 +75,7 @@ async function run_in(
|
|
|
63
75
|
return await buffered_process.run_buffered_process(list, { cwd: directory })
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
//
|
|
78
|
+
// All run at once, buffered, and every failure is reported, so one ship shows all there is to fix.
|
|
67
79
|
async function checks(directory: string = process.cwd()): Promise<JoshResult> {
|
|
68
80
|
const lists = await command_lists(directory)
|
|
69
81
|
const results = await Promise.all(lists.map(async (list) => await run_in(directory, list)))
|
|
@@ -77,6 +89,12 @@ async function checks(directory: string = process.cwd()): Promise<JoshResult> {
|
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
const run_ship_pre_detach = {
|
|
92
|
+
const run_ship_pre_detach = {
|
|
93
|
+
GUARD_TEST_TARGETS,
|
|
94
|
+
METRICS_TOTALS_COMMAND,
|
|
95
|
+
PASSED,
|
|
96
|
+
STATIC_CHECKS,
|
|
97
|
+
checks,
|
|
98
|
+
}
|
|
81
99
|
|
|
82
100
|
export { run_ship_pre_detach }
|
|
@@ -3,9 +3,11 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
|
3
3
|
import { git_pr } from '#scripts/gh/git-pr'
|
|
4
4
|
import { changed_paths } from '#scripts/git/changed-paths'
|
|
5
5
|
import { git_branch } from '#scripts/git/git-branch'
|
|
6
|
+
import { git_issue } from '#scripts/git/git-issue'
|
|
6
7
|
import { git_preflight } from '#scripts/git/git-preflight'
|
|
7
8
|
import type { JoshResult } from '#scripts/josh/josh-run'
|
|
8
9
|
import { error_text } from '#scripts/lib/error-message'
|
|
10
|
+
import { acceptance_map_check } from '#scripts/review/acceptance-map-check'
|
|
9
11
|
import { live_evidence } from '#scripts/review/live-evidence'
|
|
10
12
|
import { run_ship_lock } from './run-ship-lock'
|
|
11
13
|
import { run_ship_scoped } from './run-ship-scoped'
|
|
@@ -22,6 +24,7 @@ import { run_ship_scoped } from './run-ship-scoped'
|
|
|
22
24
|
|
|
23
25
|
const SUCCESS_EXIT_CODE = 0
|
|
24
26
|
const FAILURE_EXIT_CODE = 1
|
|
27
|
+
const UNREADABLE = Symbol('unreadable body')
|
|
25
28
|
const EVIDENCE_PROBLEM = `This change touches runtime code, but the PR body has no ${live_evidence.EVIDENCE_HEADING} section: run the acceptance criteria for real and pass the body with --body-file <path>. Write it ${live_evidence.EVIDENCE_FORMAT}`
|
|
26
29
|
const EVIDENCE_PENDING = `This change touches runtime code and no PR is open yet: write the ${live_evidence.EVIDENCE_HEADING} section to a file and hand it to josh ship with --body-file <path>, or the ship refuses. Write it ${live_evidence.EVIDENCE_FORMAT}`
|
|
27
30
|
|
|
@@ -71,16 +74,39 @@ async function caught(problems: Promise<Array<string>>): Promise<Array<string>>
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
async function readable_body(
|
|
78
|
+
body_path: string | undefined,
|
|
79
|
+
): Promise<string | undefined | typeof UNREADABLE> {
|
|
80
|
+
try {
|
|
81
|
+
return await body_text(body_path)
|
|
82
|
+
} catch {
|
|
83
|
+
return UNREADABLE
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// `followup`'s acceptance-map refusal, asked of the body this ship will deliver. A branch with no issue
|
|
88
|
+
// number is `git_preflight`'s problem to name, and an unreadable body the evidence side's.
|
|
89
|
+
async function acceptance_problems(body_path: string | undefined): Promise<Array<string>> {
|
|
90
|
+
const issue = git_issue.branch_number(await git_branch.current())
|
|
91
|
+
const body = await readable_body(body_path)
|
|
92
|
+
|
|
93
|
+
if (issue === undefined || body === UNREADABLE) return []
|
|
94
|
+
const problem = await acceptance_map_check.body_refusal(issue, body)
|
|
95
|
+
|
|
96
|
+
return problem === undefined ? [] : [problem]
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
// The lock file is asked here too, so a drift stops the ship before the gate
|
|
75
100
|
// rather than being rewritten by the pre-push hook's install under the push.
|
|
76
101
|
async function asked_problems(request: PreflightRequest): Promise<Array<string>> {
|
|
77
|
-
const [preflight, evidence, lock] = await Promise.all([
|
|
102
|
+
const [preflight, evidence, acceptance, lock] = await Promise.all([
|
|
78
103
|
caught(git_preflight.problems_of({ cli_input: request.title, will_open_pr: true })),
|
|
79
104
|
caught(evidence_problems(request.body_path)),
|
|
105
|
+
caught(acceptance_problems(request.body_path)),
|
|
80
106
|
caught(run_ship_lock.lock_problems()),
|
|
81
107
|
])
|
|
82
108
|
|
|
83
|
-
return [...preflight, ...evidence, ...lock]
|
|
109
|
+
return [...preflight, ...evidence, ...acceptance, ...lock]
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
async function stage(request: PreflightRequest): Promise<JoshResult> {
|
|
@@ -6,8 +6,11 @@ import { lane_relaunch } from '#scripts/lane/lane-relaunch'
|
|
|
6
6
|
import { lane_session_broker } from '#scripts/lane/lane-session-broker'
|
|
7
7
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
8
8
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
9
|
-
import type {
|
|
9
|
+
import type { ShipSection } from './run-ship'
|
|
10
|
+
import { run_ship_next, type ShipResume } from './run-ship-next'
|
|
10
11
|
import { run_ship_stage, type Stage } from './run-ship-stage'
|
|
12
|
+
import type { ShipArguments } from './run-ship-steps'
|
|
13
|
+
import { run_ship_stop_context } from './run-ship-stop-context'
|
|
11
14
|
import { run_ship_stop_text } from './run-ship-stop-text'
|
|
12
15
|
|
|
13
16
|
// How a detached `josh ship` supervisor hands a stopped stage back. The agent
|
|
@@ -66,19 +69,26 @@ function relaunched(lane: LaneInfo, stop: ShipStop): ReturnOutcome {
|
|
|
66
69
|
// child's Bash tool, which Claude Code starts without the token, so a child started here could not sign
|
|
67
70
|
// in where the token is the only credential (joshuafolkken/kit#3817).
|
|
68
71
|
async function brokered(lane: LaneInfo, stop: ShipStop): Promise<ReturnOutcome> {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
const answer = await lane_session_broker.request(lane.directory, lane.issue, {
|
|
73
|
+
kind: 'relaunch',
|
|
74
|
+
stop,
|
|
75
|
+
})
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
if (answer.is_success) return 'relaunched'
|
|
78
|
+
|
|
79
|
+
note(
|
|
80
|
+
`the lane child could not be relaunched: the lane broker did not start it — ${answer.reason}`,
|
|
81
|
+
)
|
|
74
82
|
|
|
75
83
|
return 'recorded'
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
// `output` is the failed stage's own output: the relaunched child's prompt carries its tail.
|
|
78
87
|
async function return_control(
|
|
79
88
|
issue: string,
|
|
80
89
|
stage: Stage,
|
|
81
90
|
resume: ShipResume,
|
|
91
|
+
output: string,
|
|
82
92
|
): Promise<ReturnOutcome> {
|
|
83
93
|
await run_event_stream_emit.emit(
|
|
84
94
|
run_event_stream.EVENT_KIND.SHIP_STOP,
|
|
@@ -89,14 +99,28 @@ async function return_control(
|
|
|
89
99
|
|
|
90
100
|
if (lane === undefined) return 'recorded'
|
|
91
101
|
|
|
92
|
-
const stop = { stage, ...resume }
|
|
102
|
+
const stop = { stage, ...resume, context: await run_ship_stop_context.build(issue, output) }
|
|
93
103
|
|
|
94
104
|
return lane_session_broker.is_serving(lane.directory, issue)
|
|
95
105
|
? await brokered(lane, stop)
|
|
96
106
|
: relaunched(lane, stop)
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
// The stopped ship's arguments and its failed section, as `return_control` takes them: the section's
|
|
110
|
+
// conflicts ride on the resume, its body is the output the prompt carries.
|
|
111
|
+
async function hand_back(
|
|
112
|
+
args: ShipArguments,
|
|
113
|
+
stage: Stage,
|
|
114
|
+
failed: Pick<ShipSection, 'body' | 'conflicts'> | undefined,
|
|
115
|
+
): Promise<ReturnOutcome> {
|
|
116
|
+
const resume = run_ship_next.resume_of(args)
|
|
117
|
+
const conflicts = failed?.conflicts
|
|
118
|
+
const stopped = conflicts === undefined ? resume : { ...resume, conflicts }
|
|
119
|
+
|
|
120
|
+
return await return_control(args.number, stage, stopped, failed?.body ?? '')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const run_ship_return = { hand_back, return_control }
|
|
100
124
|
|
|
101
125
|
export type { ReturnOutcome }
|
|
102
126
|
export { run_ship_return }
|
|
@@ -77,15 +77,18 @@ function note(text: string): void {
|
|
|
77
77
|
process.stderr.write(`${text}\n`)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// Under the reviewer profile — the same model and effort (`JOSH_REVIEWER_*` included) the
|
|
81
|
-
// subagent is given — and waited on, so the join below runs only once the review has finished.
|
|
82
|
-
// call is its own process, so round 2 is a fresh session by construction.
|
|
80
|
+
// Under the round's reviewer profile — the same model and effort (`JOSH_REVIEWER_*` included) the
|
|
81
|
+
// chain's subagent is given — and waited on, so the join below runs only once the review has finished.
|
|
82
|
+
// Each call is its own process, so round 2 is a fresh session by construction.
|
|
83
83
|
async function run_direct_session(
|
|
84
84
|
argv: AgentArgv,
|
|
85
85
|
profile: AgentProfile,
|
|
86
86
|
log_path: string,
|
|
87
|
+
round: Round,
|
|
87
88
|
): Promise<JoshResult> {
|
|
88
89
|
const request = { argv, cwd: PROJECT_ROOT, log_path, profile }
|
|
90
|
+
|
|
91
|
+
note(agent_role_profile.describe_review(Number(round), profile))
|
|
89
92
|
const result = await detached_launch.launch_attached(request, note)
|
|
90
93
|
|
|
91
94
|
if (result.kind === 'failed') return failure(`reviewer could not start: ${result.note}`)
|
|
@@ -110,19 +113,18 @@ async function run_session(
|
|
|
110
113
|
issue: string,
|
|
111
114
|
round: Round,
|
|
112
115
|
): Promise<JoshResult> {
|
|
113
|
-
const built = agent_argv.
|
|
116
|
+
const built = agent_argv.review_in(prompt, Number(round), PROJECT_ROOT)
|
|
114
117
|
if (built.kind === 'rejected') return failure(`reviewer profile rejected: ${built.note}`)
|
|
115
118
|
|
|
116
119
|
if (!is_brokered(built.profile, issue)) {
|
|
117
|
-
return await run_direct_session(built.argv, built.profile, log_path)
|
|
120
|
+
return await run_direct_session(built.argv, built.profile, log_path, round)
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
const
|
|
121
|
-
kind: 'review',
|
|
122
|
-
round,
|
|
123
|
-
})
|
|
123
|
+
const answer = await lane_session_broker.request(PROJECT_ROOT, issue, { kind: 'review', round })
|
|
124
124
|
|
|
125
|
-
return is_success
|
|
125
|
+
return answer.is_success
|
|
126
|
+
? { code: SUCCESS_EXIT_CODE, out: '' }
|
|
127
|
+
: failure(`isolated reviewer failed: ${answer.reason}`)
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
// The brief goes to a file and the previous round's findings are removed, so a reviewer that writes
|