@joshuafolkken/kit 1.957.0 → 1.978.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 +4 -4
- package/.claude/skills/workflow-commands/halfrun.md +12 -7
- package/.claude/skills/workflow-commands/observation-filing.md +31 -25
- package/.claude/skills/workflow-commands/pre-gate-cut.md +96 -161
- package/.claude/skills/workflow-commands/working-tree-hold.md +16 -1
- package/.github/workflows/pr-classification.yml +12 -1
- package/CLAUDE.md +3 -3
- package/README.md +25 -21
- package/dist/CLAUDE.md +3 -3
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-XSL4SRMA.js → chunk-2K52BRWV.js} +2 -2
- package/dist/hooks/{chunk-M6K7EQDO.js → chunk-BNGGEY3Z.js} +38 -32
- package/dist/hooks/{chunk-KOCK5FXQ.js → chunk-BVMSPG3M.js} +3 -3
- package/dist/hooks/{chunk-3ON7HOYB.js → chunk-CRGJTD6E.js} +1 -1
- package/dist/hooks/{chunk-HJXQZ7V4.js → chunk-F6OM6SIG.js} +3 -3
- package/dist/hooks/{chunk-LDQDZYHX.js → chunk-KQG4776P.js} +1 -1
- package/dist/hooks/{chunk-244WYOEO.js → chunk-NXL3SBXY.js} +2 -2
- package/dist/hooks/{chunk-IJPBTDTB.js → chunk-Q7LE77CO.js} +88 -18
- package/dist/hooks/{chunk-BELI5XSS.js → chunk-ZPLXLJ7O.js} +15 -5
- package/dist/hooks/{chunk-GNOITLRR.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 +8 -4
- package/package.json +4 -4
- 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-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/ci/pr-classification.ts +13 -12
- 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 +75 -5
- package/scripts/hooks/stop-guard.ts +1 -0
- package/scripts/init/init-bootstrap.ts +92 -0
- package/scripts/init/init-install.ts +67 -0
- package/scripts/init/init-logic-workspace.ts +1 -1
- package/scripts/init/init.ts +58 -13
- package/scripts/init/start-plan.ts +1 -1
- package/scripts/init/start-steps.ts +5 -28
- package/scripts/issue/issue-closing-pr.ts +93 -0
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +2 -1
- package/scripts/josh/josh-commands-project.ts +5 -1
- 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/repo/repository-labels.ts +91 -0
- 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 +70 -3
- package/scripts/run/run-halfrun-resume.ts +104 -0
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-hold-cli.ts +57 -13
- package/scripts/run/run-hold.ts +26 -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-step-cli.ts +16 -0
- package/scripts/run/run-step.ts +28 -4
- 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/security/security-audit.ts +12 -1
- package/scripts/sync/sync.ts +6 -0
- 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
|
@@ -18,11 +18,9 @@ by that supervisor; an AI parent is started only for a driver branch that requir
|
|
|
18
18
|
pnpm josh run:progress --wait --output <the transcript path of each delegated unit> # in the background
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**`--wait` waits one silence interval out, prints one line and exits —
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
right only where output is streamed. The parent changes no value on the line. **Every interval is three
|
|
25
|
-
moves and no fourth:**
|
|
21
|
+
**`--wait` waits one silence interval out, prints one line and exits** — the long-running form (no
|
|
22
|
+
`--wait`) is right only where output is streamed. The parent changes no value on the line. **Every
|
|
23
|
+
interval is three moves and no fourth:**
|
|
26
24
|
|
|
27
25
|
1. Start `pnpm josh run:progress --wait --output <transcript paths>` **in the background**.
|
|
28
26
|
2. When it exits, **present what it printed as-is** — the command prints the five labelled lines and the
|
|
@@ -35,8 +33,7 @@ lane children are in-flight but the watcher's life record has gone stale. **It i
|
|
|
35
33
|
until the watcher is restarted — once per run, so the `pnpm josh run:progress --wait` that fixes it is
|
|
36
34
|
not blocked.
|
|
37
35
|
|
|
38
|
-
**`run:progress` prints the five labelled lines itself
|
|
39
|
-
as-is.** It emits the observation instant (`at`, local first and UTC beside it), the elapsed figures
|
|
36
|
+
**`run:progress` prints the five labelled lines itself (joshuafolkken/kit#2026); present them as-is.** It emits the observation instant (`at`, local first and UTC beside it), the elapsed figures
|
|
40
37
|
(`quiet` and `unchanged`), the children in flight, the run state (`lanes`, `load`, `record`), and the
|
|
41
38
|
scheduled `next`, each behind its own label and on its own line — so relay the output verbatim and
|
|
42
39
|
round, rephrase or re-label nothing. How each field reads when it has nothing to report (`record
|
|
@@ -47,30 +44,24 @@ running*; and the `next` line is a schedule — the time *if the silence continu
|
|
|
47
44
|
real report resets the clock through `--mark`.
|
|
48
45
|
|
|
49
46
|
**It exits only when it has a line to hand over**, or when `--hours` runs out having never gone quiet
|
|
50
|
-
for a whole interval (that exit says so on standard error). **Nothing in flight keeps it waiting
|
|
51
|
-
step 3 would make it a poll.
|
|
47
|
+
for a whole interval (that exit says so on standard error). **Nothing in flight keeps it waiting.**
|
|
52
48
|
|
|
53
49
|
**`--mark` at every real report.** Whenever this loop reports something of its own — a child merged,
|
|
54
|
-
parked, a stop — run `pnpm josh run:progress --mark` in the same turn to restart the silence clock,
|
|
55
|
-
heartbeat does not land immediately behind a real report. The clock is silence, never a timer
|
|
50
|
+
parked, a stop — run `pnpm josh run:progress --mark` in the same turn to restart the silence clock. The clock is silence, never a timer
|
|
56
51
|
(`docs/josh-commands.md` → "`josh run:progress`").
|
|
57
52
|
|
|
58
|
-
**Do not keep a progress clock of your own** — the hook refuses the arm rather than asking.
|
|
59
|
-
|
|
60
|
-
`decide` refuses a sleep-only `Bash` call on three tests — a timer it already allowed is still live, the
|
|
53
|
+
**Do not keep a progress clock of your own** — the hook refuses the arm rather than asking.
|
|
54
|
+
`scripts/rules/early-heartbeat.ts` → `decide` refuses a sleep-only `Bash` call on three tests — a timer it already allowed is still live, the
|
|
61
55
|
report it would produce would land before the interval is up, or the wait runs longer than the interval
|
|
62
56
|
— so **a single correctly-spaced arm is allowed**. It is still not the way to report; the step above
|
|
63
57
|
prints without arming anything, and `--wait` is not a wait timer.
|
|
64
58
|
|
|
65
59
|
**The default interval is twenty minutes, overridable — by the person, not the run.**
|
|
66
60
|
`JOSH_PROGRESS_INTERVAL_MINUTES` moves both sides (the guard reads it through the watcher's reader);
|
|
67
|
-
`josh` → `progress_interval_minutes` in `package.json` sits one step below the variable
|
|
68
|
-
|
|
69
|
-
has no command line — so it makes the watcher quieter than the floor, never the guard stricter. Twenty
|
|
70
|
-
rather than ten because a child measures 20–46 minutes.
|
|
61
|
+
`josh` → `progress_interval_minutes` in `package.json` sits one step below the variable. **`--interval`
|
|
62
|
+
moves the watcher alone** — it makes the watcher quieter than the floor, never the guard stricter.
|
|
71
63
|
|
|
72
|
-
**An explicit ask is not a heartbeat, and it is exempt by construction
|
|
73
|
-
*timer*, and a person asking "how is it going" arrives with no timer in front of it.
|
|
64
|
+
**An explicit ask is not a heartbeat, and it is exempt by construction.**
|
|
74
65
|
**Every unscheduled progress statement is answered by `pnpm josh run:progress --once`** — presented in
|
|
75
66
|
the same five field lines, the reply to an explicit ask and the note just after a run starts alike.
|
|
76
67
|
**Never write a clock time the command did not print** (not an approximation, not a placeholder — where
|
|
@@ -79,15 +70,14 @@ is counted from the record the guard writes when it allows one**, never from the
|
|
|
79
70
|
processes.
|
|
80
71
|
|
|
81
72
|
**Every report opens with the time the observation was taken** — not only how long it has been quiet: an
|
|
82
|
-
absolute instant, `at YYYY-MM-DD HH:MM±HH:MM / YYYY-MM-DDTHH:MMZ`.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
sessions). **It is added, never substituted for the elapsed figure**, and **the presented line needs no
|
|
87
|
-
stamp computed for it** — the command prints the `at` stamp and the `next` field.
|
|
73
|
+
absolute instant, `at YYYY-MM-DD HH:MM±HH:MM / YYYY-MM-DDTHH:MMZ`. **The date is part of it**, and **the local clock
|
|
74
|
+
leads with UTC beside it and the offset that ties them**. **It is added, never substituted for the
|
|
75
|
+
elapsed figure**, and **the presented line needs no stamp computed for it** — the command prints the
|
|
76
|
+
`at` stamp and the `next` field.
|
|
88
77
|
|
|
89
78
|
**The line carries observations, never "still running", and nothing in it is a verification result** —
|
|
90
|
-
no gate, CI or check rollup, because the command reads none of them.
|
|
79
|
+
no gate, CI or check rollup, because the command reads none of them. Rationale:
|
|
80
|
+
`docs/maintainers/backlogrun-progress-rationale.md` → "Why the heartbeat reads as it does".
|
|
91
81
|
|
|
92
82
|
**The invariant is a tier, not a mechanism** (joshuafolkken/kit#2156, joshuafolkken/kit#2207). Three
|
|
93
83
|
tiers say how much of a person's attention a signal takes: **interrupt** reaches for it now (a Telegram),
|
|
@@ -101,59 +91,48 @@ stop, a merge — are appended to one ordered stream keyed to the run's identity
|
|
|
101
91
|
which survives the cut because it is the run's and not any one session's. **Every session is a writer;
|
|
102
92
|
the reader is a script in a pane of the person's own, never a conversation** (#2492):
|
|
103
93
|
`pnpm josh run:event --watch` prints each event the moment it lands, in the session language, and the
|
|
104
|
-
`heartbeat` events say the run is alive when quiet.
|
|
105
|
-
event, so **no session relays the stream** — name the watch command once, at the first cut or when
|
|
94
|
+
`heartbeat` events say the run is alive when quiet. **No session relays the stream** — name the watch command once, at the first cut or when
|
|
106
95
|
asked. **It is the same reader before and after the cut**: a cut moves who executes, never where the
|
|
107
96
|
stream lives.
|
|
108
97
|
|
|
109
|
-
**Interrupt is withheld on purpose**: no Telegram
|
|
110
|
-
interrupt a person, and a line every fifteen minutes is the fatigue that stops them being
|
|
111
|
-
read. **`pnpm josh run:wake --list` is the requested tier** — the last-event read of the stream the
|
|
98
|
+
**Interrupt is withheld on purpose**: no Telegram for the heartbeat. **`pnpm josh run:wake --list` is the requested tier** — the last-event read of the stream the
|
|
112
99
|
watch pane reads, for a person who types for one line rather than watching. **`tail -F` on the
|
|
113
100
|
raw stream file is a recovery path, not the ambient surface** — the watch pane is how progress is seen
|
|
114
101
|
without asking, and the raw `tail` is the fallback when the pane has stopped, named for that role in
|
|
115
102
|
`--list`'s own output.
|
|
116
103
|
|
|
117
|
-
**A stop is an interrupt, and only a stop** (joshuafolkken/kit#2136).
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
after a cut "quiet" and "stopped" look identical until a person reads for it. So the terminal stop is
|
|
121
|
-
pushed to the interrupt tier: `pnpm josh run:carry --end --stopped "<reason>"` sends one ⏸️ confirmation
|
|
104
|
+
**A stop is an interrupt, and only a stop** (joshuafolkken/kit#2136). A run that has _stopped_ — every
|
|
105
|
+
remaining child blocked behind a parked one, the backlog drained of anything runnable, the failure
|
|
106
|
+
streak tripped — is pushed to the interrupt tier: `pnpm josh run:carry --end --stopped "<reason>"` sends one ⏸️ confirmation
|
|
122
107
|
as it ends the record, and because the record is gone by the second `--end` the same stop never notifies
|
|
123
108
|
twice. A parked child is pushed by the child itself, which records the park and sends its own
|
|
124
109
|
`confirmation` before it stops (`backlogrun-park.md`). **Nothing new carries either one** — both ride the
|
|
125
|
-
existing notification types and the record the watcher already keeps.
|
|
110
|
+
existing notification types and the record the watcher already keeps. Rationale for the tiers:
|
|
111
|
+
`docs/maintainers/backlogrun-progress-rationale.md` → "Why the signal tiers are split this way".
|
|
126
112
|
|
|
127
113
|
**A heartbeat is emitted from the moment a run has started, even before any child carries
|
|
128
114
|
`in-progress`** — "a run has started" is read from a mechanical record (a registered lane, a held work
|
|
129
115
|
tree, or a carried budget), and the line names that stage as an observed fact (`no in-progress child
|
|
130
|
-
yet`). **Only a checkout with no run recorded at all stays silent
|
|
131
|
-
conversational session outside the heartbeat.
|
|
116
|
+
yet`). **Only a checkout with no run recorded at all stays silent.**
|
|
132
117
|
|
|
133
118
|
**The scope is every implementing run, not this command alone.** `fullrun`, `halfrun` and `backlogrun`
|
|
134
119
|
start the same watcher under the same rules, and this section is the single source for all four; `kickoff`
|
|
135
|
-
starts none. **`halfrun` is included
|
|
136
|
-
still implements, runs the whole gate, both review rounds and `pnpm josh test:e2e`, most of the 20–46
|
|
137
|
-
minutes.
|
|
120
|
+
starts none. **`halfrun` is included.**
|
|
138
121
|
|
|
139
122
|
**One watcher per run, and the outermost invocation is the one that starts it.** A `fullrun` running as
|
|
140
123
|
a `backlogrun` named issue or an `backlogrun` child starts none: the brief names the invocation it descends
|
|
141
124
|
from. `backlogrun` starts one for the whole batch. **A dispatched lane child is refused a watcher by its
|
|
142
125
|
`JOSH_LANE_CHILD` mark** — it still runs `--mark` for the parent's clock, but every reporting form
|
|
143
|
-
(`--wait`, `--once`, the default watch) exits at once with a notice
|
|
144
|
-
is harmless.
|
|
126
|
+
(`--wait`, `--once`, the default watch) exits at once with a notice.
|
|
145
127
|
|
|
146
|
-
**In a single-issue run it starts immediately after `pnpm josh run:hold` succeeds**
|
|
147
|
-
|
|
148
|
-
`--mark` is run there too** — a cross-repository run is implemented in that repository's checkout, and a
|
|
149
|
-
watcher started in the session's own tree would read the wrong `in-progress` listing.
|
|
128
|
+
**In a single-issue run it starts immediately after `pnpm josh run:hold` succeeds.** **It is started in the target repository's checkout, and
|
|
129
|
+
`--mark` is run there too.**
|
|
150
130
|
|
|
151
131
|
**What counts as a real report when the run has one issue.** A `fullrun` has exactly one child, so the
|
|
152
132
|
unit is **any turn that puts a progress statement in front of the person** — four of them, `--mark` run
|
|
153
133
|
in the same turn as each: **the Step 0 work summary, the pull request opening, each review round's
|
|
154
134
|
verdict, and any `confirmation` / `failure` / `completion` notification or stop.** **A tool result only
|
|
155
|
-
you read is not one** — a gate run, a `gh` read, an edit
|
|
156
|
-
the interval measures.
|
|
135
|
+
you read is not one** — a gate run, a `gh` read, an edit.
|
|
157
136
|
|
|
158
137
|
**On a quiet tick the turn is the relayed line plus at most two lines of the run's own prose**, saying
|
|
159
138
|
only what changed stage and what is being waited on. **A tick is quiet when none of the real reports
|
|
@@ -163,9 +142,9 @@ plan** — `epic:next`, the epic body and the progress comment hold all three. *
|
|
|
163
142
|
bounded by that**: `--mark` restarts the clock, so a merge, a park or a stop is where the run may be
|
|
164
143
|
long.
|
|
165
144
|
|
|
166
|
-
**`--output` is omitted in a single-issue run, and `record` reads `unread
|
|
167
|
-
|
|
168
|
-
|
|
145
|
+
**`--output` is omitted in a single-issue run, and `record` reads `unread`.** Rationale for the
|
|
146
|
+
watcher's scope and placement: `docs/maintainers/backlogrun-progress-rationale.md` → "Why the watcher
|
|
147
|
+
runs where it does".
|
|
169
148
|
|
|
170
149
|
**A run that merges needs no teardown; a run that stops has to end the reporting itself.** The issue
|
|
171
150
|
leaves the `in-progress` listing at the merge, so whatever is waiting prints nothing and `--hours` ends
|
|
@@ -210,21 +189,16 @@ single non-numeric line as the verdict.
|
|
|
210
189
|
|
|
211
190
|
**Start the unit without blocking on it — `pnpm josh lane:dispatch <N>` when the child runs in a
|
|
212
191
|
lane — and in the same turn start `pnpm josh lane:await <N...>` in the background** (joshuafolkken/kit#2113).
|
|
213
|
-
`lane:await` watches local process presence and exits when any named child confirms-complete
|
|
214
|
-
waking the parent at the actual completion rather than at the next heartbeat interval. **The
|
|
192
|
+
`lane:await` watches local process presence and exits when any named child confirms-complete. **The
|
|
215
193
|
re-confirm delay and poll interval are the command's, not the agent's** — pass only the issue
|
|
216
194
|
numbers. Without that wake (Codex), hand off instead — "A parent without a completion callback".
|
|
217
195
|
|
|
218
196
|
**A `lane:await` wake is a confirmed-gone process, so classify the ending at once — do not wait out
|
|
219
|
-
the silent-unit window** (joshuafolkken/kit#2277). `lane:await`
|
|
220
|
-
has disappeared and stayed gone across its re-confirm delay, which is the definitive end signal; a
|
|
221
|
-
child that died reaching the API wrote its last line — an `API Error` — just before it went, so a
|
|
222
|
-
`run:liveness` poll would read that fresh output as `alive` and sit for the whole 30-minute silent-unit
|
|
223
|
-
window before it ever said `stopped` (the measured 402-/52-minute waits). So on a `lane:await` wake go
|
|
224
|
-
straight to `pnpm josh run:merge <N> --output <path>` — the composite reads the child's **exit
|
|
197
|
+
the silent-unit window** (joshuafolkken/kit#2277). On a `lane:await` wake go straight to `pnpm josh run:merge <N> --output <path>` — the composite reads the child's **exit
|
|
225
198
|
record**, decisive whether the child merged, died on an outage, or abandoned mid-implementation
|
|
226
199
|
("Running a named epic's children" reads its tokens). An outage child is re-dispatched in the same run
|
|
227
|
-
and a run of them trips the environment guard; nothing waits for a person.
|
|
200
|
+
and a run of them trips the environment guard; nothing waits for a person. Rationale:
|
|
201
|
+
`docs/maintainers/backlogrun-progress-rationale.md` → "Why a child's ending is classified at once".
|
|
228
202
|
|
|
229
203
|
**`run:liveness` stays the fallback for a lane no `lane:await` is watching** — a handed-over lane a
|
|
230
204
|
resumed session polls ("Picking the lanes up in the fresh session"). Ask
|
|
@@ -247,8 +221,7 @@ single non-numeric line as the verdict.
|
|
|
247
221
|
the repository this session runs in.
|
|
248
222
|
|
|
249
223
|
`state: CLOSED` is the only answer that means the child finished. **Read the `human_review:` and
|
|
250
|
-
`labels:` lines before calling anything else a failure**
|
|
251
|
-
is why one command prints all of them. **A non-zero exit is not `OPEN`**: the command exits non-zero
|
|
224
|
+
`labels:` lines before calling anything else a failure.** **A non-zero exit is not `OPEN`**: the command exits non-zero
|
|
252
225
|
without a state when the number resolves to nothing (`does not resolve`) or the read failed (`could
|
|
253
226
|
not read`, a rate limit or expired auth). Re-read before deciding.
|
|
254
227
|
|
|
@@ -256,22 +229,17 @@ single non-numeric line as the verdict.
|
|
|
256
229
|
**not** count it against the consecutive-failure guard, and go back to step 1.
|
|
257
230
|
- **Open, and `human_review: yes`** — the child **stopped before its commit**, the run's own ending
|
|
258
231
|
(§2z). **`Open` is part of the test** — a CLOSED child carrying the label finished and merged.
|
|
259
|
-
**Read that line, not the `labels:` one
|
|
260
|
-
matching the lowercase string by eye drops the child into the failure branch below. Leave
|
|
232
|
+
**Read that line, not the `labels:` one.** Leave
|
|
261
233
|
`in-progress` **on**, do not park it, do not count it, and do **not** go back to step 1. Finish the
|
|
262
234
|
session and report. **Do not send a second `confirmation` Telegram** — the unit already sent one.
|
|
263
235
|
Where the child ran in this session's own context, that first notification is yours to send.
|
|
264
236
|
- **Open, without `needs-decision`** — it failed. Remove the stale `in-progress` here (Tier A), count
|
|
265
|
-
it against the consecutive-failure guard, and **park it**.
|
|
266
|
-
`epic:next` from handing the same child straight back.
|
|
237
|
+
it against the consecutive-failure guard, and **park it**.
|
|
267
238
|
|
|
268
|
-
**Never ask `epic:next` in place of this read.**
|
|
269
|
-
`in-progress`, which `epic:next` classifies as waiting on time — the loop would poll to the 90-minute
|
|
270
|
-
stale window and learn nothing.
|
|
239
|
+
**Never ask `epic:next` in place of this read.**
|
|
271
240
|
|
|
272
241
|
**A merge is one event, and one call of the parent's — `pnpm josh run:merge <N>`**
|
|
273
|
-
(joshuafolkken/kit#2024).
|
|
274
|
-
confirms the child from GitHub, does the post-merge steps, folds in the hand-off check, and prints
|
|
242
|
+
(joshuafolkken/kit#2024). The composite command confirms the child from GitHub, does the post-merge steps, folds in the hand-off check, and prints
|
|
275
243
|
the next child number — or a control verdict — for the parent to read. Pass the merged child's
|
|
276
244
|
number and the offer's source — `--epic <E> --repo <owner/repo>` for a named epic,
|
|
277
245
|
nothing for the opted-in backlog — with `--owner "$PPID"` so it counts into the carry record under
|
|
@@ -282,9 +250,9 @@ single non-numeric line as the verdict.
|
|
|
282
250
|
# a child number (or several, one per free lane) to run next, or a verdict token
|
|
283
251
|
```
|
|
284
252
|
|
|
285
|
-
**Pass `--output <path>` — the child's transcript** (joshuafolkken/kit#2240).
|
|
286
|
-
composite tell an API-outage ending
|
|
287
|
-
|
|
253
|
+
**Pass `--output <path>` — the child's transcript** (joshuafolkken/kit#2240). Without it the
|
|
254
|
+
composite cannot tell an API-outage ending from a genuine failure: an OPEN, unparked child is a plain
|
|
255
|
+
failure.
|
|
288
256
|
|
|
289
257
|
**What the one call does is decided by what the child turned out to be** — read from its GitHub state
|
|
290
258
|
(and, for the failed case alone, its exit record), never from a log: a **merged** child (CLOSED) is
|
|
@@ -295,8 +263,7 @@ single non-numeric line as the verdict.
|
|
|
295
263
|
**not** parked and **not** counted against the consecutive-failure guard, and stays re-dispatchable
|
|
296
264
|
(joshuafolkken/kit#2240); a **failed** child (OPEN, neither label, not an outage) has its stale
|
|
297
265
|
`in-progress` dropped, is parked with `needs-decision`, and is counted against the consecutive-failure
|
|
298
|
-
guard.
|
|
299
|
-
collapses.**
|
|
266
|
+
guard.
|
|
300
267
|
|
|
301
268
|
**Beyond the offer `epic:next` prints** (`run` becomes numbers; `wait` / `stop` / `complete` /
|
|
302
269
|
`error` pass through), the composite adds six verdict tokens: `over` — the merge crossed the budget,
|
|
@@ -311,8 +278,7 @@ single non-numeric line as the verdict.
|
|
|
311
278
|
call** — `pnpm josh cost --cut`, run after `pnpm josh ms`; `under` offers the next child,
|
|
312
279
|
`over` prints `over` so the parent hands its lanes over and takes the cut ("The hand-off" below), and
|
|
313
280
|
a session that cannot measure is read as `over`. **Never read the condition off `pnpm josh delegate
|
|
314
|
-
epic-child`**
|
|
315
|
-
fires.
|
|
281
|
+
epic-child`** ("The check is asked at every merge" below).
|
|
316
282
|
3. **`wait`** — go back to step 1. **With something of this run's own in flight, that happens on the
|
|
317
283
|
wake the progress watcher's exit delivers** and the parent starts no sleep of its own; the 60 s
|
|
318
284
|
figure bounds how soon the ask may be repeated. **With nothing in flight the watcher declines and
|
|
@@ -336,19 +302,18 @@ in the repository that owns it ("Who sends the summary, and who propagates" belo
|
|
|
336
302
|
|
|
337
303
|
## The hand-off — one session does not have to run the whole batch
|
|
338
304
|
|
|
339
|
-
**
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
305
|
+
**The run reads the marginal cost off a line rather than feeling for it** — at **every** child's
|
|
306
|
+
merge, and crossing it **hands the session off to a fresh one, carrying the budget with it**
|
|
307
|
+
(`backlogrun-steps.md` → "The session cut is inside the invocation"). The cut is where a `backlogrun`
|
|
308
|
+
spans several sessions; it is not a stop. Rationale: `docs/maintainers/backlogrun-progress-rationale.md`
|
|
309
|
+
→ "Why the hand-off check is asked at every merge".
|
|
344
310
|
|
|
345
311
|
```bash
|
|
346
312
|
pnpm josh cost --cut
|
|
347
313
|
```
|
|
348
314
|
|
|
349
315
|
It prints `over` or `under` on standard output and the measured figure on standard error. `over` means
|
|
350
|
-
the next turn of this session costs more than the threshold in billed input
|
|
351
|
-
explicitly so a run cannot drift it by remembering it wrong.
|
|
316
|
+
the next turn of this session costs more than the threshold in billed input.
|
|
352
317
|
|
|
353
318
|
**135,000 is shared by the scheduler entry, scheduler hand-off and lane worker implementation cut.**
|
|
354
319
|
The value is `CONTEXT_CUT_THRESHOLD` in `scripts/cost-runtime/context-cut-threshold.ts`; `cost --cut`,
|
|
@@ -357,23 +322,18 @@ numbers. The output ceiling and the entry-read figure are a separate 150,000, no
|
|
|
357
322
|
|
|
358
323
|
### The check is asked at every merge, and delegation does not excuse it
|
|
359
324
|
|
|
360
|
-
**A delegating parent reaches the threshold too
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
**Never wire the question to `pnpm josh delegate epic-child`** — a static policy lookup answering
|
|
365
|
-
`delegate` on every machine forever, so a gate built on it never fires.
|
|
325
|
+
**A delegating parent reaches the threshold too, so there is no condition: `pnpm josh cost --cut` is
|
|
326
|
+
asked after every child's merge**, delegated or not.
|
|
327
|
+
**Never wire the question to `pnpm josh delegate epic-child`** — a static policy lookup that answers
|
|
328
|
+
`delegate` everywhere.
|
|
366
329
|
|
|
367
330
|
`over` and `under` are not the only answers: the command **exits 1 with empty standard output** when
|
|
368
|
-
there is no transcript, or no request in it. **Neither is `under
|
|
369
|
-
|
|
370
|
-
the check could not answer, and take `over`'s branch at that child.
|
|
331
|
+
there is no transcript, or no request in it. **Neither is `under`** — report that the check could not
|
|
332
|
+
answer, and take `over`'s branch at that child.
|
|
371
333
|
|
|
372
334
|
### When to ask, and what to do
|
|
373
335
|
|
|
374
|
-
**Ask once per child, immediately after its merge and `pnpm josh ms`** — never mid-child.
|
|
375
|
-
only moment where **this** child's work is all written down: the PR is merged, the working tree is clean
|
|
376
|
-
on the default branch, and the epic's state on GitHub is complete.
|
|
336
|
+
**Ask once per child, immediately after its merge and `pnpm josh ms`** — never mid-child.
|
|
377
337
|
|
|
378
338
|
### A parent without a completion callback hands off at its first dispatch
|
|
379
339
|
|
|
@@ -383,16 +343,13 @@ below the cut cap, `wait` at it** (joshuafolkken/kit#2653): dispatch the wave, r
|
|
|
383
343
|
### The lane child reuses this measurement mid-implementation
|
|
384
344
|
|
|
385
345
|
**The same `pnpm josh cost --over` measurement bounds a lane child's context _during_ implementation, not
|
|
386
|
-
only the parent's between children
|
|
387
|
-
never caps the thinking a child accumulates while implementing. The child measures its own per-request
|
|
346
|
+
only the parent's between children**. The child measures its own per-request
|
|
388
347
|
context with **this command** at the shared `CONTEXT_CUT_THRESHOLD`, 135_000, and cuts with
|
|
389
348
|
`pnpm josh run:cut --impl <N> --handoff <path>`. **The measurement and threshold are single-sourced**:
|
|
390
349
|
`cost_verdict.per_request_cost` is what both seams compare, and `cost --cut` selects the same constant
|
|
391
350
|
for both. The boundary and the resume: `pre-gate-cut.md` → "The implementation-phase cut".
|
|
392
351
|
|
|
393
|
-
**A merge is not by itself a safe seam, because another lane may still be running.**
|
|
394
|
-
where each unit writes lives in the lane, so a session that never dispatched the child can poll it and
|
|
395
|
-
the pool is handed over instead of drained. **Read the lanes rather than judging them:**
|
|
352
|
+
**A merge is not by itself a safe seam, because another lane may still be running.** **Read the lanes rather than judging them:**
|
|
396
353
|
|
|
397
354
|
```bash
|
|
398
355
|
pnpm josh lane:list # `none`, or one line per lane with its state and its recorded output path
|
|
@@ -409,9 +366,7 @@ unit_output=$(pnpm josh lane:output <N>) &&
|
|
|
409
366
|
|
|
410
367
|
**`--process` carries what `pgrep` found, and with a dispatched child that is the whole answer.** Run
|
|
411
368
|
`pgrep -laf "(fullrun|run-ship-cli\.ts .*) #<N>$"` first and pass `alive` where it found the child and `none` where it did not
|
|
412
|
-
— **never `alive` because the child was dispatched
|
|
413
|
-
`pgrep` on the lane's directory never matches a live child; the trace is the deciding input, because a
|
|
414
|
-
log that has stopped moving is a session thinking rather than one that died.
|
|
369
|
+
— **never `alive` because the child was dispatched**, and never from a `pgrep` on the lane's directory.
|
|
415
370
|
|
|
416
371
|
**The `&&` is load-bearing.** A lane that records nothing prints `none` and exits non-zero; substituted
|
|
417
372
|
straight into `--output`, that `none` is a relative path and `run:liveness` answers `undetermined` for
|
|
@@ -421,8 +376,7 @@ ever.
|
|
|
421
376
|
- **`over`** — **the run hands its lanes over and takes the cut at once.** Open no new lane and take no
|
|
422
377
|
new child. Confirm every lane still in flight records an output path — one missing is filled in with
|
|
423
378
|
`pnpm josh lane:output <N> <path>` — then take one of the next two bullets in the same turn. **There
|
|
424
|
-
is no waiting here at all**:
|
|
425
|
-
an operating-system process of its own (`pnpm josh lane:dispatch`), so the lanes keep running.
|
|
379
|
+
is no waiting here at all**: the lanes keep running as processes of their own.
|
|
426
380
|
- **A lane nobody could poll** — `unreadable`, or `open` with no recorded path and none that can be
|
|
427
381
|
supplied — **and the cut does not happen.** Name that lane in the progress comment and go back to
|
|
428
382
|
step 1 of the loop; the reading is asked again at the next merge.
|
|
@@ -435,20 +389,17 @@ ever.
|
|
|
435
389
|
`backlogrun-steps.md` → "The session cut is inside the invocation" is the single source of the
|
|
436
390
|
carry; this reading is only where the cut is *taken*.
|
|
437
391
|
- **`capped`** — the invocation has taken its `MAX_CUTS` cuts (joshuafolkken/kit#2346), so `--cut`
|
|
438
|
-
refused and left the record un-handed-off. **Do not `run:wake` or hand off
|
|
439
|
-
as a crashed owner into a fresh cold session — the churn the cap prevents. Carry **this** session on
|
|
392
|
+
refused and left the record un-handed-off. **Do not `run:wake` or hand off.** Carry **this** session on
|
|
440
393
|
uncut and re-ask at the next merge.
|
|
441
394
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
every merge and cut at none. Recording the path removes that cost.
|
|
395
|
+
Rationale for handing lanes over: `docs/maintainers/backlogrun-progress-rationale.md` → "Why lanes are
|
|
396
|
+
handed over rather than drained".
|
|
445
397
|
|
|
446
398
|
**A lane is handed over only where the next session can actually poll it.** A lane whose state is
|
|
447
399
|
`open` **and** whose recorded path is not `-` is handed over. A **`stranded`** lane has no work tree and
|
|
448
400
|
so no running child — `pnpm josh lane:prune` closes it. An **`unreadable`** one cannot be told apart
|
|
449
401
|
from a running child, and an `open` lane recording **no path** is the same: **the cut does not happen**,
|
|
450
|
-
the lane is named in the epic progress comment, and the run goes back to step 1. **Never assume idle
|
|
451
|
-
a wrong cut abandons a child, a missed cut only costs tokens.
|
|
402
|
+
the lane is named in the epic progress comment, and the run goes back to step 1. **Never assume idle.**
|
|
452
403
|
|
|
453
404
|
**The hand-off report belongs to the stop, not to the reading.** For the one reading that does not stop —
|
|
454
405
|
an `unreadable` lane, or an `open` one recording no path — the run goes back to step 1 and writes no
|
|
@@ -469,8 +420,7 @@ a fresh session did not start those processes, so it reports `none` unless it ha
|
|
|
469
420
|
lane needs when its child has to be finished by hand.
|
|
470
421
|
|
|
471
422
|
**Restart the progress watcher by name** — `pnpm josh run:progress --wait --output <handed-over paths>`
|
|
472
|
-
in the background, in the same turn as that first `lane:list`. The
|
|
473
|
-
and the wired `run:watcher:guard` stops a resume that skips this (joshuafolkken/kit#2353).
|
|
423
|
+
in the background, in the same turn as that first `lane:list`. The wired `run:watcher:guard` stops a resume that skips this (joshuafolkken/kit#2353).
|
|
474
424
|
|
|
475
425
|
### A carried-over merge does not stand in front of the next lane
|
|
476
426
|
|
|
@@ -479,8 +429,7 @@ of its merge.** A lane handed over at the cut is still running its own `fullrun`
|
|
|
479
429
|
`pnpm josh followup` and the CI wait included — in a process of its own (background-commands.md →
|
|
480
430
|
"`pnpm josh followup` — foreground": foreground is *within the unit*, the background from the parent).
|
|
481
431
|
**So the parent never runs a carried-over child's `followup` itself**: it polls the handed-over lane and
|
|
482
|
-
opens new work beside it
|
|
483
|
-
new lane opens. **The reads and the dispatches go out together, in one turn** — `pnpm josh lane:list`,
|
|
432
|
+
opens new work beside it. **The reads and the dispatches go out together, in one turn** — `pnpm josh lane:list`,
|
|
484
433
|
`pnpm josh run:liveness`, `epic:next --lanes`, and opening a lane for a child it offers take none of each
|
|
485
434
|
other's results (the turn-batching rule this file states for a merge event, applied at the resume).
|
|
486
435
|
**Independence is the offer command's answer**: a new child `blocked-by` a carried-over Issue is
|
|
@@ -492,16 +441,12 @@ survive the cut is re-dispatched, never adopted** — `pnpm josh lane:open <N>`
|
|
|
492
441
|
|
|
493
442
|
**The single source of the run's counters is the carry record (`pnpm josh run:carry`), and the epic
|
|
494
443
|
progress comment is generated from it** (joshuafolkken/kit#2024) — children run, Issues filed,
|
|
495
|
-
**consecutive failures**, and the time the run started
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
alike** — that is what it is for — so the counters a run's own guards rest on are never taken by the
|
|
502
|
-
moment the context is dropped. **This is not what "Nothing is carried in the conversation" denies**:
|
|
503
|
-
that is about the state a *next session* needs, all of it on GitHub, while the record is about *this*
|
|
504
|
-
run's own guards.
|
|
444
|
+
**consecutive failures**, and the time the run started. `pnpm josh run:merge` writes the merge into
|
|
445
|
+
the record and mirrors the counters onto the epic comment at every child's merge. **The
|
|
446
|
+
consecutive-failure count is the one that matters** — the stopped-unit section leans on it to notice
|
|
447
|
+
the environment is at fault, and a merge resets it. **The record survives a session cut and a
|
|
448
|
+
compaction alike.** Rationale: `docs/maintainers/backlogrun-progress-rationale.md` → "Why state lives
|
|
449
|
+
in records, not the conversation".
|
|
505
450
|
|
|
506
451
|
### What carries over, and where it lives
|
|
507
452
|
|
|
@@ -514,11 +459,6 @@ run's own guards.
|
|
|
514
459
|
| What each remaining child is | the child Issue body |
|
|
515
460
|
| What already merged | the epic's task list, and the closed children |
|
|
516
461
|
|
|
517
|
-
That is the same state a resumed run has always used, which is why the hand-off needs no new mechanism.
|
|
518
|
-
**A planned hand-off is strictly more certain than an interruption**: an interruption can land mid-child
|
|
519
|
-
with a dirty tree and a stale `in-progress` label, and this cannot, because it is only taken when a child
|
|
520
|
-
has just closed.
|
|
521
|
-
|
|
522
462
|
**A resumed session is a new session**, so it asks `pnpm josh latest:scope` once before its first child.
|
|
523
463
|
|
|
524
464
|
## Waiting, and never waiting forever
|
|
@@ -526,8 +466,8 @@ has just closed.
|
|
|
526
466
|
| Setting | Value | Why |
|
|
527
467
|
| --- | --- | --- |
|
|
528
468
|
| Polling interval | 60 s | **A floor between two asks, never a clock the parent sets.** It bounds a re-ask made while the parent is *already awake*; what wakes it is "The parent keeps no clock of its own" below. |
|
|
529
|
-
| `backlogrun` idle-watch poll | 5 min | Not the interval above, and a floor in the same sense.
|
|
530
|
-
| Silent delegated unit | 30 min | Not the child's duration — the time its output has gone **unchanged**.
|
|
469
|
+
| `backlogrun` idle-watch poll | 5 min | Not the interval above, and a floor in the same sense. |
|
|
470
|
+
| Silent delegated unit | 30 min | Not the child's duration — the time its output has gone **unchanged**. Past it, run the traces above and book a stopped unit as a failure. |
|
|
531
471
|
| Stale `in-progress` | 90 min | Longer than any single child has taken; past it, the other session is gone. |
|
|
532
472
|
| Publish wait | 10 min | `josh propagate`'s own budget. A failed publish never appears. |
|
|
533
473
|
| Whole run | 8 h | An unattended run that has not finished overnight needs a person, not more waiting. |
|
|
@@ -543,8 +483,9 @@ completion and the backlog inside the detached process, without waking an AI par
|
|
|
543
483
|
instructions below apply only to a returned judgment branch. `run:progress --wait` stays the heartbeat
|
|
544
484
|
source and `run:report` the final report.
|
|
545
485
|
|
|
546
|
-
**The numbers above are floors between asks, not a timer the parent sets
|
|
547
|
-
|
|
486
|
+
**The numbers above are floors between asks, not a timer the parent sets.** Rationale:
|
|
487
|
+
`docs/maintainers/backlogrun-progress-rationale.md` → "Why the parent waits on classification, not a
|
|
488
|
+
clock".
|
|
548
489
|
|
|
549
490
|
The driver checks lane completion every five seconds and makes a new backlog offer no sooner than one
|
|
550
491
|
minute after the previous offer, except after collecting a child — waits that cost no AI turns. The
|
|
@@ -575,10 +516,6 @@ Waiting is decided by `epic:next`'s classification, never by reading labels:
|
|
|
575
516
|
| Nothing runnable, nothing resolves on its own, children remain | **Stop and report the parked children** |
|
|
576
517
|
| No open child | Post the epic summary and finish |
|
|
577
518
|
|
|
578
|
-
The distinction is not academic. When kit's child has closed and app-kit's child is waiting for the
|
|
579
|
-
release to publish, there is no runnable child, nothing carries `in-progress` and nothing carries
|
|
580
|
-
`needs-decision` — a label-based reading calls that "done" and stops, in the one moment it must wait.
|
|
581
|
-
|
|
582
519
|
## Who sends the summary, and who propagates
|
|
583
520
|
|
|
584
521
|
With several sessions on one epic, **exactly one does the end-of-epic work: the session standing in the
|
|
@@ -591,8 +528,7 @@ Per-child completion notifications are unchanged: `pnpm josh followup` sends one
|
|
|
591
528
|
compose that summary by hand — `pnpm josh run:report` generates it from this invocation's events**
|
|
592
529
|
(joshuafolkken/kit#2249, scoped in #2393): it renders what merged, what parked and why, and what was cut,
|
|
593
530
|
reusing `format_event`, closing with the release tail below. Its output is the Telegram body too — pass it to
|
|
594
|
-
`pnpm josh notify --body-file
|
|
595
|
-
from one generator.
|
|
531
|
+
`pnpm josh notify --body-file`.
|
|
596
532
|
|
|
597
533
|
**That same session asks `pnpm josh release:scope` once, after the last child has merged** — in the
|
|
598
534
|
primary checkout, after the last lane is closed, and never once per child. `followup-reference.md` →
|