@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
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**Read this file in full before the first lane opens** — in the turn that reaches
|
|
4
4
|
`pnpm josh lane:open`. It is a point-of-use document, never an entry read: the entry procedure is
|
|
5
|
-
`backlogrun.md`, which points here at that step
|
|
6
|
-
|
|
7
|
-
is resolved.
|
|
5
|
+
`backlogrun.md`, which points here at that step. This file is the single source of the per-repository
|
|
6
|
+
lane ceiling, the lane lifecycle, and how a merge conflict between lanes is resolved.
|
|
8
7
|
|
|
9
8
|
## Concurrency: as many children per repository as it has free lanes
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
repository's children.
|
|
10
|
+
**An `backlogrun` need not be a single session.** One session per repository; each calls
|
|
11
|
+
`josh epic:next <E> --repo <owner/repo>` and runs only its own repository's children.
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
# In the kit checkout
|
|
@@ -26,7 +24,7 @@ kit's issue does not publish kit, so such a dependency resolves only once the bl
|
|
|
26
24
|
its release has appeared in the registry — and while the blocker is still open the registry is never
|
|
27
25
|
consulted. **Unless that repository publishes nothing** — no `package.json` on its default branch, or
|
|
28
26
|
one declaring `private` — in which case a closed blocker there resolves. The answer is read from the
|
|
29
|
-
blocker repository's manifest, never from the registry
|
|
27
|
+
blocker repository's manifest, never from the registry.
|
|
30
28
|
|
|
31
29
|
**The lane count is per repository, and `epic:next` is what applies it.** When it has a child to offer,
|
|
32
30
|
it first asks that repository **how many of its lanes are already running something**: every open issue
|
|
@@ -34,29 +32,23 @@ carrying `in-progress` and not parked counts for one — whichever epic it belon
|
|
|
34
32
|
`JOSH_LANE_LIMIT` (**default 6**) is what gets offered, and at zero the answer is `wait`. It is asked
|
|
35
33
|
**only when there is a candidate**. A **parked** issue does not hold a lane: `needs-decision` outranks
|
|
36
34
|
`in-progress` here exactly as in the classification. A child stopped by `needs-human-review` is
|
|
37
|
-
deliberately not parked and goes on holding its lane
|
|
38
|
-
checkout.
|
|
35
|
+
deliberately not parked and goes on holding its lane.
|
|
39
36
|
|
|
40
37
|
**It is advisory and not atomic.** The label is applied when a child is dispatched — the parent claims
|
|
41
38
|
it in `lane:dispatch` before the child process starts, and a standalone `fullrun` / `halfrun` claims it
|
|
42
|
-
the moment `run:hold` answers `hold` — *after* this read
|
|
43
|
-
can both read the same free lane. What the check closes is the window that actually occurs — a
|
|
44
|
-
dispatched lane holds the label from the launch onward, not the tens of minutes it took when the
|
|
45
|
-
child's own `fullrun` had to reach its own apply first. It is a guard that makes the invariant
|
|
46
|
-
mechanical, not a mutex.
|
|
39
|
+
the moment `run:hold` answers `hold` — *after* this read. It is a guard, not a mutex.
|
|
47
40
|
|
|
48
41
|
**It is scoped to the resource, not the epic.** A lane is its own checkout with its own branch and
|
|
49
42
|
ports, so the repository-wide number is a **ceiling on how many lanes run at once**. How a session
|
|
50
43
|
drives more than one child at a time is "Lanes — running more than one child at a time" below.
|
|
51
44
|
|
|
52
|
-
**A stale label holds a lane, so the stale rule reaches past this epic's own children** —
|
|
53
|
-
is removed by whoever finds it stale"
|
|
45
|
+
**A stale label holds a lane, so the stale rule reaches past this epic's own children** — `backlogrun-park.md` →
|
|
46
|
+
"`in-progress` is removed by whoever finds it stale" applies to **any** open issue in the repository. `epic:next`
|
|
54
47
|
names the holders on standard error, and the 90-minute stale window bounds the wait.
|
|
55
48
|
|
|
56
49
|
**A listing it could not read is not an idle repository.** `epic:next` answers `wait` there rather than
|
|
57
|
-
offering the child
|
|
58
|
-
|
|
59
|
-
short one with no visible holder is `wait`, with its own message.
|
|
50
|
+
offering the child. **A listing that was *cut short* is the same answer**: a short one with no visible
|
|
51
|
+
holder is `wait`, with its own message.
|
|
60
52
|
|
|
61
53
|
**Two children of one repository may run at once, and the section below is how.** The guard is a
|
|
62
54
|
ceiling rather than a prohibition, scoped to the lane rather than to the epic. **Do not read this as
|
|
@@ -66,6 +58,9 @@ switching the guard off is not one of the ways to get parallelism.
|
|
|
66
58
|
Parallelism only helps children that do not depend on each other. When app-kit's child needs kit's new
|
|
67
59
|
feature, that is recorded as `blocked-by` and `epic:next` makes it wait.
|
|
68
60
|
|
|
61
|
+
Rationale: `docs/maintainers/backlogrun-lanes-rationale.md` → "Why the lane ceiling is shaped the way
|
|
62
|
+
it is".
|
|
63
|
+
|
|
69
64
|
## Lanes — running more than one child at a time
|
|
70
65
|
|
|
71
66
|
`pnpm josh epic:next <E> --repo <owner/repo> --lanes` answers with **one issue number per line**, up to
|
|
@@ -85,41 +80,37 @@ number (`pre-gate-cut.md` → "The dispatch mark" and "The stage is passed to th
|
|
|
85
80
|
verification path itself.** It runs alone, and the batch resumes only once it has merged. **Decide it
|
|
86
81
|
from the enumeration, never from how serious it looks** — does the defect reach the verification gate
|
|
87
82
|
(lint / type check / spell check / unit tests), the code review, the pre-push hook, or the merge
|
|
88
|
-
checks? One of those, and the
|
|
89
|
-
|
|
83
|
+
checks? One of those, and the issue carries `run:solo`; none, and it carries `run:lane` and fills a
|
|
84
|
+
lane like any other child. `backlog:next` and `epic:next --lanes` enforce both (joshuafolkken/kit#2776,
|
|
85
|
+
#2779). The rule is
|
|
90
86
|
`prompts/collaboration-workflow/wip-cap.md` → 「実行のしかた」, its single source.
|
|
91
87
|
|
|
92
88
|
**A lane's review does not inherit the lane, and `pnpm josh review:brief` is what closes that.**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`pnpm josh followup` asks again before it merges; `missing` and `mismatch` are both refusals. A clean
|
|
101
|
-
round is therefore the case to check hardest, not the case to skip the check on.
|
|
89
|
+
`pnpm josh review:brief` prints the lane's absolute root, branch and HEAD, hands over targets written
|
|
90
|
+
`git -C <root> …`, and prints a nonce the review attests with `pnpm josh review:attest <nonce>` from
|
|
91
|
+
the checkout it actually read. **The child asks `pnpm josh review:attest --check` before it counts a
|
|
92
|
+
round**, and `pnpm josh followup` asks again before it merges; `missing` and `mismatch` are both
|
|
93
|
+
refusals. A clean round is the case to check hardest, not the case to skip the check on. Rationale:
|
|
94
|
+
`docs/maintainers/backlogrun-lanes-rationale.md` → "Why a lane's review needs a brief and an
|
|
95
|
+
attestation".
|
|
102
96
|
|
|
103
97
|
**A lane's branch is `<N>-lane`, and the issue number leads it so that the commit path accepts it.**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
the lane
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
overlap between children real, and every overlap that becomes a conflict costs the child that loses the
|
|
113
|
-
race a resolution, a re-run gate and a review ("Conflicts are not predicted" below), and parks it
|
|
114
|
-
outright under that section's four conditions. Report the throughput gain only with its merge-race
|
|
115
|
-
cost.
|
|
98
|
+
**Never switch the lane to another branch** — a switched lane loses its seat, its listing and its
|
|
99
|
+
isolation. So `epic:next` is asked **with** `--lanes`, and everything below runs as written. Rationale:
|
|
100
|
+
`docs/maintainers/backlogrun-lanes-rationale.md` → "Why the lane branch is `<N>-lane` and never
|
|
101
|
+
switched".
|
|
102
|
+
|
|
103
|
+
**Report the throughput gain only with its merge-race cost** — every overlap that becomes a conflict
|
|
104
|
+
costs a resolution, a re-run gate and a review ("Conflicts are not predicted" below). Rationale:
|
|
105
|
+
`docs/maintainers/backlogrun-lanes-rationale.md` → "Why lanes cost as well as buy throughput".
|
|
116
106
|
|
|
117
107
|
### Once per repository, before the first lane opens
|
|
118
108
|
|
|
119
109
|
In the **primary checkout**, in this order, and never again per lane:
|
|
120
110
|
|
|
121
111
|
1. `git switch main && git pull` — every lane branches from this ref.
|
|
122
|
-
2. `pnpm josh latest:scope`, and the update on `required` — "`josh latest`
|
|
112
|
+
2. `pnpm josh latest:scope`, and the update on `required` — `backlogrun-child.md` → "`josh latest`
|
|
113
|
+
runs once per session, not once per child".
|
|
123
114
|
3. `pnpm josh lane:prune` — closes the lanes an interruption left registered without a work tree.
|
|
124
115
|
4. `pnpm josh run:tidy` — sweeps merged lanes and stashes (as `run:hold` does).
|
|
125
116
|
|
|
@@ -141,10 +132,9 @@ is a repository-wide stack every lane shares, so a positional pop would take whi
|
|
|
141
132
|
|
|
142
133
|
### Opening one lane and dispatching its child
|
|
143
134
|
|
|
144
|
-
**One command opens the lane, prepares it, and dispatches the child — `pnpm josh lane:launch
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`lane:dispatch`. Four to six turns of the parent folded into one:
|
|
135
|
+
**One command opens the lane, prepares it, and dispatches the child — `pnpm josh lane:launch`.** It
|
|
136
|
+
runs `lane:open`, then — only when `--stash` is given, which is the first lane alone — pops that stash
|
|
137
|
+
into the lane and re-installs against the lock it brought in, then `lane:dispatch`:
|
|
148
138
|
|
|
149
139
|
```bash
|
|
150
140
|
pid=$(pnpm josh lane:launch "$n") || exit 1 # the child's pid on stdout, nothing else; alias: josh lnla
|
|
@@ -153,6 +143,8 @@ pid=$(pnpm josh lane:launch "$n" --stash "backlogrun: josh latest before lanes")
|
|
|
153
143
|
|
|
154
144
|
`lane:launch` is a thin layer over `lane:open`, `stash:pop` and `lane:dispatch`, so each of their
|
|
155
145
|
guards, refusals and messages holds unchanged; the bullets below describe the steps it runs.
|
|
146
|
+
Rationale: `docs/maintainers/backlogrun-lanes-rationale.md` → "Why `lane:launch` folds the lane steps
|
|
147
|
+
into one".
|
|
156
148
|
|
|
157
149
|
- **A refusal is an empty capture beside a non-zero exit**, with the reason on standard error: `full`,
|
|
158
150
|
`already-open`, and a **failed install** — which leaves a real work tree behind holding its seat, so
|
|
@@ -162,21 +154,15 @@ guards, refusals and messages holds unchanged; the bullets below describe the st
|
|
|
162
154
|
dispatched**.
|
|
163
155
|
- **`lane:open` installs; the `--stash` lane re-installs, and only that lane needs it.** A failed
|
|
164
156
|
install fails `lane:open`, so `lane:launch` reaching `lane:dispatch` is the guarantee the lane runs.
|
|
165
|
-
|
|
166
|
-
the `pnpm-lock.yaml` that `josh latest` rewrote, which this child's gate must build against. **A pop
|
|
167
|
-
that fails stops the lane** rather than re-installing anyway.
|
|
157
|
+
**A pop that fails stops the lane** rather than re-installing anyway.
|
|
168
158
|
- **A `<N>-lane` that already exists is attached to, and `lane:open` says so on standard error** — the
|
|
169
159
|
way back to a child parked after it pushed: a local branch of that name gets the work tree put on it,
|
|
170
160
|
and where there is none the **remote** is asked (`ls-remote`, fetched first) and the lane cut from
|
|
171
161
|
`origin/<N>-lane` where the branch survives — otherwise from the default branch. **A reused branch is
|
|
172
162
|
not a fresh lane.** Nothing deletes a branch to open a lane, so a reopen cannot cost the pushed work.
|
|
173
|
-
- **Nothing switches the lane's branch
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- **`pnpm josh run:hold`'s preflight check is not asked in a lane; `lane:open`'s own answer replaces it.** A fresh
|
|
177
|
-
lane is clean by construction, preflight's `reclaim` arm (HEAD != default branch) is true of every
|
|
178
|
-
lane, and its recovery (`git switch <default>`) cannot run in a linked work tree. What a leftover
|
|
179
|
-
looks like here is `lane:open` answering `already-open`.
|
|
163
|
+
- **Nothing switches the lane's branch.**
|
|
164
|
+
- **`pnpm josh run:hold`'s preflight check is not asked in a lane; `lane:open`'s own answer replaces it.**
|
|
165
|
+
What a leftover looks like here is `lane:open` answering `already-open`.
|
|
180
166
|
- **`pnpm josh run:hold` is unchanged, and is claimed inside the lane** — it keys on
|
|
181
167
|
`git rev-parse --absolute-git-dir` (`.git/worktrees/<name>`), so each lane holds independently.
|
|
182
168
|
|
|
@@ -191,27 +177,25 @@ final step, the `lane:dispatch` it runs after the lane is opened and prepared, w
|
|
|
191
177
|
The bullets below describe that dispatch:
|
|
192
178
|
|
|
193
179
|
- **It records where the child writes as it starts it** — the dispatch's own
|
|
194
|
-
`<temp>/josh-lane-dispatch-<N>.log
|
|
195
|
-
has to except.
|
|
180
|
+
`<temp>/josh-lane-dispatch-<N>.log`.
|
|
196
181
|
- **A refusal is an empty capture beside a non-zero exit**, and every refusal also sends a `warning`
|
|
197
182
|
Telegram — no lane open, a path that could not be recorded, a launch that failed. **Park that child
|
|
198
183
|
and name the log the message carries**; do not re-dispatch into the same lane without reading it.
|
|
199
184
|
- **A child that started with nowhere to write exits zero and warns.** The launcher starts the session
|
|
200
185
|
even when it could not open the log, so the child really is running. **Poll that lane on the process
|
|
201
186
|
trace alone**, because its log will never grow.
|
|
202
|
-
- **The
|
|
203
|
-
|
|
204
|
-
`backlogrun` covers the child.
|
|
187
|
+
- **The child is started as `fullrun #<N>` with the lane as its working directory.** The batch a
|
|
188
|
+
person approved by typing `backlogrun` covers the child.
|
|
205
189
|
- **It does not pass `--dangerously-skip-permissions`.** What the child may do is the checkout's own
|
|
206
190
|
`.claude/settings.json` to decide; a headless child reaches `gh` and merges through `pnpm josh
|
|
207
191
|
followup` without the flag, and the run reports what stopped rather than loosening it.
|
|
208
192
|
|
|
209
|
-
**Start each child without blocking on it, and poll them all.** That is "A
|
|
210
|
-
without reporting"
|
|
211
|
-
alive` is read **in that child's lane**. **Run
|
|
212
|
-
first and pass what it found** — `alive` where the
|
|
213
|
-
`alive` merely because the child was dispatched. The
|
|
214
|
-
keeps `#12` from matching a running `#123`.
|
|
193
|
+
**Start each child without blocking on it, and poll them all.** That is `backlogrun-child.md` → "A
|
|
194
|
+
delegated unit that stopped without reporting" applied N times, and `pnpm josh run:liveness <N>
|
|
195
|
+
--output <path> --process alive` is read **in that child's lane**. **Run
|
|
196
|
+
`pgrep -laf "(fullrun|run-ship-cli\.ts .*) #<N>$"` first and pass what it found** — `alive` where the
|
|
197
|
+
child is there, `none` where it is not, and never `alive` merely because the child was dispatched. The
|
|
198
|
+
command line carries no path; the `$` anchor keeps `#12` from matching a running `#123`.
|
|
215
199
|
|
|
216
200
|
**`git switch main && git pull` is the parent's now, not the child's.** No lane can switch to the
|
|
217
201
|
default branch, so the refresh moves to the primary checkout **before each `lane:open`** — the ref the
|
|
@@ -231,37 +215,28 @@ reports it: a pull request that conflicts with its base comes back `mergeStateSt
|
|
|
231
215
|
ends that child with a named conflict in about ten seconds.
|
|
232
216
|
|
|
233
217
|
**That child does not stop: it resolves the conflict in its own lane.** It is **not** counted against
|
|
234
|
-
the consecutive-failure guard either way
|
|
235
|
-
lanes.
|
|
218
|
+
the consecutive-failure guard either way.
|
|
236
219
|
|
|
237
|
-
**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
rewrite pushed commits and need a force push, which this package denies.
|
|
220
|
+
**The merge direction is `origin/main` into the lane's branch, never a rebase**, and no step below
|
|
221
|
+
rewrites a pushed commit. Rationale: `docs/maintainers/backlogrun-lanes-rationale.md` → "Why a conflict
|
|
222
|
+
is resolved in the lane rather than handed to a person".
|
|
241
223
|
|
|
242
224
|
1. **Resolve in the lane.** `git fetch origin main`, merge `origin/main` into the lane's branch, resolve
|
|
243
|
-
in place. Do not close the lane, open another, or switch its branch.
|
|
244
|
-
reattach to a pushed branch**, so a lane closed by mistake here is recoverable; staying put is still
|
|
245
|
-
what keeps the run clear of the question.
|
|
225
|
+
in place. Do not close the lane, open another, or switch its branch.
|
|
246
226
|
2. **Re-run the whole gate.** The tree changed, so the green recorded before the conflict is void:
|
|
247
227
|
`pnpm josh lint:related` and `pnpm josh test:related`, then `pnpm josh gate`.
|
|
248
228
|
3. **Review the resolution, one round.** Brief it with `pnpm josh review:brief` and run `/code-review`
|
|
249
229
|
over the resolution diff, then require `pnpm josh review:attest --check` to answer `ok`. That round
|
|
250
230
|
is a different subject and does not spend one of the two the cap allows — `prompts/review.md` →
|
|
251
231
|
"Review round cap" carries the exception.
|
|
252
|
-
4. **Conclude the merge and push it, with `pnpm josh git -y
|
|
253
|
-
`origin
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
only sanctioned way. **Record the resolution on the Issue in this same step** — a comment naming the
|
|
257
|
-
conflicting paths — because condition 3 counts resolutions off the Issue comments, not off memory.
|
|
232
|
+
4. **Conclude the merge and push it, with `pnpm josh git -y`** — the only sanctioned way; without it
|
|
233
|
+
nothing changes on `origin` and condition 3 parks the child for good. **Record the resolution on the
|
|
234
|
+
Issue in this same step** — a comment naming the conflicting paths — because condition 3 counts
|
|
235
|
+
resolutions off the Issue comments, not off memory.
|
|
258
236
|
5. **Merge**, by re-running `pnpm josh followup` exactly as before.
|
|
259
237
|
|
|
260
|
-
**The safeguard is the re-run verification, not who holds the pen
|
|
261
|
-
|
|
262
|
-
the work — so routing the decision to a person does not address it, while re-running the gate and the
|
|
263
|
-
review does. **In this repository routing it to a person does not even resolve it**: the user does not
|
|
264
|
-
read code, so a code-level conflict handed over is a deferral rather than a decision.
|
|
238
|
+
**The safeguard is the re-run verification, not who holds the pen** — a conflict the run can resolve is
|
|
239
|
+
not routed to a person.
|
|
265
240
|
|
|
266
241
|
**The run steps back under these four conditions, and under no others. The list is exhaustive and
|
|
267
242
|
carries no judgement.**
|
|
@@ -292,38 +267,27 @@ to abort.
|
|
|
292
267
|
| The child **merged** | `pnpm josh lane:close <N>` | `followup` released the hold and the branch is on `main`; nothing in that tree is wanted |
|
|
293
268
|
| The child was **parked before its commit** | `git -C <dir> stash push -u -m "backlogrun: parked #<N>"`, record it on the Issue, then `pnpm josh lane:close <N>` | The stash is a repository-level ref, so it outlives the work tree — and `lane:close` is a **forced** removal that would otherwise take the work. `epic:next` counts a parked child's lane as released, so a lane left open holds a seat the count believes is free |
|
|
294
269
|
| The child was **parked after its commit and push** | **Left open**, its directory and held seat recorded on the Issue | The stash step is a **no-op** (tree clean because committed). What `lane:close` would take is the **local branch**, which is the resume path |
|
|
295
|
-
| The child hit a **merge conflict** | **Left open** — the resolution happens in it | The child resolves in place ("Conflicts are not predicted").
|
|
270
|
+
| The child hit a **merge conflict** | **Left open** — the resolution happens in it | The child resolves in place ("Conflicts are not predicted"). A park under that section's four conditions takes the row above |
|
|
296
271
|
| The child stopped on **`needs-human-review`** | **Left open and untouched** | The uncommitted work *is* the artifact a person has to look at. Name the lane directory in the stop report and in the Telegram |
|
|
297
272
|
| The child **failed** | Whichever of the two parked rows applies, plus the consecutive-failure count | Same reasoning; only the counter differs. **A merge conflict is not this row** — it takes the row above, and it is not counted |
|
|
298
273
|
| **`lane:open` failed on the install** | `pnpm josh lane:close <N>`, then park the child | A lane exists that no `pnpm josh …` runs in and the next `lane:open` answers `already-open`. Closing frees the seat; parking is right because the cause is one a person fixes. Carry pnpm's reason into the park note |
|
|
299
274
|
| The run was **interrupted** | Nothing to do — the work tree survives on disk | The next session's `pnpm josh lane:prune` closes what git no longer has a tree for. **Park that child**, naming the lane and `pnpm josh lane:close <N>` as the way out |
|
|
300
275
|
|
|
301
|
-
**A
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
there, and the resume is one command.
|
|
307
|
-
|
|
308
|
-
**What is no longer true is that closing it is unrecoverable.** `pnpm josh lane:open <N>` now attaches
|
|
309
|
-
to a `<N>-lane` that exists locally and creates one from `origin/<N>-lane` where only the remote has it,
|
|
310
|
-
saying on standard error which it reused. So a lane closed after a push is reopened rather than lost.
|
|
311
|
-
**The branch still has to have reached the remote for that to hold** — a child parked before its commit
|
|
312
|
-
has nothing on `origin`, which is why that row stashes first and this one does not.
|
|
276
|
+
**A lane closed after a push is reopened by `pnpm josh lane:open <N>`**, which attaches to a local
|
|
277
|
+
`<N>-lane` or creates one from `origin/<N>-lane`, saying on standard error which it reused. **The branch
|
|
278
|
+
still has to have reached the remote for that to hold** — a child parked before its commit has nothing
|
|
279
|
+
on `origin`, which is why that row stashes first and the after-push row does not. Rationale:
|
|
280
|
+
`docs/maintainers/backlogrun-lanes-rationale.md` → "Why a committed child's lane is kept".
|
|
313
281
|
|
|
314
282
|
**Which row a child takes is decided by what `followup` printed, not by reading the situation.** **What
|
|
315
283
|
it prints is `PR checks failed (merge conflict)`** — that string is what reaches you;
|
|
316
284
|
`mergeStateStatus: DIRTY` is the internal spelling `git-pr-checks-eval.ts` compares against, never an
|
|
317
285
|
output line to search for. That string sends the child to resolve the conflict in the lane it is
|
|
318
286
|
standing in. Do not grep the output for `mergeable_state`: that is the REST field name, normalized away
|
|
319
|
-
before anything prints it.
|
|
320
|
-
table.**
|
|
287
|
+
before anything prints it.
|
|
321
288
|
|
|
322
|
-
**A kept lane holds its seat
|
|
323
|
-
|
|
324
|
-
is `epic:next`'s — it counts a parked child as having released its lane — and it costs one child offered
|
|
325
|
-
that cannot get a lane, which is visible and recoverable. **Say it in the park comment**: the lane
|
|
326
|
-
directory, that its seat is held, and `pnpm josh lane:close <N>` as the way to give it back.
|
|
289
|
+
**A kept lane holds its seat. Say it in the park comment**: the lane directory, that its seat is held,
|
|
290
|
+
and `pnpm josh lane:close <N>` as the way to give it back.
|
|
327
291
|
|
|
328
292
|
**Release the hold on either parked arm, and leave the lane before closing it.** `pnpm josh followup`
|
|
329
293
|
releases the working-tree hold at the **merge**, so a parked child's is still held: run
|
|
@@ -334,13 +298,12 @@ so removing the tree strands it. And `cd` out of the lane **before** `pnpm josh
|
|
|
334
298
|
close removes the directory the shell is sitting in.
|
|
335
299
|
|
|
336
300
|
**A `needs-human-review` stop ends the run, and the lanes already in flight are allowed to finish.** No
|
|
337
|
-
new lane is opened,
|
|
338
|
-
|
|
301
|
+
new lane is opened, and no unit is killed mid-gate. When the others have merged or parked, report and
|
|
302
|
+
stop.
|
|
339
303
|
|
|
340
304
|
### CI concurrency
|
|
341
305
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
`checks-wait` that grows with the lane count is the queueing.
|
|
306
|
+
**`JOSH_LANE_LIMIT` lowers the ceiling with no code** when CI jobs start to queue. Whether queueing
|
|
307
|
+
bites is read from the multi-day backlog timing report — a `checks-wait` that grows with the lane count
|
|
308
|
+
is the queueing. Rationale: `docs/maintainers/backlogrun-lanes-rationale.md` → "Why CI concurrency is
|
|
309
|
+
capped by the lane limit".
|