@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
|
@@ -56,7 +56,9 @@ Each step is a terse trigger and a pointer; the procedure is read on demand from
|
|
|
56
56
|
opted-in pool, a run's own filings once bundled, and the brake (`--max`, ten filings, the WIP cap)
|
|
57
57
|
that bounds the amount. `auto-ok` is a person's alone.
|
|
58
58
|
4. **Report the plan before the first child** — `pnpm josh backlog:plan`, then resolve every
|
|
59
|
-
`needs-decision` issue decidable from its body in one pass.
|
|
59
|
+
`needs-decision` issue decidable from its body in one pass. In the same pass, record `blocked-by`
|
|
60
|
+
between issues that must land in order, and apply `run:solo` where the verification-path
|
|
61
|
+
enumeration holds, `run:lane` elsewhere. `backlogrun-steps.md` → "The plan,
|
|
60
62
|
before the first child starts" and its "Resolve what the plan can resolve, before starting".
|
|
61
63
|
5. **Named issues run first, in order** — `backlogrun-steps.md` → "Named issues run first, in order":
|
|
62
64
|
the supervisor's driver dispatches each as a delegated `fullrun`, one at a time, then drains the
|
|
@@ -58,6 +58,9 @@ What one invocation does, in order:
|
|
|
58
58
|
A run that stopped at the merge therefore has no Issue comment, and the missing comment is what a
|
|
59
59
|
failed merge looks like from GitHub.
|
|
60
60
|
- **Closes the epics the Issue completes**, on a merged run only.
|
|
61
|
+
- **Confirms the Issue itself closed**, on a merged run only. GitHub has left merged runs' Issues
|
|
62
|
+
open despite `closes #N` (joshuafolkken/kit#2770), so an Issue still open a few seconds after the
|
|
63
|
+
merge is closed with a comment saying why, and the repair is printed as a warning.
|
|
61
64
|
- **Takes the `in-progress` label back off the Issue**, on a merged run only. A run that merged nothing
|
|
62
65
|
keeps it. The removal reads the Issue's labels and sends back the spelling GitHub stored, so an
|
|
63
66
|
`In-Progress` repository is not missed, and an Issue that never carried the label is never written to.
|
|
@@ -58,11 +58,10 @@ call reports `hold`.
|
|
|
58
58
|
ambient surface is the run's event stream, followed identically before and after a cut, so nothing
|
|
59
59
|
about the reporter moves when execution hands off (`backlogrun-progress.md` → "The invariant is a
|
|
60
60
|
tier, not a mechanism").
|
|
61
|
-
- **A
|
|
62
|
-
`resume` it skips the title, plan, hold and implementation and goes to the gate. At the pre-gate
|
|
61
|
+
- **A lane child asks whether it is a resume first — `pnpm josh run:cut --resume <N>`** (`run:entry` asks it
|
|
62
|
+
before the hold, joshuafolkken/kit#2760); on `resume` it skips the title, plan, hold and implementation and goes to the gate. At the pre-gate
|
|
63
63
|
boundary — immediately after `pnpm josh main:merge`, before the gate — it takes the cut with
|
|
64
|
-
`pnpm josh run:cut <N>` (the ordered step in `chain-rule.md
|
|
65
|
-
rather than after a refusal), and it records any park on the Issue before the stop notify.
|
|
64
|
+
`pnpm josh run:cut <N>` (the ordered step in `chain-rule.md`), and it records any park on the Issue before the stop notify.
|
|
66
65
|
`pre-gate-cut.md` is the single source of both.
|
|
67
66
|
|
|
68
67
|
## The stop branches
|
|
@@ -254,37 +254,43 @@ it — and seven lines had never left a working tree. **So the repeat count belo
|
|
|
254
254
|
that is empty on every other machine**, and every sighting was a first one, which is the state
|
|
255
255
|
joshuafolkken/kit#1728 created the ledger to end.
|
|
256
256
|
|
|
257
|
-
- **
|
|
258
|
-
`
|
|
259
|
-
point goes through (`scripts/git/git-staging.ts`), so
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
257
|
+
- **A run's appended lines ride its own commit** (joshuafolkken/kit#2763). `pnpm josh git` stages
|
|
258
|
+
`docs/maintainers/observations.md` with the run's other changes, in the one staging step every entry
|
|
259
|
+
point goes through (`scripts/git/git-staging.ts`), so the lines are reviewed and merged with the
|
|
260
|
+
pull request of the run that recorded them, and the run's CI is the only wait they cost. Until
|
|
261
|
+
#2763 the ledger was excluded there (joshuafolkken/kit#1756) and every run that appended a line
|
|
262
|
+
paid a second branch, CI wait and merge after its own — 144 ledger-only pull requests in one month.
|
|
263
|
+
**Record before the commit, not after the merge:** a finding recorded with `pnpm josh review:record`
|
|
264
|
+
before `pnpm josh git -y` is carried; the time while CI runs is for the records that need no CI
|
|
265
|
+
result — filing an observation Issue, drafting the completion report.
|
|
266
|
+
- **A line that breaks the grammar is not carried** (joshuafolkken/kit#2123). The staging step parses
|
|
267
|
+
the ledger first — after moving lines still on the old path (joshuafolkken/kit#2724) — and on a
|
|
268
|
+
broken line leaves the ledger out of the commit and says so; the commit itself goes ahead.
|
|
269
|
+
- **What no run's commit carried is flushed as a pull request of its own** — `pnpm josh
|
|
270
|
+
observations:flush`: a lane's lines (next bullets) and a line appended after the commit, such as a
|
|
271
|
+
second review round's record. It stages that one path and nothing else, commits it on a branch of
|
|
272
|
+
its own, opens a docs-only pull request, waits on the same required checks every other pull request
|
|
273
|
+
waits on, merges it and returns the checkout to the default branch. **Nothing is committed to the
|
|
274
|
+
default branch directly.**
|
|
275
|
+
- **`pnpm josh followup` runs that flush itself, so no run has to remember to** (joshuafolkken/kit#1810).
|
|
276
|
+
After the merge and before it releases the working-tree hold, `followup` reads the tree, and **only
|
|
277
|
+
when the ledger still holds a pending append** returns the checkout to the default branch
|
|
278
|
+
(`pnpm josh ms`) and flushes. A run whose lines rode its commit pays nothing, and a lane skips it
|
|
279
|
+
(next bullet). A flush that fails is reported and does not take the merge, the epic close or the
|
|
280
|
+
hold release down with it. **A side effect: on a run that did flush, `followup` ends on the default
|
|
281
|
+
branch.**
|
|
276
282
|
- **A lane's writers use the primary checkout's ledger** (joshuafolkken/kit#2419): a lane refuses
|
|
277
283
|
`pnpm josh ms`, so its own copy had no way out. `pnpm josh observations:flush` acts on the primary
|
|
278
284
|
checkout from a lane. **A dispatched lane child does not flush at all** (joshuafolkken/kit#2492): its
|
|
279
285
|
`run:tail` skips the step, and the `backlogrun` flushes every lane's lines once, in one pull request,
|
|
280
286
|
at `pnpm josh run:carry --end`.
|
|
281
|
-
- **
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
- **A lane child's pull request does not carry the lines, and that is deliberate.** Lanes run side by
|
|
288
|
+
side, and two pull requests that each append at the ledger's tail conflict with each other the
|
|
289
|
+
moment the first merges — a conflict ends the other's CI wait. The batch's single flush at
|
|
290
|
+
`run:carry --end` is already one pull request per batch, not one per child.
|
|
285
291
|
- **Run by hand it goes in the primary checkout, once per cycle rather than once per observation** —
|
|
286
|
-
but with `pnpm josh followup`
|
|
287
|
-
than the rule. It refuses off the default branch and refuses a working tree holding anything besides
|
|
292
|
+
but with the lines riding the run's commit and `pnpm josh followup` flushing the rest (above), a hand
|
|
293
|
+
run is the exception rather than the rule. It refuses off the default branch and refuses a working tree holding anything besides
|
|
288
294
|
the ledger, so a run in progress cannot be flushed out from under, and a lane's checkout is never
|
|
289
295
|
the one it acts on.
|
|
290
296
|
- **Nothing to flush is an answer, not a failure.** With the ledger matching the commit it sits on
|
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
# The pre-gate cut — a lane child ends its turn before the gate
|
|
2
2
|
|
|
3
3
|
A lane child is a detached `fullrun #<N>` process, and the thinking it accumulates while implementing
|
|
4
|
-
rides on every later API call in the same session
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
rides on every later API call in the same session. The **pre-gate cut** drops that accumulation: the
|
|
5
|
+
child ends its process the moment implementation is done, and a **fresh** process resumes the same lane
|
|
6
|
+
from the gate onward. Rationale: `docs/maintainers/pre-gate-cut-rationale.md` → "Why the cut exists".
|
|
7
7
|
|
|
8
8
|
This file is the single source of the boundary. `run:cut` takes it, and `run:cut --resume` is the
|
|
9
|
-
check a fresh process makes at its own entry.
|
|
10
|
-
after the workflow skill has been read, so it is not an entry read — `fullrun.md`, `chain-rule.md` and
|
|
11
|
-
`backlogrun.md` name it with a plain mention and route here. **A second boundary is added below** — the
|
|
9
|
+
check a fresh process makes at its own entry. **A second boundary is added below** — the
|
|
12
10
|
implementation-phase cut (joshuafolkken/kit#1933), at a context threshold _during_ implementation.
|
|
13
11
|
Everything through "What is carried" describes the pre-gate cut; the later section states only what
|
|
14
12
|
differs.
|
|
15
13
|
|
|
16
14
|
## Where the boundary is
|
|
17
15
|
|
|
18
|
-
**After implementation and the refactor, before `pnpm josh gate
|
|
19
|
-
|
|
20
|
-
the
|
|
21
|
-
(gate → review → commit → push → merge) is verification the fresh process runs on the tree.
|
|
16
|
+
**After implementation and the refactor, before `pnpm josh gate`**: implementation complete, nothing
|
|
17
|
+
committed, the gate not started. Everything after it (gate → review → commit → push → merge) is
|
|
18
|
+
verification the fresh process runs on the tree.
|
|
22
19
|
|
|
23
20
|
**A lane child first hands it to a detached supervisor** (`chain-rule.md` step 0); the cut is the fallback.
|
|
24
21
|
|
|
25
22
|
## It applies to a dispatched lane child, and to nothing else
|
|
26
23
|
|
|
27
|
-
The cut is for a **detached** `fullrun` a lane dispatched
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
The cut is for a **detached** `fullrun` a lane dispatched; a person's interactive `fullrun` never
|
|
25
|
+
relaunches itself. A dispatched child is told apart **mechanically, by a mark the dispatch sets** — never
|
|
26
|
+
by the model reading the shape of its own prompt (joshuafolkken/kit#1904).
|
|
30
27
|
|
|
31
28
|
### The dispatch mark
|
|
32
29
|
|
|
@@ -34,8 +31,7 @@ never by the model reading the shape of its own prompt (joshuafolkken/kit#1904).
|
|
|
34
31
|
to the lane's issue number. `scripts/lane/lane-child-marker.ts` is its one definition.
|
|
35
32
|
|
|
36
33
|
- **Meaning**: "this session was launched for issue `<N>`, not typed by a person."
|
|
37
|
-
- **Lifetime**: the child process — set at launch, read at the gate, never written by the run itself
|
|
38
|
-
mark a run could set for itself would be no mark at all).
|
|
34
|
+
- **Lifetime**: the child process — set at launch, read at the gate, never written by the run itself.
|
|
39
35
|
- **Trust**: only where its value equals the lane's own issue. A mark that leaked in from a parent
|
|
40
36
|
session — naming another issue, or none — is read as a person, so a leak cannot make the guard fire
|
|
41
37
|
on a checkout it does not belong to.
|
|
@@ -48,13 +44,11 @@ checkout, a cut in a lane.
|
|
|
48
44
|
|
|
49
45
|
## Taking the cut
|
|
50
46
|
|
|
51
|
-
**The order is fixed in the chain
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
decides whether the cut is warranted (joshuafolkken/kit#2312), so a short lane with nothing to drop is
|
|
57
|
-
answered `under-threshold` and walks on to the gate rather than relaunching.
|
|
47
|
+
**The order is fixed in the chain** (joshuafolkken/kit#2177): `chain-rule.md` step 1 issues
|
|
48
|
+
`pnpm josh run:cut <N>` after `pnpm josh main:merge`, before the scoped pair and the gate. **The command
|
|
49
|
+
is issued unconditionally**; `run:cut` itself decides whether the cut is warranted
|
|
50
|
+
(joshuafolkken/kit#2312), so a short lane with nothing to drop is answered `under-threshold` and walks on
|
|
51
|
+
to the gate rather than relaunching.
|
|
58
52
|
|
|
59
53
|
At the pre-gate boundary, issue:
|
|
60
54
|
|
|
@@ -65,9 +59,8 @@ pnpm josh run:cut <N> # alias: josh rct
|
|
|
65
59
|
- `cut` (0) — record written and handed to the lane's launch owner (OpenAI supervisor starts the
|
|
66
60
|
successor after this process exits; Anthropic relaunches directly). **End the turn immediately** —
|
|
67
61
|
the fresh process owns the run.
|
|
68
|
-
- `under-threshold` (0) — the recent-window per-request cost is under the shared `CONTEXT_CUT_THRESHOLD
|
|
69
|
-
|
|
70
|
-
was cut; **continue to the gate.**
|
|
62
|
+
- `under-threshold` (0) — the recent-window per-request cost is under the shared `CONTEXT_CUT_THRESHOLD`,
|
|
63
|
+
so nothing was cut; **continue to the gate.**
|
|
71
64
|
- `not-a-lane` (0) — no open lane for this issue, so not a dispatched child. **Continue to the gate.**
|
|
72
65
|
- `unready` (1) — the tree is clean or on the default branch, nothing to carry. Continue to the gate.
|
|
73
66
|
- `busy` (1) — a cut is already in flight for this tree (the double-cut guard). Do not relaunch.
|
|
@@ -79,29 +72,22 @@ the current process to carry the run on itself.
|
|
|
79
72
|
|
|
80
73
|
### The gate refuses until the cut has been taken (joshuafolkken/kit#1864)
|
|
81
74
|
|
|
82
|
-
**This step was carried as prose and fired exactly never.**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
is
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`JOSH_LANE_CHILD` names that same issue. An interactive `fullrun` carries no mark and runs its gate
|
|
93
|
-
untouched. `run:cut` draws its own line its own way — an open-lane lookup — which is why the guard and
|
|
94
|
-
the command need not agree byte for byte; the mark makes the guard's half mechanical.
|
|
75
|
+
**This step was carried as prose and fired exactly never.** Across six measured lane children the cut
|
|
76
|
+
was taken **0 times**. So `pnpm josh rule:guard` **refuses `pnpm josh gate`** while this checkout is a
|
|
77
|
+
lane and no cut record is carried, handing back the command above. With the ordered step above the
|
|
78
|
+
refusal is insurance, not the primary trigger. Rationale:
|
|
79
|
+
`docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
|
|
80
|
+
|
|
81
|
+
- **It fires for a marked child and nowhere else**: the working directory is a lane —
|
|
82
|
+
`<lane root>/<issue number>` — **and** `JOSH_LANE_CHILD` names that same issue. An interactive
|
|
83
|
+
`fullrun` carries no mark and runs its gate untouched. `run:cut` uses an open-lane lookup instead, so
|
|
84
|
+
the guard and the command need not agree byte for byte.
|
|
95
85
|
- **It fires only when the recent-window context warrants a cut** (joshuafolkken/kit#2312). The guard
|
|
96
86
|
reads the same `cost_cli.session_verdict` `run:cut` reads, so an `under`-threshold lane is let through
|
|
97
|
-
to the gate rather than refused
|
|
98
|
-
due. The context read is lazy, after the command and lane checks, so an ordinary run pays nothing for
|
|
99
|
-
it.
|
|
87
|
+
to the gate rather than refused.
|
|
100
88
|
- **It is silent once the cut is carried**, so the resumed process goes straight to the gate.
|
|
101
|
-
- **It fires once per run.**
|
|
102
|
-
|
|
103
|
-
- **`--resume`, `--end` and `--json` do not count as taking the cut**, because they ask about one
|
|
104
|
-
rather than take it — counting them would have credited four of the six measured children.
|
|
89
|
+
- **It fires once per run.** A refusal that repeated would wedge the runs that obeyed.
|
|
90
|
+
- **`--resume`, `--end` and `--json` do not count as taking the cut.**
|
|
105
91
|
|
|
106
92
|
The row, its trigger and the enumeration it joins are
|
|
107
93
|
`prompts/collaboration-workflow/rule-delivery.md`; `scripts/rules/pre-gate-cut.test.ts` pins it.
|
|
@@ -131,34 +117,34 @@ pnpm josh run:cut --resume <N>
|
|
|
131
117
|
the wrong tree.
|
|
132
118
|
- `busy` (1) — another process already owns the resume. Send a `confirmation` Telegram and stop.
|
|
133
119
|
|
|
134
|
-
**A resume is never reported as a success it did not earn
|
|
135
|
-
than gating
|
|
120
|
+
**A resume is never reported as a success it did not earn**: `stale` and `busy` stop the run rather
|
|
121
|
+
than gating.
|
|
136
122
|
|
|
137
123
|
### The stage is passed to the resumed child, not reconstructed by it
|
|
138
124
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
A resumed child arrives with `JOSH_LANE_CHILD` still set and `run:cut --resume` reads the record the cut
|
|
126
|
+
left, so it learns _where to resume from_ at its entry. **Skip the entry it has already done**: do not
|
|
127
|
+
re-read the workflow skill, repeat the split assessment or re-normalize the title; the plan and
|
|
128
|
+
decisions are read back from the issue. Rationale:
|
|
129
|
+
`docs/maintainers/pre-gate-cut-rationale.md` → "Why the resume carries what it carries".
|
|
144
130
|
|
|
145
131
|
## What is carried, and what is not
|
|
146
132
|
|
|
147
133
|
- **The working tree is not in the record, because it never left the disk.** The record carries only
|
|
148
134
|
what a fresh process cannot recover — the issue, the branch, the invocation, and that a **declared
|
|
149
|
-
cut** put it here. **The conversation is not carried
|
|
150
|
-
|
|
151
|
-
- **The hold is kept, not released** — the
|
|
152
|
-
|
|
153
|
-
- **The dispatch mark is re-applied, not inherited
|
|
154
|
-
|
|
135
|
+
cut** put it here. **The conversation is not carried**; the fresh process reads the plan and
|
|
136
|
+
auto-decisions back off GitHub.
|
|
137
|
+
- **The hold is kept, not released** — the resumed process adopts the `run:hold` record and
|
|
138
|
+
`pnpm josh followup` releases it at the merge.
|
|
139
|
+
- **The dispatch mark is re-applied, not inherited**: the relaunch strips the parent's environment and
|
|
140
|
+
sets `JOSH_LANE_CHILD` afresh from the lane's issue.
|
|
155
141
|
|
|
156
142
|
## Verification, uniqueness and double-launch
|
|
157
143
|
|
|
158
144
|
- **The resume verifies the tree against the record**: the branch matches, the tree is dirty, the
|
|
159
145
|
issue matches, and the tree is still under the cutting run's hold. Any mismatch is `stale`.
|
|
160
|
-
- **No owner is recorded on the cut
|
|
161
|
-
|
|
146
|
+
- **No owner is recorded on the cut** — the cut is the stage hand-off alone (OpenAI keeps its
|
|
147
|
+
supervisor owner separately).
|
|
162
148
|
- **The cut is exclusive** — `run:cut` writes the record with an exclusive create, so a second cut on
|
|
163
149
|
the same tree is refused `busy`.
|
|
164
150
|
- **The resume is unique.** Taking the hand-off over spends it — a second `run:cut --resume` reads a
|
|
@@ -168,11 +154,8 @@ reconstruction a stopped child redid on every resume (2.6 minutes / ~150K tokens
|
|
|
168
154
|
|
|
169
155
|
## The implementation-phase cut — a lane child cuts before the gate too
|
|
170
156
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
conversation on every request, so a long implementation is billed like a long `backlogrun` parent (which
|
|
174
|
-
on 2026-09-13 measured **208k–386k** median context per request). The **implementation-phase cut** caps
|
|
175
|
-
that growth — the child ends mid-implementation and a fresh one resumes the lane **back into
|
|
157
|
+
The **implementation-phase cut** (joshuafolkken/kit#1933) caps the context accumulated _during_
|
|
158
|
+
implementation — the child ends mid-implementation and a fresh one resumes the lane **back into
|
|
176
159
|
implementation**.
|
|
177
160
|
|
|
178
161
|
### The measurement is the parent hand-off's, never a second one
|
|
@@ -186,106 +169,67 @@ built for the lane child.
|
|
|
186
169
|
|
|
187
170
|
### It is a guard, fired at the edit that crosses the threshold (joshuafolkken/kit#2310)
|
|
188
171
|
|
|
189
|
-
**This step was carried as prose and fired exactly never** —
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
exact verdict `pnpm josh cost --cut` prints. joshuafolkken/kit#2310 measured the child-side check across
|
|
193
|
-
five lanes: the verdict was read once each at session entry, before the context grew, so the cut fired
|
|
194
|
-
**0 times** while 33.9% of their requests ran past 200,000 tokens.
|
|
172
|
+
**This step was carried as prose and fired exactly never** — measured across five lanes, the cut fired
|
|
173
|
+
**0 times** while 33.9% of their requests ran past 200,000 tokens. Rationale:
|
|
174
|
+
`docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
|
|
195
175
|
|
|
196
176
|
So `pnpm josh rule:guard` **refuses an `Edit` / `Write`** while this checkout is a dispatched lane child
|
|
197
|
-
whose recent-context cost is over threshold, handing back `pnpm josh run:cut --impl <N> --handoff <path>` —
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
- **An
|
|
214
|
-
cannot wedge the run: the reissue lands inside a short window and goes through, a genuinely new crossing
|
|
215
|
-
past it refuses again.
|
|
216
|
-
- **An unmeasurable session warrants the cut** (joshuafolkken/kit#2385) — the `verdict !== UNDER_VERDICT`
|
|
217
|
-
reading matches the pre-gate cut's `warrants_the_cut`, so they never disagree.
|
|
177
|
+
whose recent-context cost is over threshold, handing back `pnpm josh run:cut --impl <N> --handoff <path>` — the
|
|
178
|
+
refusal lands _before_ the edit. `cut` ends the turn, the rest leave this process implementing; a fresh
|
|
179
|
+
process's `pnpm josh run:cut --resume <N>` then answers **`resume-impl`**, so it **skips the title, plan,
|
|
180
|
+
hold claim and split assessment** and **continues implementation** rather than gating. Unlike the
|
|
181
|
+
pre-gate cut, **the implementation resume clears the record** — it may fire again, unlike the
|
|
182
|
+
once-per-lane pre-gate cut (joshuafolkken/kit#1935); `begin_cut`'s exclusive create still gives each
|
|
183
|
+
crossing one successor.
|
|
184
|
+
|
|
185
|
+
- **It fires for a marked child, or a run held outside a lane on a measured `over`**
|
|
186
|
+
(joshuafolkken/kit#2760); a person's tree holds no run. Outside a lane `cut` relaunches nothing: keep
|
|
187
|
+
the hold, send a `confirmation` Telegram naming `fullrun #<N>`, end the turn.
|
|
188
|
+
- **It fires on every threshold crossing, not once per run** (joshuafolkken/kit#2385) — it re-asks on
|
|
189
|
+
each over-threshold edit, including after a `busy` / `failed` / `unready` verdict.
|
|
190
|
+
- **An edit reissued right after a refusal passes** — so a `busy` / `failed` cut cannot wedge the run:
|
|
191
|
+
the reissue lands inside a short window and goes through, a genuinely new crossing past it refuses
|
|
192
|
+
again.
|
|
193
|
+
- **An unmeasurable session warrants the cut** in a lane, as the pre-gate cut's does.
|
|
218
194
|
- **It is silent between a cut and its resume** — a carried record naming this issue keeps it quiet; the
|
|
219
195
|
resume clears it, and the fresh process reads its own transcript under threshold.
|
|
220
|
-
- **The verdict read is reused over a short window**
|
|
221
|
-
|
|
222
|
-
of edits shares one read.
|
|
196
|
+
- **The verdict read is reused over a short window** — cached per checkout for a few seconds, so a
|
|
197
|
+
turn's burst of edits shares one read.
|
|
223
198
|
|
|
224
199
|
`scripts/rules/implementation-cut.ts` implements the trigger, `scripts/rules/implementation-cut.test.ts`
|
|
225
200
|
pins it, and the row joins `prompts/collaboration-workflow/rule-delivery.md`. **A child that ended
|
|
226
201
|
without cutting is still detected after the fact by `pnpm josh run:ending <N> --output <path>`**
|
|
227
202
|
(joshuafolkken/kit#2139). **A single check (`lint:related` / `test:related`) is run after a batch of
|
|
228
|
-
edits, not after each one
|
|
203
|
+
edits, not after each one.**
|
|
229
204
|
|
|
230
205
|
## The threshold, and the statistic it is measured against
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
statistic
|
|
234
|
-
requests
|
|
235
|
-
joshuafolkken/kit#
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
parent past the point a cut was due. The recent-10 average removes that lag.
|
|
245
|
-
- **It is not the forbidden second measurement.** joshuafolkken/kit#1933 forbids the child pricing its
|
|
246
|
-
cut on a statistic the parent does not share; #2295 keeps one statistic both read and redefines it.
|
|
247
|
-
|
|
248
|
-
### The value is a break-even, not a chosen number (joshuafolkken/kit#2406)
|
|
249
|
-
|
|
250
|
-
**The threshold is derived.** joshuafolkken/kit#2374 set 150_000; joshuafolkken/kit#2406 replaced the
|
|
251
|
-
hand-picked number with the arithmetic behind it. A cut costs one preamble rewrite
|
|
252
|
-
(`POST_CUT_CONTEXT`, ~60_000 tokens) and saves the dropped context's cache read on every later request,
|
|
253
|
-
so it turns on a rate, not a ceiling: `context-cut-payback.ts` computes the per-request context at which a
|
|
254
|
-
cut pays back over the expected remaining requests, from the cache multipliers in `cost-pricing.ts` (no
|
|
255
|
-
second price list). At a 10-request horizon that is **135_000**, in #2374's band; the horizon is the knob
|
|
256
|
-
a before/after lane measurement tunes (#2406 PR).
|
|
257
|
-
|
|
258
|
-
### The cut is conditional, and the resume side is why (joshuafolkken/kit#2312)
|
|
259
|
-
|
|
260
|
-
**Until joshuafolkken/kit#2312 the pre-gate cut was _unconditional_, and its case recorded only the
|
|
261
|
-
gain** — the context reduction it carries (~130k → ~60k per request). **The resume side was never
|
|
262
|
-
priced.** A cut relaunches a fresh process that re-reads the plan and decisions off GitHub before the
|
|
263
|
-
gate — a fixed ramp of ~12 requests per session. That ramp was **$81.01 of $435.09 (19%)**
|
|
264
|
-
across the 32 lanes of 2026-09-21, falling hardest on the short lanes with the least to drop: #2282 spent
|
|
265
|
-
**58%** of its billing on it at 60k median context, #2295 **39%** at 74k, #2289 **37%** at 81k — each
|
|
266
|
-
with **0%** of its requests over 200k.
|
|
267
|
-
|
|
268
|
-
**So the cut is now taken only when the recent-window per-request cost is over `CONTEXT_CUT_THRESHOLD`**
|
|
269
|
-
— the same statistic and threshold the implementation-phase cut reads, so **no second threshold is
|
|
270
|
-
introduced**. Below it `run:cut` answers `under-threshold` and the gate runs uncut; the guard reads the
|
|
271
|
-
same verdict and stays silent. A lane like #2298 (31% of requests over 200k) still cuts as before, and an
|
|
272
|
-
**unmeasurable** session keeps the old unconditional cut so it is never left uncut.
|
|
207
|
+
**`CONTEXT_CUT_THRESHOLD` (135_000) is one value, and `cost_verdict.per_request_cost` — the one
|
|
208
|
+
statistic the parent and the lane child both read — averages the most recent `RECENT_REQUEST_WINDOW`
|
|
209
|
+
(10) requests, not the whole session** (joshuafolkken/kit#2295). The value is a derived break-even, not a
|
|
210
|
+
chosen number (joshuafolkken/kit#2406): `scripts/cost-runtime/context-cut-payback.ts` computes it from a
|
|
211
|
+
cut's one-time preamble rewrite (`POST_CUT_CONTEXT`, ~60_000 tokens — a cut carries a request from ~130k
|
|
212
|
+
down to ~60k), the cache multipliers in `cost-pricing.ts`, and a 10-request horizon.
|
|
213
|
+
|
|
214
|
+
**The cut is conditional** (joshuafolkken/kit#2312): taken only when the recent-window per-request cost
|
|
215
|
+
is over `CONTEXT_CUT_THRESHOLD` — the same statistic and threshold the implementation-phase cut reads, so
|
|
216
|
+
no second threshold exists. Below it `run:cut` answers `under-threshold` and the gate runs uncut; the
|
|
217
|
+
guard reads the same verdict and stays silent. An **unmeasurable** session is always cut.
|
|
218
|
+
Rationale: `docs/maintainers/pre-gate-cut-rationale.md` → "Why the threshold is a break-even".
|
|
273
219
|
|
|
274
220
|
## Consistency with the chain rule
|
|
275
221
|
|
|
276
222
|
The chain forbids ending a turn at the push, with CI in flight and nothing set to resume. **The pre-gate
|
|
277
223
|
cut and the detached hand-off end it _before_ the gate** and start what carries the run on in the same
|
|
278
224
|
act — a fresh process, or the supervisor — so the push is never a turn boundary. The implementation-phase
|
|
279
|
-
cut is the same pattern one boundary earlier
|
|
225
|
+
cut is the same pattern one boundary earlier.
|
|
280
226
|
|
|
281
227
|
## A lane child records its park before it stops
|
|
282
228
|
|
|
283
229
|
**A dispatched lane child records its park on the Issue before it stops for a decision**
|
|
284
|
-
(joshuafolkken/kit#2034). A headless child's only route to ask a person
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
the parent found an OPEN Issue with `in-progress` and no question. The child is the only process that
|
|
288
|
-
knows the question, so it records the park itself rather than leaving the parent to reconstruct it.
|
|
230
|
+
(joshuafolkken/kit#2034). A headless child's only route to ask a person is to park the Issue — measured
|
|
231
|
+
on #2012 and #2011, whose questions reached only their final message. Rationale:
|
|
232
|
+
`docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
|
|
289
233
|
|
|
290
234
|
**The record, before the Telegram, is the park procedure exactly** — `needs-decision` plus a comment
|
|
291
235
|
carrying the question, the options, and whether work was stashed. Its single source is
|
|
@@ -299,18 +243,16 @@ would otherwise leave the Issue bare.
|
|
|
299
243
|
|
|
300
244
|
### The stop notify refuses until the park is recorded
|
|
301
245
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
while this checkout is a lane child — the mark `JOSH_LANE_CHILD` names this lane's own issue — handing
|
|
305
|
-
back the park commands.
|
|
246
|
+
`pnpm josh rule:guard` **refuses `pnpm josh notify --task-type confirmation`** while this checkout is a
|
|
247
|
+
lane child — the mark `JOSH_LANE_CHILD` names this lane's own issue — handing back the park commands.
|
|
306
248
|
|
|
307
249
|
- **It fires for a marked child and nowhere else.** A person's own `fullrun` carries no mark and sends
|
|
308
250
|
its stop notify untouched.
|
|
309
251
|
- **It fires once per run.** The child records the park and reissues the same notify; a
|
|
310
252
|
`needs-human-review` or `already-done` stop reissues after the one delivery, its label already on the
|
|
311
253
|
Issue.
|
|
312
|
-
- **The park state is not readable synchronously
|
|
313
|
-
|
|
254
|
+
- **The park state is not readable synchronously**, so the delivery is a checklist — one wasted reissue
|
|
255
|
+
on the compliant path is safe.
|
|
314
256
|
|
|
315
257
|
This section is the single source of the lane-child park rule; `scripts/rules/lane-park.ts` implements
|
|
316
258
|
the trigger, `scripts/rules/lane-park.test.ts` pins it, and the row joins the enumeration in
|
|
@@ -318,18 +260,11 @@ the trigger, `scripts/rules/lane-park.test.ts` pins it, and the row joins the en
|
|
|
318
260
|
|
|
319
261
|
### The interactive ask is refused one call earlier
|
|
320
262
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
`in-progress` and no question — the failure joshuafolkken/kit#2034 set out to prevent, one call
|
|
327
|
-
upstream.
|
|
328
|
-
|
|
329
|
-
So `pnpm josh rule:guard` **refuses `AskUserQuestion` for a marked lane child**, one call before the
|
|
330
|
-
stop it would have reached. A hook deny returns to the model rather than ending the turn, so the
|
|
331
|
-
refusal becomes an instruction — park the question. It fires on **every occurrence** and stays silent
|
|
332
|
-
for a person working in a lane.
|
|
263
|
+
(joshuafolkken/kit#2201) The harness refuses an interactive ask in a headless session by **ending the
|
|
264
|
+
turn**, so the notify guard never fires — #2178's child did exactly that, the failure
|
|
265
|
+
joshuafolkken/kit#2034 set out to prevent, one call upstream. So `pnpm josh rule:guard` **refuses
|
|
266
|
+
`AskUserQuestion` for a marked lane child**; the deny returns to the model, and the instruction is to
|
|
267
|
+
park the question. It fires on **every occurrence** and stays silent for a person working in a lane.
|
|
333
268
|
|
|
334
269
|
**A child that slips past it is still recoverable** — `pnpm josh run:ending` lifts the refused ask out
|
|
335
270
|
of the exit record's `permission_denials` into the `abandoned` verdict's park basis, so the parent puts
|
|
@@ -15,6 +15,13 @@ on:
|
|
|
15
15
|
# nothing here needs it: every run reads the pull request's labels as they stand when it runs, not as
|
|
16
16
|
# its event carried them, so whichever suite GitHub counts as newest holds a current verdict.
|
|
17
17
|
|
|
18
|
+
# The same safe-chain installer pin as ci.yml — the release and its install-safe-chain.sh SHA-256.
|
|
19
|
+
# `pnpm dlx … setup-ci` left only shims, so the install below ran without a scan (joshuafolkken/kit#2765).
|
|
20
|
+
# `josh latest` moves both together, in this file and in ci.yml.
|
|
21
|
+
env:
|
|
22
|
+
SAFE_CHAIN_INSTALLER_VERSION: 1.5.20
|
|
23
|
+
SAFE_CHAIN_INSTALLER_SHA256: 0ad25efe15d1fa56105157a454d647223e78eb0c53d1f85e3d10afcd722e7bfd
|
|
24
|
+
|
|
18
25
|
jobs:
|
|
19
26
|
classification:
|
|
20
27
|
name: Release classification
|
|
@@ -63,7 +70,11 @@ jobs:
|
|
|
63
70
|
${{ runner.os }}-pnpm-store-
|
|
64
71
|
|
|
65
72
|
- name: Setup safe-chain
|
|
66
|
-
run:
|
|
73
|
+
run: |
|
|
74
|
+
installer="$RUNNER_TEMP/install-safe-chain.sh"
|
|
75
|
+
curl --proto '=https' -fsSL "https://github.com/AikidoSec/safe-chain/releases/download/$SAFE_CHAIN_INSTALLER_VERSION/install-safe-chain.sh" -o "$installer"
|
|
76
|
+
echo "$SAFE_CHAIN_INSTALLER_SHA256 $installer" | sha256sum -c -
|
|
77
|
+
sh "$installer" --ci
|
|
67
78
|
|
|
68
79
|
- name: Install dependencies
|
|
69
80
|
env:
|
package/CLAUDE.md
CHANGED
|
@@ -89,7 +89,7 @@ Read from a `.env` file at the project root by the AI scripts, `josh port` and `
|
|
|
89
89
|
|
|
90
90
|
### Dependency overrides (`pnpm-workspace.yaml` / `package.json`)
|
|
91
91
|
|
|
92
|
-
- **Effective overrides live in `pnpm-workspace.yaml`.** pnpm 11 and 12 ignore `pnpm.overrides` in `package.json
|
|
92
|
+
- **Effective overrides live in `pnpm-workspace.yaml`.** pnpm 11 and 12 ignore `pnpm.overrides` in `package.json`. Check both files for existing declarations, but never count the ignored package field as an effective override. **An absent or empty `pnpm.overrides` is not evidence that the project has no overrides.**
|
|
93
93
|
- **NEVER** remove or modify entries in **either** location without explicit user approval.
|
|
94
94
|
- **NEVER** modify the `devEngines` field in `package.json` without explicit user confirmation. It pins the development toolchain (e.g. pnpm version); silently changing it can break CI or other contributors' environments.
|
|
95
95
|
- **The check is a command you run, not a conclusion you reach.** After `pnpm update`, `josh latest`, or any dependency-update command, **load the `dependency-update` skill** and follow its procedure before reporting anything about the pins.
|
|
@@ -144,7 +144,7 @@ Before every `git commit` (follow-up commits included), self-review against `pro
|
|
|
144
144
|
|
|
145
145
|
## Doc Sync Rules
|
|
146
146
|
|
|
147
|
-
**`CLAUDE.md` is the single source for every agent rule.** `AGENTS.md` and `GEMINI.md` carry no rules — a rule addition, spec change or wording fix is written **once**, here (joshuafolkken/kit#963).
|
|
147
|
+
**`CLAUDE.md` is the single source for every agent rule.** `AGENTS.md` and `GEMINI.md` carry no rules — a rule addition, spec change or wording fix is written **once**, here (joshuafolkken/kit#963).
|
|
148
148
|
|
|
149
149
|
**docs/ must stay in sync with the package.** When `josh bump` changes the version, review `docs/` and update any section describing changed behavior (new/renamed commands, `josh init` / `josh sync` behavior, new config files) before committing.
|
|
150
150
|
|
|
@@ -166,7 +166,7 @@ Before every `git commit` (follow-up commits included), self-review against `pro
|
|
|
166
166
|
|
|
167
167
|
`kickoff`, `fullrun`, `halfrun` and `backlogrun` are the Issue-driven shorthand commands. **Their procedures are not resident** — they live in the `workflow-commands` skill. **What stays here is decided by one question: must the rule fire on a turn where no skill was loaded?** Explicit invocation, the mid-workflow stop notification, the `overrides` / `devEngines` prohibitions, the UI-verification gate and the three `epic:*` rules below all do; everything a run reaches only after reading the skill is routed to, never restated (`SKILL.md` → "What stays resident, and what is read from here").
|
|
168
168
|
|
|
169
|
-
**Read the skill before running any part of a command — including the first `gh` call.**
|
|
169
|
+
**Read the skill before running any part of a command — including the first `gh` call.**
|
|
170
170
|
|
|
171
171
|
| Typed keyword | What it does | Read first |
|
|
172
172
|
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ kit adds no linter or test runner for languages other than JavaScript and TypeSc
|
|
|
19
19
|
In your project directory:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
pnpm add -D --allow-build=esbuild @joshuafolkken/kit
|
|
22
|
+
pnpm add -D --allow-build=esbuild --allow-build=unrs-resolver @joshuafolkken/kit
|
|
23
23
|
pnpm exec josh init
|
|
24
|
-
pnpm install
|
|
25
|
-
pnpm josh format
|
|
26
24
|
pnpm josh gate
|
|
27
25
|
```
|
|
28
26
|
|
|
27
|
+
`josh init` finishes by running `pnpm install` and `josh format` itself; pass `--no-install` to skip both, for example on CI or offline.
|
|
28
|
+
|
|
29
29
|
`josh init` is the entry without GitHub. To start a new project on the GitHub Issue workflow (`kickoff`), run `pnpm exec josh start` instead of `josh init` — it also creates the Git repository, the GitHub repository and the labels ([init.md → `josh init` or `josh start`](./docs/init.md#josh-init-or-josh-start)).
|
|
30
30
|
|
|
31
31
|
It needs [Node.js](https://nodejs.org/) 22.19.0 or later with [pnpm](https://pnpm.io/), even for a `static` project; [getting-started.md](./docs/getting-started.md) installs both. The global `josh` command ([cli.md](./docs/cli.md)) and the [gh CLI](https://cli.github.com/) for `josh version` and the GitHub Issue workflow are optional.
|