@joshuafolkken/kit 1.957.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 +3 -3
- 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-BNWIOJFU.js} +29 -16
- 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-WWTMHHZU.js} +59 -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 +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-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 +23 -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/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/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-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/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
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
- Issue `pnpm josh git -y` in the background; its completion resumes the run, so the turn never ends
|
|
9
9
|
at the push.
|
|
10
10
|
- Keep `pnpm josh followup` in the foreground — nearly every following step reads its result.
|
|
11
|
+
- **Record before the CI wait ends, never after the merge** (joshuafolkken/kit#2763): the commit
|
|
12
|
+
carries the observation ledger lines recorded so far, and while CI runs the run makes the records
|
|
13
|
+
that need no CI result — observation Issues, the completion report draft.
|
|
11
14
|
- Overlap only tree-readers, and compose merge-independent tail data before `followup` (the table
|
|
12
15
|
below is the full mapping); keep `josh ms`, state checks, next-child selection, lane closing and the
|
|
13
16
|
session-cost check after the merge.
|
|
@@ -3,24 +3,21 @@
|
|
|
3
3
|
**Read this file in full once per session — before the first child is dispatched**, in the turn that
|
|
4
4
|
reaches `pnpm josh delegate epic-child` (or `pnpm josh lane:dispatch`). **A later child does not re-read
|
|
5
5
|
the whole file; it fetches only the section that child needs** — `pnpm josh doc:section backlogrun-child.md
|
|
6
|
-
"<heading>"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
single source of how one `backlogrun` child — of a named epic, a named issue, or the opted-in pool — is
|
|
11
|
-
run.
|
|
6
|
+
"<heading>"`. It is a point-of-use document, never an entry read: the entry procedure is `backlogrun.md`,
|
|
7
|
+
which points here at that step. This file is the single source of how one `backlogrun` child — of a
|
|
8
|
+
named epic, a named issue, or the opted-in pool — is run. Rationale:
|
|
9
|
+
`docs/maintainers/backlogrun-child-rationale.md` → "Why the file is read once and then by section".
|
|
12
10
|
|
|
13
11
|
## Running a child — the shared mechanics
|
|
14
12
|
|
|
15
|
-
Everything below runs **one child** — of a named epic, or of the opted-in backlog.
|
|
13
|
+
Everything below runs **one child** — of a named epic, or of the opted-in backlog. A single-issue named item, a named epic's child and a bare backlog child all run the same `fullrun` in the same way, and this part is the single source of how. A named epic runs these mechanics over its own children until every one is processed, then the run advances to the next named item.
|
|
16
14
|
|
|
17
15
|
## When `#N` is not an epic
|
|
18
16
|
|
|
19
17
|
`backlogrun` accepts an ordinary Issue as well as an epic. `backlogrun #<N>` on an Issue with no task list
|
|
20
|
-
runs `#<N>` as a `fullrun` and then finishes.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
**not** stop the run:
|
|
18
|
+
runs `#<N>` as a `fullrun` and then finishes. Inside `backlogrun #<N>`, a prerequisite or a split found
|
|
19
|
+
mid-run does **not** stop the run (rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why a
|
|
20
|
+
bare Issue promotes itself under `backlogrun` but not under `fullrun`"):
|
|
24
21
|
|
|
25
22
|
1. File the new Issue(s) with the matching route label — `route:split` for a split, `route:tier-a`
|
|
26
23
|
for a prerequisite — no confirmation.
|
|
@@ -28,9 +25,7 @@ had one Issue's authorization. So inside `backlogrun #<N>`, a prerequisite or a
|
|
|
28
25
|
"A prerequisite discovered mid-run" do — `git stash push -u -m "..."` with the `-u`, the
|
|
29
26
|
`gh api repos/{owner}/{repo}/issues/<N>/comments` post that records the stash, and
|
|
30
27
|
`gh api -X DELETE repos/{owner}/{repo}/issues/<N>/labels/in-progress 2>/dev/null || true`.
|
|
31
|
-
3. **Ask `pnpm josh epic:bundle <N>` whether an epic already tracks `#<N>` before creating one.**
|
|
32
|
-
names the epic rather than only reporting that one exists — creating a second epic over it gives
|
|
33
|
-
the auto-close two task lists to disagree about.
|
|
28
|
+
3. **Ask `pnpm josh epic:bundle <N>` whether an epic already tracks `#<N>` before creating one.**
|
|
34
29
|
|
|
35
30
|
| Answer | What to do |
|
|
36
31
|
| --- | --- |
|
|
@@ -48,8 +43,7 @@ had one Issue's authorization. So inside `backlogrun #<N>`, a prerequisite or a
|
|
|
48
43
|
| A split into independent children | `pnpm josh epic "<title>" <N> <N1> ...` — **no `--ordered`** |
|
|
49
44
|
| A split whose children do have an order | `pnpm josh epic "<title>" ... --ordered`, arguments in that order |
|
|
50
45
|
|
|
51
|
-
4. **Run `pnpm josh epic:audit <E>` now**, not earlier — there is no epic to audit until step 3
|
|
52
|
-
`epic:audit` refuses an Issue with no task list exactly as `epic:next` does.
|
|
46
|
+
4. **Run `pnpm josh epic:audit <E>` now**, not earlier — there is no epic to audit until step 3.
|
|
53
47
|
5. **Do not stop.** Continue into the loop below against the new epic `#<E>`.
|
|
54
48
|
|
|
55
49
|
**Nothing found means no epic.** If `#<N>` reaches its merge without a prerequisite or a split turning
|
|
@@ -73,7 +67,8 @@ reference is named, the refusal is the whole answer. Where several are, a task-l
|
|
|
73
67
|
## Each child runs in a delegated unit
|
|
74
68
|
|
|
75
69
|
**A child is not run in the parent loop's context.** One child goes to an isolated execution unit,
|
|
76
|
-
and only its summary comes back.
|
|
70
|
+
and only its summary comes back. Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why
|
|
71
|
+
each child runs in a delegated unit".
|
|
77
72
|
|
|
78
73
|
**The supervisor's driver is the mechanical parent after hand-off.** It uses the same lane dispatch
|
|
79
74
|
and GitHub verification commands; an AI parent handles only a returned judgment branch. *Every* child takes a
|
|
@@ -82,8 +77,8 @@ one sentence: **whatever offers a child — the loop, or a person clearing a lab
|
|
|
82
77
|
to a lane, never to the parent.**
|
|
83
78
|
|
|
84
79
|
**The mechanism is the one `pnpm josh delegate` defines** — the enumeration plus the command — with
|
|
85
|
-
the unit changed from one step of a run to one child of an epic.
|
|
86
|
-
|
|
80
|
+
the unit changed from one step of a run to one child of an epic. Never build a second. Ask the command
|
|
81
|
+
rather than deciding:
|
|
87
82
|
|
|
88
83
|
```bash
|
|
89
84
|
pnpm josh delegate epic-child # → delegate
|
|
@@ -91,9 +86,8 @@ pnpm josh delegate epic-child # → delegate
|
|
|
91
86
|
|
|
92
87
|
**In a lane, that unit is a detached operating-system process rather than a subagent of this
|
|
93
88
|
session.** The verifier is still `pnpm josh issue:state <N>` read from GitHub, and the summary is
|
|
94
|
-
still bounded at 25 lines.
|
|
95
|
-
|
|
96
|
-
carries the command.
|
|
89
|
+
still bounded at 25 lines. `pnpm josh lane:dispatch` is where a lane's child is started; "Handing the
|
|
90
|
+
child over" in `backlogrun-lanes.md` carries the command.
|
|
97
91
|
|
|
98
92
|
**The lane child uses the invoking CLI's `worker` profile.** Claude Code defaults to Anthropic
|
|
99
93
|
`claude-opus-5-5` / `medium`; Codex uses `codex exec`, OpenAI `gpt-6-sol` / `medium`, workspace-write and
|
|
@@ -109,11 +103,9 @@ loop on the summary alone** — that discards the verifier.
|
|
|
109
103
|
|
|
110
104
|
### What the summary carries, and how long it may be
|
|
111
105
|
|
|
112
|
-
**The summary's only job is to carry what GitHub does not.**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
recorded and keeps the file list anyone could have fetched. So both lists are written out, and the
|
|
116
|
-
brief hands them to the unit.
|
|
106
|
+
**The summary's only job is to carry what GitHub does not.** Both lists below are written out, and the
|
|
107
|
+
brief hands them to the unit. Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why the
|
|
108
|
+
summary is bounded the way it is".
|
|
117
109
|
|
|
118
110
|
**Always kept — five things, because none of them is anywhere else:**
|
|
119
111
|
|
|
@@ -134,26 +126,23 @@ brief hands them to the unit.
|
|
|
134
126
|
pull request's own file list), the per-round review detail (each round's verdict is one line; the
|
|
135
127
|
findings are on the pull request), and restatements of rules the parent already holds.
|
|
136
128
|
|
|
137
|
-
**The bound is 25 lines, and it is a number so that it is not a judgement.**
|
|
138
|
-
|
|
139
|
-
because a unit never told it writes to the length its own report format suggests.
|
|
129
|
+
**The bound is 25 lines, and it is a number so that it is not a judgement.** **The brief states the
|
|
130
|
+
bound.**
|
|
140
131
|
|
|
141
132
|
**This does not shorten the person-facing completion report.**
|
|
142
133
|
`prompts/collaboration-workflow/report-format.md` → 「完了報告(セッション向け)」 is unchanged. What is
|
|
143
|
-
bounded here is the child's hand-back to the parent
|
|
134
|
+
bounded here is the child's hand-back to the parent.
|
|
144
135
|
|
|
145
136
|
**Read the state directly rather than asking `epic:next` again.** A child that did not finish still
|
|
146
137
|
carries `in-progress`, which `epic:next` classifies as waiting on time before it consults any blocker.
|
|
147
138
|
|
|
148
|
-
**The merge authorization reaches the unit**, and **so does the explicit invocation**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
that omits it is the defect: the unit is then guessing, and refusing is the correct answer to a guess.
|
|
139
|
+
**The merge authorization reaches the unit**, and **so does the explicit invocation**. **The brief
|
|
140
|
+
therefore names the invocation it descends from** — `backlogrun #<E> --only`, the child number, and that
|
|
141
|
+
the child is to be run as `fullrun #<N>` under that authorization. A brief that omits it is the defect,
|
|
142
|
+
and the unit refuses it.
|
|
153
143
|
|
|
154
|
-
**Where no isolated unit exists, run the child in the parent's context.** The hand-off below is
|
|
155
|
-
at every merge; delegation
|
|
156
|
-
session's length, so it is not an alternative to it.
|
|
144
|
+
**Where no isolated unit exists, run the child in the parent's context.** The hand-off below is still
|
|
145
|
+
asked at every merge; delegation is not an alternative to it.
|
|
157
146
|
|
|
158
147
|
## A delegated unit that stopped without reporting
|
|
159
148
|
|
|
@@ -161,7 +150,7 @@ session's length, so it is not an alternative to it.
|
|
|
161
150
|
trips none of this file's guards, which all assume it is running. **So the parent checks rather than
|
|
162
151
|
waiting — which means it must not be waiting.** **Hand the child to the unit without blocking on its
|
|
163
152
|
return, and poll** at the loop's polling interval; ask once the unit's output has been unchanged for
|
|
164
|
-
the silent-unit window (`| Silent delegated unit | 30 min |`
|
|
153
|
+
the silent-unit window (`| Silent delegated unit | 30 min |` in `backlogrun-progress.md`).
|
|
165
154
|
|
|
166
155
|
**Note where the unit writes at hand-off; the modification time is read from the file, not carried.**
|
|
167
156
|
**Where the child runs in a lane, that note goes into the lane rather than the conversation** —
|
|
@@ -189,13 +178,11 @@ pnpm josh run:liveness <N> --output <path> --process none --window 45 --repo <ow
|
|
|
189
178
|
consecutive `undetermined` on the same child ends the polling: send a `confirmation` Telegram naming
|
|
190
179
|
the trace that failed, and stop. **It is never escalated to a `stopped`** — nothing was read.
|
|
191
180
|
|
|
192
|
-
**Silence and no process together — never either one alone
|
|
193
|
-
(a unit inside a long check writes nothing; a unit only reading has no check process) and together they
|
|
194
|
-
do not. **Which way an error falls is the whole design**: a live unit booked as stopped has its work
|
|
195
|
-
killed, a stopped one booked as alive only costs waiting — so **a trace that could not be read answers
|
|
181
|
+
**Silence and no process together — never either one alone.** **A trace that could not be read answers
|
|
196
182
|
`undetermined`, never `stopped`**, and a process trace nobody gave is an unasked question rather than
|
|
197
183
|
"no process". **Output that moved answers `alive` on its own; a live process is weighed only once every
|
|
198
|
-
trace has answered.**
|
|
184
|
+
trace has answered.** Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why liveness needs
|
|
185
|
+
silence and no process together".
|
|
199
186
|
|
|
200
187
|
**The path passed to `--output` is absolute** — the command refuses a relative one. **The process
|
|
201
188
|
trace is the one the command does not read for itself**: run it in the unit's checkout and read the
|
|
@@ -224,44 +211,34 @@ guard, and go back to step 1.
|
|
|
224
211
|
2. **Classify how the child ended and act, in one call** — `pnpm josh run:merge <N> --output <path>`
|
|
225
212
|
(add `--epic <E> --repo <owner/repo> --owner "$PPID"` for a named epic). **It is the same composite a
|
|
226
213
|
returned child takes** (`backlogrun-progress.md` → "Running a named epic's children"), reached here
|
|
227
|
-
from the poll rather than from a return —
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(joshuafolkken/kit#2240) — apart from an `abandoned` one that stopped mid-implementation, and it drops
|
|
214
|
+
from the poll rather than from a return — **one** decision, never a second copy of it. It **reads the
|
|
215
|
+
child's exit record without waiting for the unit to return**, telling an `outage` — a child that
|
|
216
|
+
could not reach the API — apart from an `abandoned` one that stopped mid-implementation, and it drops
|
|
231
217
|
the stale `in-progress` itself, so there is no separate label-removal step:
|
|
232
218
|
- **outage** — the child never reached the API, so it is counted into its own outage streak and
|
|
233
219
|
**re-dispatched in the same run** by being offered again, **not** parked with `needs-decision` and
|
|
234
220
|
**not** counted against the consecutive-failure guard. **The re-dispatch resumes the child's
|
|
235
|
-
session
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
past the fold window, as a dead API produces) trip the separate outage guard, at which point the
|
|
242
|
-
command prints `environment` and the run stops. It never re-dispatches into a dead API forever.
|
|
221
|
+
session**: `lane:dispatch` reads the `session_id` off the exit record and relaunches with
|
|
222
|
+
`--resume`; with no session id it falls back to a fresh `fullrun`, and the report says which path
|
|
223
|
+
it took. **Outages inside a two-minute window count once** toward the streak. The re-dispatch is
|
|
224
|
+
bounded by `CONSECUTIVE_OUTAGE_LIMIT` in `scripts/run/run-merge.ts`: *distinct* outages trip the
|
|
225
|
+
separate outage guard, at which point the command prints `environment` and the run stops. It never
|
|
226
|
+
re-dispatches into a dead API forever.
|
|
243
227
|
- **abandoned** — counted against the consecutive-failure guard and parked with `needs-decision`,
|
|
244
|
-
exactly as a failed child.
|
|
245
|
-
thing that notices the environment rather than the children is at fault.
|
|
228
|
+
exactly as a failed child. Never retried silently.
|
|
246
229
|
3. **Read the token it printed and take that branch** — a next child number to run (the re-dispatched
|
|
247
230
|
outage child among them), `environment` / `stop` to end the run, or `busy` / `retry` to re-read —
|
|
248
231
|
the same tokens the merge event reads (`backlogrun-progress.md` → "Running a named epic's children").
|
|
249
232
|
Then go back to step 1 of the loop.
|
|
250
233
|
|
|
251
|
-
|
|
252
|
-
re-dispatch cap and the park all live in one place, so the poll cannot drift from the return path:
|
|
253
|
-
counting the outage into its streak is what lets the cap trip, and skipping the count would
|
|
254
|
-
re-dispatch into a dead API without bound. Reading the exit record here — not waiting for a unit that
|
|
255
|
-
an API outage may never return — is what turns a 400-minute wait into a same-run re-dispatch
|
|
256
|
-
(joshuafolkken/kit#2277).
|
|
234
|
+
Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why the poll routes through `run:merge`".
|
|
257
235
|
|
|
258
236
|
## Audit before the first child
|
|
259
237
|
|
|
260
238
|
Run `pnpm josh epic:audit <E>` before step 1 below. **When the run began from a bare Issue there is
|
|
261
|
-
nothing to audit yet** — that path runs the audit at the moment it creates the epic instead.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
past. Fixing what it finds is Tier A.
|
|
239
|
+
nothing to audit yet** — that path runs the audit at the moment it creates the epic instead. Errors
|
|
240
|
+
stop the run; warnings are read and carried on past. Fixing what it finds is Tier A. Rationale:
|
|
241
|
+
`docs/maintainers/backlogrun-child-rationale.md` → "Why the audit runs before the first child".
|
|
265
242
|
|
|
266
243
|
## `josh latest` runs once per session, not once per child
|
|
267
244
|
|
|
@@ -280,65 +257,50 @@ On `required`, load the `dependency-update` skill and follow its procedure — t
|
|
|
280
257
|
`pnpm-workspace.yaml` and `package.json`, and the one expected `devEngines` pnpm bump. **The answer is
|
|
281
258
|
the command's, never a judgement**, and `latest-gate.md` is its single source.
|
|
282
259
|
|
|
283
|
-
**Session, not run** —
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
there.
|
|
287
|
-
|
|
288
|
-
**Waiting until a child is in hand is what keeps the tree clean.** Run it before the first `epic:next`
|
|
289
|
-
and a run whose first answer is `wait`, `stop` or `complete` leaves a rewritten `pnpm-lock.yaml`
|
|
290
|
-
modified on the default branch with nothing to commit it, which the next `git pull` refuses to merge
|
|
291
|
-
over.
|
|
260
|
+
**Session, not run** — each session runs one repository's children, so each updates its own checkout;
|
|
261
|
+
never skip it because another repository's session already ran it. **Never ask it before a child is
|
|
262
|
+
in hand.**
|
|
292
263
|
|
|
293
264
|
**The lock file the update rewrites lands with the first child.** The first child's `pnpm josh git -y`
|
|
294
|
-
commits it — so that one PR carries the dependency bumps
|
|
295
|
-
|
|
296
|
-
CodeRabbit read, whose CI failures are attributed to the child, and which miss the eslint cache key
|
|
297
|
-
(`hashFiles('pnpm-lock.yaml')`) on every PR. Should the first child fail CI on a bump, fix it forward
|
|
298
|
-
before parking it.
|
|
265
|
+
commits it — so that one PR carries the dependency bumps and the other children carry none. Should the
|
|
266
|
+
first child fail CI on a bump, fix it forward before parking it.
|
|
299
267
|
|
|
300
268
|
**`git switch main && git pull` stays per child** — it brings the previous child's merge into the tree,
|
|
301
269
|
and a child that skips it implements on a stale main. Only the dependency update moves to the run. **In
|
|
302
270
|
lanes it changes hands**: no lane can switch to the default branch, so the parent runs it in the primary
|
|
303
271
|
checkout **before each `lane:open`**. **And in a lane `josh latest` is not even asked** — `latest:scope`
|
|
304
|
-
skips and `latest:guard` refuses. The stash that carries the lock file into the first lane is in
|
|
305
|
-
per repository, before the first lane opens"
|
|
272
|
+
skips and `latest:guard` refuses. The stash that carries the lock file into the first lane is in
|
|
273
|
+
`backlogrun-lanes.md` → "Once per repository, before the first lane opens".
|
|
306
274
|
|
|
307
275
|
This is the same rule `latest-gate.md` is the single source of.
|
|
308
276
|
|
|
309
|
-
**A resumed `backlogrun` is a new session**, so it asks once again before its first child.
|
|
310
|
-
resumed session finds may be days old — a session resumed within the window is told `skip`, one resumed
|
|
311
|
-
a day later `required`.
|
|
277
|
+
**A resumed `backlogrun` is a new session**, so it asks once again before its first child.
|
|
312
278
|
|
|
313
|
-
|
|
279
|
+
Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why `josh latest` is hoisted to the
|
|
280
|
+
session".
|
|
314
281
|
|
|
315
|
-
|
|
316
|
-
it leaves is a **working tree**: a feature branch, an open pull request, uncommitted changes. The loop
|
|
317
|
-
opens every child with `git switch main && git pull`, which refuses over a dirty tree, while an agent
|
|
318
|
-
may not reach for `git stash` on its own judgement. So an unattended batch could not recover from its
|
|
319
|
-
own crash.
|
|
282
|
+
## Preflight — reclaim what an interrupted run left, before the next child starts
|
|
320
283
|
|
|
321
284
|
**The child's claim is what asks it.** `pnpm josh run:hold <N>` — the first call of the child's
|
|
322
|
-
`fullrun` procedure —
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
285
|
+
`fullrun` procedure — runs the preflight check before it takes the tree: it hands back a hold **only on
|
|
286
|
+
a clean tree**, and returns `reclaim` / `resume` / `park` without claiming otherwise. There is no
|
|
287
|
+
separate command to ask first. Rationale: `docs/maintainers/backlogrun-child-rationale.md` → "Why the
|
|
288
|
+
preflight is part of the claim".
|
|
326
289
|
|
|
327
290
|
```bash
|
|
328
291
|
answer=$(pnpm josh run:hold 926) # alias: josh rh ; one token on stdout, prose on stderr
|
|
329
292
|
```
|
|
330
293
|
|
|
331
|
-
| Answer
|
|
332
|
-
|
|
|
333
|
-
| `hold`
|
|
334
|
-
| `reclaim` | Uncommitted changes, or HEAD off the default branch
|
|
335
|
-
| `resume`
|
|
336
|
-
| `park`
|
|
337
|
-
| `busy`
|
|
338
|
-
| `unknown` | The tree could not be read
|
|
294
|
+
| Answer | What it found | What to do |
|
|
295
|
+
| --- | --- | --- |
|
|
296
|
+
| `hold` | Nothing left behind; the tree is claimed | Start the child. This is the ordinary answer. |
|
|
297
|
+
| `reclaim` | Uncommitted changes, or HEAD off the default branch | Run what stderr printed: `git stash push -u -m "run:hold reclaimed before #<N>"`, then `git switch <default> && git pull`. **`-u` is not optional** (a new `*.test.ts` is untracked). **Then record the stash on `#<N>` as a comment, and ask `run:hold` again** — the check is re-askable. |
|
|
298
|
+
| `resume` | A branch for `#<N>`, or an open pull request, is still here | **Reuse it and run the whole verification gate from the start** — never only the part the interrupted run had not reached. The tools are idempotent; what was missing was that **nobody had verified what the dead run already committed**. |
|
|
299
|
+
| `park` | The pull request for `#<N>` is **merged** or **closed** | **Park the child** — `needs-decision` plus a comment naming what was found — and continue. Carrying on over a merged PR duplicates work; over a closed one revives rejected work. Do not delete the branch, reopen the PR, or commit on top of it. |
|
|
300
|
+
| `busy` | Another **live** run holds this tree | Stop before filing anything — `run:hold`'s own answer, unchanged. |
|
|
301
|
+
| `unknown` | The tree could not be read | Stop the session and report. It is not "the tree is clean". |
|
|
339
302
|
|
|
340
|
-
**The rule answers, so the run does not judge.**
|
|
341
|
-
branch, I had better stop" are both defensible in the moment, which is why the choice is not left to it.
|
|
303
|
+
**The rule answers, so the run does not judge.**
|
|
342
304
|
|
|
343
305
|
**The `git stash` above is one of the flows that authorizes automatic stashing**
|
|
344
306
|
(`prompts/collaboration-workflow/operating-rules.md` → the `git stash` bullet). **It is the one entry
|
|
@@ -350,15 +312,8 @@ divert.
|
|
|
350
312
|
**Report what was reclaimed** — a child that started from a `reclaim` or a `resume` says so, with the
|
|
351
313
|
stash reference where there was one.
|
|
352
314
|
|
|
353
|
-
**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
**It is not asked in a lane.** Every lane's HEAD is on `<N>-lane`, so the `reclaim` arm would fire on
|
|
360
|
-
all of them and its recovery cannot run in a linked work tree — so `run:hold` claimed inside a lane
|
|
361
|
-
skips the check, and `lane:open`'s own answer covers an interrupted lane instead. **Asked in the
|
|
362
|
-
primary checkout while a lane for that child is open, `run:hold` would read `resume`, which is
|
|
363
|
-
correct** — the branch read `git branch --list '<N>-*'` matches `<N>-lane`. The loop never reaches it
|
|
364
|
-
that way, because `epic:next` does not offer a child already carrying `in-progress`.
|
|
315
|
+
**It is not asked in a lane** — `run:hold` claimed inside a lane skips the check, and `lane:open`'s own
|
|
316
|
+
answer covers an interrupted lane instead. **Asked in the primary checkout while a lane for that child
|
|
317
|
+
is open, `run:hold` would read `resume`, which is correct** — the branch read
|
|
318
|
+
`git branch --list '<N>-*'` matches `<N>-lane`. The loop never reaches it that way, because `epic:next`
|
|
319
|
+
does not offer a child already carrying `in-progress`.
|