@joshuafolkken/kit 1.953.0 → 1.972.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/workflow-commands/background-commands.md +3 -0
- package/.claude/skills/workflow-commands/backlogrun-child.md +75 -120
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +78 -115
- package/.claude/skills/workflow-commands/backlogrun-progress.md +82 -146
- package/.claude/skills/workflow-commands/backlogrun-steps.md +173 -229
- package/.claude/skills/workflow-commands/backlogrun.md +3 -1
- package/.claude/skills/workflow-commands/followup.md +3 -0
- package/.claude/skills/workflow-commands/fullrun.md +3 -4
- package/.claude/skills/workflow-commands/observation-filing.md +31 -25
- package/.claude/skills/workflow-commands/pre-gate-cut.md +96 -161
- package/.github/workflows/pr-classification.yml +12 -1
- package/CLAUDE.md +3 -3
- package/README.md +5 -3
- package/dist/CLAUDE.md +3 -3
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-OM5VDM6F.js → chunk-2K52BRWV.js} +2 -2
- package/dist/hooks/{chunk-Z5TVZ4AP.js → chunk-BNWIOJFU.js} +29 -16
- package/dist/hooks/{chunk-NKVLBB65.js → chunk-BVMSPG3M.js} +3 -3
- package/dist/hooks/{chunk-IA7WC4NS.js → chunk-CRGJTD6E.js} +1 -1
- package/dist/hooks/{chunk-M4U4YNRE.js → chunk-F6OM6SIG.js} +3 -3
- package/dist/hooks/{chunk-O3PEDF3M.js → chunk-KQG4776P.js} +1 -1
- package/dist/hooks/{chunk-V7RH56HU.js → chunk-NXL3SBXY.js} +2 -2
- package/dist/hooks/{chunk-452OKRQX.js → chunk-WWTMHHZU.js} +120 -18
- package/dist/hooks/{chunk-EYFJ74MJ.js → chunk-ZPLXLJ7O.js} +21 -7
- package/dist/hooks/{chunk-IBFC25IX.js → chunk-ZYYSBNR6.js} +4 -4
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +206 -176
- package/dist/josh.js +14 -6
- package/package.json +3 -3
- package/prompts/collaboration-workflow/wip-cap.md +1 -1
- package/scripts/backlog/backlog-arrival.ts +11 -1
- package/scripts/backlog/backlog-budget.ts +25 -8
- package/scripts/backlog/backlog-drive-cli.ts +5 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +20 -0
- package/scripts/backlog/backlog-drive.ts +7 -3
- package/scripts/backlog/backlog-next.ts +45 -2
- package/scripts/backlog/backlog-offer.ts +1 -0
- package/scripts/backlog/backlog-plan-cli.ts +66 -27
- package/scripts/backlog/backlog-plan.ts +30 -2
- package/scripts/backlog/backlog-stalled.ts +9 -0
- package/scripts/backlog/backlog-waves.ts +169 -0
- package/scripts/document/document-byte-budget.ts +1 -1
- package/scripts/document/entry-read-budget.ts +7 -5
- package/scripts/epic/epic-lane-offer.ts +69 -4
- package/scripts/epic/epic-next.ts +2 -2
- package/scripts/epic/epic-solo.ts +147 -0
- package/scripts/epic/epic-triage.ts +47 -0
- package/scripts/gate/hook-gate-reuse.ts +7 -8
- package/scripts/git/git-closes-keyword.ts +15 -0
- package/scripts/git/git-followup-flush.ts +5 -6
- package/scripts/git/git-followup-issue-close.ts +75 -0
- package/scripts/git/git-pr-followup-wrapup.ts +36 -6
- package/scripts/git/git-pr-followup.ts +9 -16
- package/scripts/git/git-staging.ts +33 -20
- package/scripts/git/issue-labels.ts +42 -0
- package/scripts/hooks/stop-guard.ts +1 -0
- package/scripts/init/init-install.ts +64 -0
- package/scripts/init/init.ts +38 -11
- package/scripts/init/project-profile.ts +9 -5
- package/scripts/init/start-plan.ts +145 -0
- package/scripts/init/start-prompt.ts +55 -0
- package/scripts/init/start-steps.ts +167 -0
- package/scripts/init/start.ts +92 -16
- package/scripts/issue/issue-closing-pr.ts +93 -0
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +4 -2
- package/scripts/josh/josh-commands-project.ts +11 -3
- package/scripts/josh/josh-commands-workflow.ts +3 -3
- package/scripts/observations/observation-ledger-prepare.ts +31 -0
- package/scripts/observations/observation-ledger.ts +5 -5
- package/scripts/observations/observations-flush.ts +11 -30
- package/scripts/rules/implementation-cut.ts +49 -16
- package/scripts/rules/lane-background.ts +25 -5
- package/scripts/rules/stop-rules.ts +16 -4
- package/scripts/run/run-carry-cli.ts +21 -0
- package/scripts/run/run-cut-cli.ts +92 -110
- package/scripts/run/run-cut-report.ts +167 -0
- package/scripts/run/run-cut.ts +23 -7
- package/scripts/run/run-entry-cli.ts +36 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-hold-cli.ts +36 -15
- package/scripts/run/run-hold.ts +15 -4
- package/scripts/run/run-merge-cli.ts +28 -14
- package/scripts/run/run-merge-steps.ts +38 -2
- package/scripts/run/run-merge.ts +41 -3
- package/scripts/run/run-prep-cli.ts +8 -2
- package/scripts/run/run-prep-locate.ts +165 -0
- package/scripts/run/run-prep.ts +12 -1
- package/scripts/run/run-retrospective.ts +34 -0
- package/scripts/run/run-step-cli.ts +19 -5
- package/scripts/run/run-step.ts +31 -19
- package/scripts/run/run-tail-cli.ts +4 -2
- package/scripts/run/run-wake-work.ts +4 -0
- package/scripts/safe-chain/ci-installer-pin.ts +8 -4
- package/scripts/time-runtime/time-background.ts +11 -0
- package/scripts/time-runtime/time-transcript-line.ts +4 -0
- package/templates/pnpm-workspace.static.yaml +2 -0
|
@@ -63,6 +63,9 @@ interface LaneCostState {
|
|
|
63
63
|
directory: string
|
|
64
64
|
source: MarkerSource
|
|
65
65
|
carried: (now?: Date) => RunCut | undefined
|
|
66
|
+
// The issue whose run hold stands over this checkout (joshuafolkken/kit#2760) — how a `fullrun`
|
|
67
|
+
// held outside a lane is told apart from a person's tree.
|
|
68
|
+
held_issue: () => string | undefined
|
|
66
69
|
verdict: () => CostVerdict
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -71,6 +74,7 @@ function current_state(): LaneCostState {
|
|
|
71
74
|
directory: process.cwd(),
|
|
72
75
|
source: process.env,
|
|
73
76
|
carried: run_cut.carried_cut_sync,
|
|
77
|
+
held_issue: run_cut.held_issue_sync,
|
|
74
78
|
// **Read through the short reuse window** (joshuafolkken/kit#2385). The row now fires per
|
|
75
79
|
// threshold crossing rather than once per run, so this predicate is a candidate on every edit;
|
|
76
80
|
// the window collapses one turn's burst of edits to a single whole-transcript price.
|
|
@@ -79,22 +83,49 @@ function current_state(): LaneCostState {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
// A dispatched lane child for this checkout
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
// this issue means a cut is already in flight, and `begin_cut`'s exclusive create would refuse a second
|
|
87
|
-
// one anyway.
|
|
88
|
-
function uncut_lane_child_issue(state: LaneCostState): string | undefined {
|
|
86
|
+
// A dispatched lane child's issue for this checkout. The dispatch mark is what tells a child apart from
|
|
87
|
+
// a person (joshuafolkken/kit#1904), read from the environment against this lane's own issue so a
|
|
88
|
+
// leaked mark for another issue reads as a person.
|
|
89
|
+
function lane_child_issue(state: LaneCostState): string | undefined {
|
|
89
90
|
if (!lane_child_marker.is_child_of(state.directory, state.source)) return undefined
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
return lane_paths.lane_issue_of(state.directory, { ...state.source })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// The issue a run holds this checkout for, read only outside a lane: a lane's hold is its dispatched
|
|
96
|
+
// child's, so a person finishing that lane's work carries no mark and still sees no refusal.
|
|
97
|
+
function held_run_issue(state: LaneCostState): string | undefined {
|
|
98
|
+
if (lane_paths.lane_issue_of(state.directory, { ...state.source }) !== undefined) return undefined
|
|
99
|
+
|
|
100
|
+
return state.held_issue()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The run this checkout's edits belong to, with no cut already carried — a dispatched lane child, or
|
|
104
|
+
// **a `fullrun` held in its own checkout** (joshuafolkken/kit#2760): the cut was lane-only, so a run a
|
|
105
|
+
// person started grew without a bound mid-implementation. The run hold naming an issue is what marks
|
|
106
|
+
// such a run, and a person's tree holds nothing, so it still sees no refusal. **The carried-cut half
|
|
107
|
+
// keeps the guard silent between a cut and its resume**, exactly as `pre-gate-cut.ts`'s does: a record
|
|
108
|
+
// naming this issue means a cut is already in flight, and `begin_cut`'s exclusive create would refuse
|
|
109
|
+
// a second one anyway.
|
|
110
|
+
function uncut_run_issue(state: LaneCostState): string | undefined {
|
|
111
|
+
const issue = lane_child_issue(state) ?? held_run_issue(state)
|
|
92
112
|
|
|
93
113
|
if (issue === undefined) return undefined
|
|
94
114
|
|
|
95
115
|
return state.carried()?.issue === issue ? undefined : issue
|
|
96
116
|
}
|
|
97
117
|
|
|
118
|
+
// **An unmeasurable session is cut in a lane and left alone outside one.** A lane child's safety net is
|
|
119
|
+
// the pre-gate direction below; a held run outside a lane is a person's own session, so only a measured
|
|
120
|
+
// `over` interrupts it — a provider with no transcript would otherwise refuse every edit it makes.
|
|
121
|
+
function warrants_the_cut(state: LaneCostState): boolean {
|
|
122
|
+
const verdict = state.verdict()
|
|
123
|
+
|
|
124
|
+
if (lane_child_issue(state) === undefined) return verdict === cost_verdict.OVER_VERDICT
|
|
125
|
+
|
|
126
|
+
return verdict !== cost_verdict.UNDER_VERDICT
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
// **The tool-name test comes first and the world is consulted second, the cost measurement last.** This
|
|
99
130
|
// predicate is asked of every call in the run, so the `Edit` / `Write` string match keeps the lane read
|
|
100
131
|
// off all but the edits, and the lane read keeps the transcript-priced verdict off every edit but those
|
|
@@ -109,9 +140,9 @@ function is_over_threshold_edit(
|
|
|
109
140
|
call: GuardedCall,
|
|
110
141
|
state: LaneCostState = current_state(),
|
|
111
142
|
): boolean {
|
|
112
|
-
if (!is_edit_tool(call.name) ||
|
|
143
|
+
if (!is_edit_tool(call.name) || uncut_run_issue(state) === undefined) return false
|
|
113
144
|
|
|
114
|
-
return state
|
|
145
|
+
return warrants_the_cut(state)
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
// **The threshold in the refusal text is assembled from the constant, never spelled** (joshuafolkken/kit#2385).
|
|
@@ -125,7 +156,8 @@ const THRESHOLD_TEXT = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-toke
|
|
|
125
156
|
// because `cut` is the only one that ends the turn and a run told merely to "take the cut" would have to
|
|
126
157
|
// read which of the others leave it implementing — the same reason `pre-gate-cut.ts` spells them out.
|
|
127
158
|
const IMPLEMENTATION_CUT_REASON =
|
|
128
|
-
'⛔ implementation-phase cut: this checkout is a lane dispatched for this issue
|
|
159
|
+
'⛔ implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for ' +
|
|
160
|
+
'this issue (joshuafolkken/kit#2760), and its recent-context ' +
|
|
129
161
|
`cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated ` +
|
|
130
162
|
'so far is now re-read on every later request. Take the cut before this edit. ' +
|
|
131
163
|
'First write a handoff file with the Write tool — the user’s instruction verbatim, what you have ' +
|
|
@@ -139,11 +171,12 @@ const IMPLEMENTATION_CUT_REASON =
|
|
|
139
171
|
'because the verdict was only ever read at session entry, where the context has not yet grown. The ' +
|
|
140
172
|
"verdict is `pnpm josh cost --cut`'s exactly — the same per-request billed-input statistic against the " +
|
|
141
173
|
'same threshold, never a second measurement. Issue `pnpm josh run:cut --impl <N> --handoff <path>` now and read the ' +
|
|
142
|
-
'verdict: on `cut`, **end the turn immediately** — the fresh process owns the run and
|
|
143
|
-
'implementing, so it must not be waited for;
|
|
174
|
+
'verdict: on `cut`, **end the turn immediately** — in a lane the fresh process owns the run and ' +
|
|
175
|
+
'continues implementing, so it must not be waited for; outside a lane nothing is relaunched, so first ' +
|
|
176
|
+
'send the `confirmation` Telegram naming the resume command `fullrun #<N>` and keep the hold; on `not-a-lane`, `unready`, `busy`, `failed` or ' +
|
|
144
177
|
'`unknown`, this process carries the run on and the edit is simply the next call. Never relaunch a ' +
|
|
145
|
-
'second process after `busy`. This fired because the dispatch mark names this lane
|
|
146
|
-
'here carries no mark and sees no refusal, so there is no human-or-child judgement left to make. The ' +
|
|
178
|
+
'second process after `busy`. This fired because the dispatch mark names this lane or the run hold ' +
|
|
179
|
+
'names this issue; a person working here carries no mark and holds no run, so sees no refusal, so there is no human-or-child judgement left to make. The ' +
|
|
147
180
|
'procedure is `.claude/skills/workflow-commands/pre-gate-cut.md`. Reissue this edit once the cut has ' +
|
|
148
181
|
'answered — an edit reissued right after this refusal passes, so `busy` / `failed` cannot wedge the ' +
|
|
149
182
|
'run edit after edit; this fires again on the next threshold crossing rather than once per run.'
|
|
@@ -195,7 +228,7 @@ const implementation_cut = {
|
|
|
195
228
|
is_over_threshold_edit,
|
|
196
229
|
is_reissued_refusal,
|
|
197
230
|
takes_the_impl_cut,
|
|
198
|
-
|
|
231
|
+
uncut_run_issue,
|
|
199
232
|
}
|
|
200
233
|
|
|
201
234
|
export type { LaneCostState }
|
|
@@ -3,7 +3,10 @@ import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
|
3
3
|
import { json_value } from '#scripts/lib/json-value'
|
|
4
4
|
import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
|
|
5
5
|
import { time_shell } from '#scripts/time-runtime/time-shell'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
time_transcript_line,
|
|
8
|
+
type Block as TranscriptBlock,
|
|
9
|
+
} from '#scripts/time-runtime/time-transcript-line'
|
|
7
10
|
import { run_tail } from './run-tail'
|
|
8
11
|
|
|
9
12
|
// A dispatched lane child is stopped from backgrounding a long-running josh command (joshuafolkken/kit#2704).
|
|
@@ -60,16 +63,32 @@ function is_background_long_run(
|
|
|
60
63
|
return is_candidate(call) && is_lane_child()
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
// The
|
|
64
|
-
// tool result and a finish off the harness notice, both by `time_transcript_line`.
|
|
65
|
-
function
|
|
66
|
+
// The launches `launched_ids` reads off the transcript tail that never saw their finish. A launch is
|
|
67
|
+
// read off its own tool result and a finish off the harness notice, both by `time_transcript_line`.
|
|
68
|
+
function unfinished_ids(
|
|
69
|
+
tail: string,
|
|
70
|
+
launched_ids: (block: TranscriptBlock) => ReadonlyArray<string>,
|
|
71
|
+
): ReadonlyArray<string> {
|
|
66
72
|
const lines = tail.split('\n').map((line) => time_transcript_line.parse_line(line))
|
|
67
73
|
const finished = new Set(lines.map((line) => line?.finished_background ?? ''))
|
|
68
|
-
const
|
|
74
|
+
const blocks = lines.flatMap((line) => line?.blocks ?? [])
|
|
75
|
+
const launched = blocks.flatMap((block) => launched_ids(block))
|
|
69
76
|
|
|
70
77
|
return launched.filter((id) => id !== '' && !finished.has(id))
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
// The background tasks the tail launched and never saw finish — commands and subagents alike
|
|
81
|
+
// (joshuafolkken/kit#2774), since a lane child's turn-end kills either.
|
|
82
|
+
function pending_background_ids(tail: string): ReadonlyArray<string> {
|
|
83
|
+
return unfinished_ids(tail, (block) => [block.background_id, block.agent_id])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The backgrounded subagents alone. A subagent always ends and its notice re-invokes the session; a
|
|
87
|
+
// command may be a server that never exits, so only this set says the run is certain to resume.
|
|
88
|
+
function pending_agent_ids(tail: string): ReadonlyArray<string> {
|
|
89
|
+
return unfinished_ids(tail, (block) => [block.agent_id])
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
const LANE_BACKGROUND_REASON =
|
|
74
93
|
'⛔ lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless ' +
|
|
75
94
|
'`claude -p` process whose background Bash tasks are killed when the turn ends — so a backgrounded ' +
|
|
@@ -105,6 +124,7 @@ const lane_background = {
|
|
|
105
124
|
LANE_BACKGROUND_STOP_REASON,
|
|
106
125
|
ROW,
|
|
107
126
|
is_background_long_run,
|
|
127
|
+
pending_agent_ids,
|
|
108
128
|
pending_background_ids,
|
|
109
129
|
}
|
|
110
130
|
|
|
@@ -67,9 +67,12 @@ interface StopContext {
|
|
|
67
67
|
headless_refusals: number
|
|
68
68
|
// This session is a dispatched lane child for this checkout — `lane_child_marker.is_child_of`.
|
|
69
69
|
lane_child: boolean
|
|
70
|
-
// A background task this run launched has not finished —
|
|
71
|
-
// over the transcript tail (joshuafolkken/kit#2704).
|
|
70
|
+
// A background task this run launched — a command or a subagent — has not finished —
|
|
71
|
+
// `lane_background.pending_background_ids` over the transcript tail (joshuafolkken/kit#2704).
|
|
72
72
|
background_pending: boolean
|
|
73
|
+
// A backgrounded subagent this run launched has not finished — `lane_background.pending_agent_ids`
|
|
74
|
+
// (joshuafolkken/kit#2774). Apart from `background_pending`, since only a subagent is sure to end.
|
|
75
|
+
agent_pending: boolean
|
|
73
76
|
// The resolved `JOSH_SESSION_LANG` — `session_language.resolve_session_lang` (joshuafolkken/kit#2470).
|
|
74
77
|
session_lang: string
|
|
75
78
|
}
|
|
@@ -221,12 +224,21 @@ function needs_filing(context: StopContext): boolean {
|
|
|
221
224
|
return filing_offer.is_first_party_target(context.message, context.session_owner)
|
|
222
225
|
}
|
|
223
226
|
|
|
227
|
+
// **A running background subagent stands both hold rules down** (joshuafolkken/kit#2774). Its completion
|
|
228
|
+
// notice re-invokes the session, so the turn-end is a wait on the run's own work, not on a person — a
|
|
229
|
+
// notify there says "nothing needed" and dilutes the ones that do. A backgrounded command does not
|
|
230
|
+
// count: a dev server never ends, and a stop behind it would leave a person silently waiting. A lane
|
|
231
|
+
// child never reaches this with a task running: `needs_background_wait` answers it first.
|
|
232
|
+
function is_held_idle(context: StopContext): boolean {
|
|
233
|
+
return context.hold_present && !context.agent_pending
|
|
234
|
+
}
|
|
235
|
+
|
|
224
236
|
function needs_notify(context: StopContext): boolean {
|
|
225
|
-
return context
|
|
237
|
+
return is_held_idle(context) && !context.notified
|
|
226
238
|
}
|
|
227
239
|
|
|
228
240
|
function needs_release(context: StopContext): boolean {
|
|
229
|
-
return context
|
|
241
|
+
return is_held_idle(context) && context.tree_clean
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
function citation_reason(message: string): string | undefined {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { cost_cli } from '#scripts/cost-runtime/cost-cli'
|
|
4
|
+
import { cost_verdict } from '#scripts/cost-runtime/cost-verdict'
|
|
3
5
|
import {
|
|
4
6
|
run_carry,
|
|
5
7
|
type CarryClaim,
|
|
@@ -44,6 +46,9 @@ const UNKNOWN_VERDICT = 'unknown'
|
|
|
44
46
|
// the run carries on uncut rather than paying a cold preamble the accumulation it would shed no longer
|
|
45
47
|
// covers, exactly as an under-threshold pre-gate cut carries on to the gate.
|
|
46
48
|
const CAPPED_VERDICT = 'capped'
|
|
49
|
+
// The asking session is over the shared context-cut threshold, so nothing was claimed
|
|
50
|
+
// (joshuafolkken/kit#2760).
|
|
51
|
+
const OVER_VERDICT = 'over'
|
|
47
52
|
const NO_CUTS = 0
|
|
48
53
|
|
|
49
54
|
const CLAIM_VERDICTS: Record<CarryClaim, string> = {
|
|
@@ -235,7 +240,22 @@ function adopt(target: string, request: CarryClaimRequest, is_json: boolean): nu
|
|
|
235
240
|
return report_claim(target, read.carry, request, is_json)
|
|
236
241
|
}
|
|
237
242
|
|
|
243
|
+
// **A session already over the shared cut threshold claims nothing** (joshuafolkken/kit#2760). A claim
|
|
244
|
+
// assumed the asking session was fresh, so a `backlogrun` parent that had taken its cut at 233k could
|
|
245
|
+
// retype the keyword in the same conversation and be answered `resumed` by its own hand-off — measured
|
|
246
|
+
// twice, at 232k and 258k. The verdict is `pnpm josh cost --cut`'s, so no second threshold exists; only a
|
|
247
|
+
// measured `over` refuses, because a provider with no transcript cannot be priced at all.
|
|
248
|
+
function report_over(is_json: boolean): number {
|
|
249
|
+
console.error(
|
|
250
|
+
'This session is over the shared context-cut threshold (`pnpm josh cost --cut` answers `over`), so no run was begun or resumed here. End this conversation and type the invocation again in a fresh session.',
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return report(OVER_VERDICT, undefined, is_json, FAILURE_EXIT_CODE)
|
|
254
|
+
}
|
|
255
|
+
|
|
238
256
|
function claim_record(target: string, request: CarryClaimRequest, is_json: boolean): number {
|
|
257
|
+
if (cost_cli.session_verdict() === cost_verdict.OVER_VERDICT) return report_over(is_json)
|
|
258
|
+
|
|
239
259
|
return request.is_adoption ? adopt(target, request, is_json) : begin(target, request, is_json)
|
|
240
260
|
}
|
|
241
261
|
|
|
@@ -425,6 +445,7 @@ const run_carry_cli = {
|
|
|
425
445
|
EXPIRED_VERDICT,
|
|
426
446
|
MISMATCH_VERDICT,
|
|
427
447
|
NONE_VERDICT,
|
|
448
|
+
OVER_VERDICT,
|
|
428
449
|
RESUMED_VERDICT,
|
|
429
450
|
STANDING_VERDICT,
|
|
430
451
|
UNKNOWN_VERDICT,
|
|
@@ -9,6 +9,7 @@ import { openai_lane_supervisor } from '#scripts/lane/openai-lane-supervisor'
|
|
|
9
9
|
import { run_cut, type CutState, type RunCut } from './run-cut'
|
|
10
10
|
import { run_cut_args, type Request } from './run-cut-args'
|
|
11
11
|
import { run_cut_handoff } from './run-cut-handoff'
|
|
12
|
+
import { run_cut_report } from './run-cut-report'
|
|
12
13
|
import { run_event_stream } from './run-event-stream'
|
|
13
14
|
import { run_event_stream_emit } from './run-event-stream-emit'
|
|
14
15
|
|
|
@@ -24,115 +25,41 @@ import { run_event_stream_emit } from './run-event-stream-emit'
|
|
|
24
25
|
// session.
|
|
25
26
|
|
|
26
27
|
const ARGV_OFFSET = 2
|
|
27
|
-
const SUCCESS_EXIT_CODE = 0
|
|
28
|
-
const FAILURE_EXIT_CODE = 1
|
|
29
|
-
|
|
30
|
-
const CUT_VERDICT = 'cut'
|
|
31
|
-
const RESUME_VERDICT = 'resume'
|
|
32
|
-
// An implementation-phase resume: the fresh process continues implementing rather than going to the
|
|
33
|
-
// gate (joshuafolkken/kit#1933). It is a distinct token so the resuming child branches on it without
|
|
34
|
-
// re-reading the record.
|
|
35
|
-
const RESUME_IMPL_VERDICT = 'resume-impl'
|
|
36
|
-
const FRESH_VERDICT = 'fresh'
|
|
37
|
-
// The recent-window context is under the shared cut threshold, so the pre-gate cut was not worth its
|
|
38
|
-
// resume and nothing was cut (joshuafolkken/kit#2312). A benign, non-failing answer: the run carries
|
|
39
|
-
// on to the gate itself, exactly as `not-a-lane` does.
|
|
40
|
-
const UNDER_THRESHOLD_VERDICT = 'under-threshold'
|
|
41
|
-
const STALE_VERDICT = 'stale'
|
|
42
|
-
// The record matched the tree but carried no instruction to resume on, so the run was refused rather
|
|
43
|
-
// than continued blind to what it was told to do (joshuafolkken/kit#2354).
|
|
44
|
-
const INCOMPLETE_VERDICT = 'incomplete'
|
|
45
|
-
// The `--handoff` path was given but could not be read as a handoff, or was too large for the record;
|
|
46
|
-
// the cut is refused rather than taken without the instruction it was meant to carry.
|
|
47
|
-
const BAD_HANDOFF_VERDICT = 'bad-handoff'
|
|
48
|
-
const HANDED_OFF_VERDICT = 'handed-off'
|
|
49
|
-
const BUSY_VERDICT = 'busy'
|
|
50
|
-
const NOT_A_LANE_VERDICT = 'not-a-lane'
|
|
51
|
-
const UNREADY_VERDICT = 'unready'
|
|
52
|
-
const FAILED_VERDICT = 'failed'
|
|
53
|
-
const ENDED_VERDICT = 'ended'
|
|
54
|
-
const UNREADABLE_VERDICT = 'unreadable'
|
|
55
|
-
const UNKNOWN_VERDICT = 'unknown'
|
|
56
|
-
|
|
57
|
-
function report(verdict: string, code: number): number {
|
|
58
|
-
console.info(verdict)
|
|
59
|
-
|
|
60
|
-
return code
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function report_stale(cut_record: RunCut): number {
|
|
64
|
-
console.error(run_cut.stale_message(cut_record))
|
|
65
|
-
|
|
66
|
-
return report(STALE_VERDICT, FAILURE_EXIT_CODE)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function report_busy(cut_record: RunCut): number {
|
|
70
|
-
console.error(run_cut.busy_message(cut_record))
|
|
71
28
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.error(run_cut.unreadable_message())
|
|
107
|
-
|
|
108
|
-
return report(UNREADABLE_VERDICT, FAILURE_EXIT_CODE)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function report_unknown(): number {
|
|
112
|
-
console.error(run_cut.unknown_message())
|
|
113
|
-
|
|
114
|
-
return report(UNKNOWN_VERDICT, FAILURE_EXIT_CODE)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// A cut is only meaningful on a lane branch whose implementation is uncommitted. On the default
|
|
118
|
-
// branch, or with a clean tree, there is nothing to carry across the boundary.
|
|
119
|
-
function refuse_unready(state: CutState): number {
|
|
120
|
-
console.error(
|
|
121
|
-
`Not ready to cut on ${state.branch} (dirty: ${String(state.is_dirty)}); a cut needs an uncommitted lane branch. Nothing was cut.`,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
return report(UNREADY_VERDICT, FAILURE_EXIT_CODE)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Below the shared context threshold there is no accumulation a cut would drop, so the resume it would
|
|
128
|
-
// relaunch costs more than it saves; the current process carries the run on to the gate uncut.
|
|
129
|
-
function report_under_threshold(): number {
|
|
130
|
-
console.error(
|
|
131
|
-
'The recent-window context is under the shared cut threshold, so nothing was cut; a resume would cost more than the accumulation it would drop. Continue to the gate.',
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
return report(UNDER_THRESHOLD_VERDICT, SUCCESS_EXIT_CODE)
|
|
135
|
-
}
|
|
29
|
+
const {
|
|
30
|
+
BAD_HANDOFF_VERDICT,
|
|
31
|
+
BUSY_VERDICT,
|
|
32
|
+
CUT_VERDICT,
|
|
33
|
+
ENDED_VERDICT,
|
|
34
|
+
FAILED_VERDICT,
|
|
35
|
+
FAILURE_EXIT_CODE,
|
|
36
|
+
FRESH_VERDICT,
|
|
37
|
+
HANDED_OFF_VERDICT,
|
|
38
|
+
HANDOFF_MISSING_NOTE,
|
|
39
|
+
HANDOFF_OVERFLOW_NOTE,
|
|
40
|
+
INCOMPLETE_VERDICT,
|
|
41
|
+
NOT_A_LANE_VERDICT,
|
|
42
|
+
OVER_VERDICT,
|
|
43
|
+
RESUME_IMPL_VERDICT,
|
|
44
|
+
RESUME_VERDICT,
|
|
45
|
+
STALE_VERDICT,
|
|
46
|
+
SUCCESS_EXIT_CODE,
|
|
47
|
+
UNDER_THRESHOLD_VERDICT,
|
|
48
|
+
UNKNOWN_VERDICT,
|
|
49
|
+
UNREADABLE_VERDICT,
|
|
50
|
+
UNREADY_VERDICT,
|
|
51
|
+
refuse_unready,
|
|
52
|
+
report,
|
|
53
|
+
report_bad_handoff,
|
|
54
|
+
report_busy,
|
|
55
|
+
report_handed_off,
|
|
56
|
+
report_incomplete,
|
|
57
|
+
report_over,
|
|
58
|
+
report_stale,
|
|
59
|
+
report_under_threshold,
|
|
60
|
+
report_unknown,
|
|
61
|
+
report_unreadable,
|
|
62
|
+
} = run_cut_report
|
|
136
63
|
|
|
137
64
|
// **The pre-gate cut is conditional on the same statistic the implementation-phase cut reads**
|
|
138
65
|
// (joshuafolkken/kit#2312). `cost_cli.session_verdict` prices the recent `RECENT_REQUEST_WINDOW`
|
|
@@ -240,7 +167,9 @@ function spec_refusal(spec: Parameters<typeof run_cut.begin_cut>[1]): string | u
|
|
|
240
167
|
return undefined
|
|
241
168
|
}
|
|
242
169
|
|
|
243
|
-
|
|
170
|
+
// Writes the record and appends the `cut` event, or answers the refusal that stopped it — the half of a
|
|
171
|
+
// cut every checkout shares; what follows the write (a relaunch or a stop) is the caller's.
|
|
172
|
+
async function write_cut(target: string, request: CutRequest): Promise<number | undefined> {
|
|
244
173
|
const loaded = run_cut_handoff.load_handoff(request.handoff_path, run_cut.MAX_HANDOFF_BYTES)
|
|
245
174
|
if (loaded.kind === 'bad') return report_bad_handoff(loaded.note)
|
|
246
175
|
|
|
@@ -258,11 +187,53 @@ async function finish_cut(target: string, lane: LaneInfo, request: CutRequest):
|
|
|
258
187
|
|
|
259
188
|
await emit_cut_event(request)
|
|
260
189
|
|
|
190
|
+
return undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function finish_cut(target: string, lane: LaneInfo, request: CutRequest): Promise<number> {
|
|
194
|
+
const refused = await write_cut(target, request)
|
|
195
|
+
|
|
196
|
+
if (refused !== undefined) return refused
|
|
197
|
+
|
|
261
198
|
return lane_relaunch.is_openai_lane(lane)
|
|
262
199
|
? report(CUT_VERDICT, SUCCESS_EXIT_CODE)
|
|
263
200
|
: relaunch(target, lane, request.phase)
|
|
264
201
|
}
|
|
265
202
|
|
|
203
|
+
// The cut outside a lane is taken, and nothing is relaunched: the session that took it is the one a
|
|
204
|
+
// person is watching, so the run is handed on the way `fullrun`'s entry `over` stop hands it on.
|
|
205
|
+
function report_held_cut(issue: string): number {
|
|
206
|
+
console.error(
|
|
207
|
+
`The cut is recorded for #${issue} outside a lane, so nothing was relaunched. Keep the hold, send a \`confirmation\` Telegram whose body names the resume command \`fullrun #${issue}\`, and end the turn; the fresh session's \`pnpm josh run:cut --resume ${issue}\` answers \`resume-impl\` with the handoff.`,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return report(CUT_VERDICT, SUCCESS_EXIT_CODE)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// **A `fullrun` held in its own checkout takes the implementation cut too** (joshuafolkken/kit#2760).
|
|
214
|
+
// The cut was lane-only, so a run a person started had no bound on its context mid-implementation. The
|
|
215
|
+
// hold naming this issue is what marks the run — a tree nobody holds for it, and the pre-gate phase
|
|
216
|
+
// (which a held run's gate reaches in the same session), stay `not-a-lane` exactly as before.
|
|
217
|
+
async function cut_outside_lane(
|
|
218
|
+
target: string,
|
|
219
|
+
request: Omit<CutRequest, 'branch'>,
|
|
220
|
+
): Promise<number> {
|
|
221
|
+
if (request.phase !== run_cut.IMPLEMENTATION_PHASE) {
|
|
222
|
+
return report(NOT_A_LANE_VERDICT, SUCCESS_EXIT_CODE)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const state = await run_cut.current_state()
|
|
226
|
+
|
|
227
|
+
if (state.held_issue !== request.issue) return report(NOT_A_LANE_VERDICT, SUCCESS_EXIT_CODE)
|
|
228
|
+
if (!(await is_lane_branch(state))) return refuse_unready(state)
|
|
229
|
+
|
|
230
|
+
clear_expired(target)
|
|
231
|
+
|
|
232
|
+
const refused = await write_cut(target, { ...request, branch: state.branch })
|
|
233
|
+
|
|
234
|
+
return refused ?? report_held_cut(request.issue)
|
|
235
|
+
}
|
|
236
|
+
|
|
266
237
|
async function cut(
|
|
267
238
|
target: string,
|
|
268
239
|
issue: string,
|
|
@@ -271,7 +242,7 @@ async function cut(
|
|
|
271
242
|
): Promise<number> {
|
|
272
243
|
const lane = await lane_registry.find_open_lane(issue)
|
|
273
244
|
|
|
274
|
-
if (lane === undefined) return
|
|
245
|
+
if (lane === undefined) return await cut_outside_lane(target, { issue, phase, handoff_path })
|
|
275
246
|
|
|
276
247
|
const state = await run_cut.current_state()
|
|
277
248
|
|
|
@@ -331,6 +302,14 @@ function adopt(target: string, cut_record: RunCut): number {
|
|
|
331
302
|
return report(resume_verdict_for(cut_record), SUCCESS_EXIT_CODE)
|
|
332
303
|
}
|
|
333
304
|
|
|
305
|
+
// A relaunched lane child and a fresh session both measure `under`, so only the session that took an
|
|
306
|
+
// implementation cut resumes over the threshold (joshuafolkken/kit#2760); unmeasurable is not refused.
|
|
307
|
+
function is_over_threshold_resume(cut_record: RunCut): boolean {
|
|
308
|
+
if (!run_cut.resumes_into_implementation(cut_record.phase)) return false
|
|
309
|
+
|
|
310
|
+
return cost_cli.session_verdict() === cost_verdict.OVER_VERDICT
|
|
311
|
+
}
|
|
312
|
+
|
|
334
313
|
async function verify_and_adopt(
|
|
335
314
|
target: string,
|
|
336
315
|
cut_record: RunCut,
|
|
@@ -350,6 +329,8 @@ async function verify_and_adopt(
|
|
|
350
329
|
|
|
351
330
|
if (verdict === INCOMPLETE_VERDICT) return report_incomplete(cut_record)
|
|
352
331
|
|
|
332
|
+
if (is_over_threshold_resume(cut_record)) return report_over()
|
|
333
|
+
|
|
353
334
|
return adopt(target, cut_record)
|
|
354
335
|
}
|
|
355
336
|
|
|
@@ -444,6 +425,7 @@ const run_cut_cli = {
|
|
|
444
425
|
HANDED_OFF_VERDICT,
|
|
445
426
|
INCOMPLETE_VERDICT,
|
|
446
427
|
NOT_A_LANE_VERDICT,
|
|
428
|
+
OVER_VERDICT,
|
|
447
429
|
RESUME_IMPL_VERDICT,
|
|
448
430
|
RESUME_VERDICT,
|
|
449
431
|
STALE_VERDICT,
|