@joshuafolkken/kit 1.1094.0 → 1.1136.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.
Files changed (177) hide show
  1. package/.claude/agents/investigator.md +17 -0
  2. package/.claude/settings.json +1 -0
  3. package/.claude/skills/workflow-commands/SKILL.md +1 -1
  4. package/.claude/skills/workflow-commands/background-commands.md +36 -85
  5. package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -34
  6. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -2
  7. package/.claude/skills/workflow-commands/delegation.md +5 -1
  8. package/.claude/skills/workflow-commands/fullrun.md +5 -4
  9. package/.claude/skills/workflow-commands/halfrun.md +5 -4
  10. package/.claude/skills/workflow-commands/latest-gate.md +7 -15
  11. package/.claude/skills/workflow-commands/pre-gate-cut.md +4 -8
  12. package/.claude/skills/workflow-commands/split-assessment.md +4 -18
  13. package/.github/actions/setup-node/action.yml +33 -0
  14. package/.github/actions/setup-pnpm/action.yml +4 -4
  15. package/.github/workflows/auto-tag.yml +2 -3
  16. package/.github/workflows/ci.yml +7 -8
  17. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  18. package/.github/workflows/pr-classification.yml +1 -3
  19. package/.github/workflows/production.yml +1 -0
  20. package/.github/workflows/publish.yml +10 -9
  21. package/.github/workflows/sonar-qube.yml +17 -13
  22. package/.pnpmfile.mjs +29 -0
  23. package/AGENTS.md +4 -11
  24. package/CLAUDE.md +24 -17
  25. package/GEMINI.md +4 -13
  26. package/README.md +8 -3
  27. package/SECURITY.md +2 -0
  28. package/cspell/index.yaml +4 -0
  29. package/dist/CLAUDE.basic.md +6 -6
  30. package/dist/CLAUDE.md +24 -17
  31. package/dist/CLAUDE.static.md +6 -6
  32. package/dist/config-merge/index.js +33 -9
  33. package/dist/hooks/batch-guard.js +4 -4
  34. package/dist/hooks/{chunk-OIZUHPJI.js → chunk-FO2I7UT7.js} +1 -1
  35. package/dist/hooks/{chunk-NKOG3XAV.js → chunk-GDU26K3J.js} +11 -3
  36. package/dist/hooks/{chunk-O4LL4HTI.js → chunk-HANSKHES.js} +2 -2
  37. package/dist/hooks/{chunk-3C4YCCEF.js → chunk-HBKZLITF.js} +4 -4
  38. package/dist/hooks/{chunk-UD24EAIL.js → chunk-JKKTVDAG.js} +1984 -146
  39. package/dist/hooks/{chunk-M2XDCUKD.js → chunk-KBXZLMRH.js} +89 -1803
  40. package/dist/hooks/{chunk-IL27YTPH.js → chunk-KOKOAV6D.js} +2 -2
  41. package/dist/hooks/{chunk-7ZPFAVR7.js → chunk-LJBLGHEH.js} +59 -19
  42. package/dist/hooks/{chunk-E54KGFEQ.js → chunk-NFZGFGOR.js} +1 -1
  43. package/dist/hooks/{chunk-KHWHKJOM.js → chunk-RFWWUI5F.js} +4 -4
  44. package/dist/hooks/codex-hook-adapter.js +12 -47
  45. package/dist/hooks/duplicate-read-guard.js +5 -5
  46. package/dist/hooks/format-edited.js +2 -2
  47. package/dist/hooks/inputs.json +175 -173
  48. package/dist/hooks/investigation-guard.js +5 -5
  49. package/dist/hooks/pretool-guard.js +9 -9
  50. package/dist/hooks/stop-guard.js +17 -12
  51. package/dist/josh.js +87 -31
  52. package/dist/version/index.js +36 -20
  53. package/eslint/rules/unicorn.js +3 -2
  54. package/package.json +23 -5
  55. package/pnpm-workspace.yaml +5 -7
  56. package/prompts/collaboration-workflow/glossary.md +17 -0
  57. package/prompts/collaboration-workflow/issue-template.md +1 -1
  58. package/prompts/collaboration-workflow/operating-rules.md +1 -1
  59. package/prompts/collaboration-workflow/principles.md +9 -0
  60. package/prompts/collaboration-workflow.md +1 -0
  61. package/scripts/backlog/backlog-arrival.ts +30 -10
  62. package/scripts/backlog/backlog-budget-cli.ts +15 -16
  63. package/scripts/backlog/backlog-offer-cli.ts +6 -14
  64. package/scripts/backlog/backlog-plan.ts +29 -18
  65. package/scripts/backlog/backlog-ready.ts +20 -7
  66. package/scripts/config-merge/parse-jsonc.ts +33 -3
  67. package/scripts/config-merge/patch-json-key.ts +13 -6
  68. package/scripts/cost-runtime/cost-cli.ts +3 -7
  69. package/scripts/delegation/delegation-policy.ts +9 -1
  70. package/scripts/delegation/investigation-guard.ts +1 -1
  71. package/scripts/delegation/investigation-reads.ts +105 -19
  72. package/scripts/doctor/doctor-consumer.ts +3 -5
  73. package/scripts/doctor/doctor-io.ts +9 -13
  74. package/scripts/document/backlogrun-parent-read-set.ts +12 -1
  75. package/scripts/document/capped-print.ts +74 -0
  76. package/scripts/document/document-byte-budget.ts +1 -0
  77. package/scripts/document/document-section-cli.ts +4 -3
  78. package/scripts/document/entry-read-budget.ts +13 -7
  79. package/scripts/document/entry-read-set.ts +32 -8
  80. package/scripts/document/lane-child-read-set.ts +7 -0
  81. package/scripts/document/read-set-point-of-use.ts +8 -3
  82. package/scripts/document/read-set-trim.ts +3 -1
  83. package/scripts/epic/epic-audit.ts +3 -18
  84. package/scripts/gate/scoped-green.ts +25 -14
  85. package/scripts/gh/git-gh-issue-list.ts +12 -11
  86. package/scripts/gh/git-gh-issue-write.ts +4 -1
  87. package/scripts/gh/git-gh-pr-auto-merge.ts +4 -1
  88. package/scripts/git/git-command.ts +6 -1
  89. package/scripts/git/git-spawn-sync.ts +65 -0
  90. package/scripts/git/git-spawn.ts +5 -4
  91. package/scripts/git/stash/stash-pop-args.ts +1 -6
  92. package/scripts/init/claude-kit-only-settings.ts +29 -0
  93. package/scripts/init/init-copy-content.ts +8 -2
  94. package/scripts/init/init-file-action.ts +54 -0
  95. package/scripts/init/init-logic-migrate.ts +35 -17
  96. package/scripts/init/init-logic-secretlint.ts +1 -1
  97. package/scripts/init/init-logic.ts +6 -22
  98. package/scripts/init/init.ts +11 -68
  99. package/scripts/init/initial-commit.ts +24 -0
  100. package/scripts/init/kit-written-paths.ts +2 -0
  101. package/scripts/init/project-profile.ts +3 -6
  102. package/scripts/init/start-exec.ts +16 -3
  103. package/scripts/init/start-steps.ts +10 -13
  104. package/scripts/issue/defect-rate-cli.ts +61 -20
  105. package/scripts/issue/issue-backlinks.ts +7 -14
  106. package/scripts/issue/issue-cite.ts +12 -6
  107. package/scripts/issue/issue-file.ts +5 -12
  108. package/scripts/issue/issue-read-cli.ts +1 -0
  109. package/scripts/issue/issue-report-failures.ts +6 -2
  110. package/scripts/issue/issue-state-cli.ts +1 -1
  111. package/scripts/issue/markdown-section.ts +21 -6
  112. package/scripts/issue/measure-rerun-cli.ts +73 -14
  113. package/scripts/josh/josh-commands-lane.ts +1 -1
  114. package/scripts/josh/josh-commands-project.ts +8 -1
  115. package/scripts/josh/josh-commands-workflow.ts +1 -1
  116. package/scripts/josh/run-github-prerequisite.ts +3 -6
  117. package/scripts/lane/lane-await.ts +89 -15
  118. package/scripts/lane/lane-cli.ts +11 -13
  119. package/scripts/lane/lane-close.ts +4 -1
  120. package/scripts/lane/lane-launch-cli.ts +10 -16
  121. package/scripts/lane/openai-review-broker.ts +4 -1
  122. package/scripts/lib/cli-flags.ts +31 -2
  123. package/scripts/lib/error-message.ts +12 -1
  124. package/scripts/lines/line-targets.ts +0 -11
  125. package/scripts/lines/lines-command.ts +5 -3
  126. package/scripts/propagate/propagate-git.ts +3 -8
  127. package/scripts/refactor/refactor-scan.ts +2 -1
  128. package/scripts/registry-migration/migrate-logic.ts +28 -19
  129. package/scripts/registry-migration/migrate.ts +6 -5
  130. package/scripts/release/github-release-cli.ts +14 -3
  131. package/scripts/release/github-release-environment.ts +1 -0
  132. package/scripts/release/github-release.ts +103 -17
  133. package/scripts/release/release-publish.ts +36 -2
  134. package/scripts/release/release-worktree.ts +97 -2
  135. package/scripts/review/review-checkout.ts +3 -2
  136. package/scripts/review/review-record-cli.ts +9 -16
  137. package/scripts/rules/early-heartbeat.ts +4 -3
  138. package/scripts/rules/lane-park.ts +9 -0
  139. package/scripts/rules/stop-rules.ts +7 -4
  140. package/scripts/run/run-carry-args.ts +8 -12
  141. package/scripts/run/run-carry-cli.ts +3 -1
  142. package/scripts/run/run-carry-conversation.ts +94 -0
  143. package/scripts/run/run-carry-reclaim.ts +37 -0
  144. package/scripts/run/run-carry.ts +21 -5
  145. package/scripts/run/run-cut-args.ts +1 -6
  146. package/scripts/run/run-ending-cli.ts +1 -5
  147. package/scripts/run/run-entry-cli.ts +40 -9
  148. package/scripts/run/run-entry-stop.ts +104 -0
  149. package/scripts/run/run-event-stream-emit.ts +7 -2
  150. package/scripts/run/run-hold.ts +4 -1
  151. package/scripts/run/run-liveness-cli.ts +1 -5
  152. package/scripts/run/run-merge-cli.ts +2 -10
  153. package/scripts/run/run-merge-steps.ts +4 -1
  154. package/scripts/run/run-parent-cut-hook.ts +4 -1
  155. package/scripts/run/run-prep-cli.ts +4 -1
  156. package/scripts/run/run-progress-args.ts +20 -4
  157. package/scripts/run/run-progress-cli.ts +31 -29
  158. package/scripts/run/run-progress-clock.ts +4 -1
  159. package/scripts/run/run-ship-cli.ts +8 -16
  160. package/scripts/run/run-ship-probe.ts +4 -1
  161. package/scripts/run/run-status-cli.ts +2 -15
  162. package/scripts/run/run-tidy-lanes.ts +4 -1
  163. package/scripts/run/run-wake-cli.ts +2 -10
  164. package/scripts/run/run-watcher-guard.ts +9 -5
  165. package/scripts/safe-chain/pack-hook.ts +92 -0
  166. package/scripts/sync/sync-ai-files.ts +4 -0
  167. package/scripts/test/test-declared-changed.ts +13 -16
  168. package/scripts/test/test-declared.ts +3 -7
  169. package/scripts/test/test-related.ts +1 -6
  170. package/scripts/test/test-unit-guard.ts +21 -7
  171. package/scripts/time-runtime/time-spans.ts +4 -0
  172. package/scripts/version/version-commands.ts +11 -1
  173. package/templates/CLAUDE.basic.md +6 -6
  174. package/templates/workflows/ci.yml +16 -82
  175. package/templates/workflows/dependabot-auto-merge.yml +1 -0
  176. package/templates/workflows/github-release.yml +14 -28
  177. package/tsconfig.sonar.json +4 -22
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: investigator
3
+ description: Read-only investigation unit for the pre-implementation reading — finds out how an Issue's subject currently works and returns the conclusion with its `file:line` citations, never the file text. Dispatch it when `pnpm josh investigation:guard` asks for a delegated unit, or when `pnpm josh delegate investigation` answers `delegate`.
4
+ tools: Read, Grep, Glob, Bash
5
+ effort: low
6
+ ---
7
+
8
+ You are an investigation unit dispatched by a run that is about to implement an Issue. Your job is
9
+ to find out **how the subject currently works**, not what to change.
10
+
11
+ - Start from what the brief says the main line already concluded; do not re-read what it lists as read.
12
+ - Search and read freely — this context is yours, and keeping it out of the main line is why you exist.
13
+ - Never edit, stage or commit anything. A throwaway probe script may be written to the session
14
+ scratchpad, run, and deleted; return its output alone.
15
+ - Return the conclusion plus the `file:line` citations that support each claim. Never paste file
16
+ text beyond a short quoted line — the main line opens the cited lines to verify you.
17
+ - Report what the code does, never a root cause or a fix: deciding that stays in the main line.
@@ -42,6 +42,7 @@
42
42
  "env": {
43
43
  "BASH_MAX_OUTPUT_LENGTH": "8000"
44
44
  },
45
+ "enableArtifact": false,
45
46
  "hooks": {
46
47
  "SessionStart": [
47
48
  {
@@ -122,7 +122,7 @@ procedure.
122
122
  | Under `backlogrun`, a stop that would end a batch, or a named non-epic item | Park one child and continue; run a named non-epic item as a `fullrun` | `backlogrun-park.md` → "park and continue"; `backlogrun-child.md` → "When `#N` is not an epic" |
123
123
  | `backlogrun`'s authorization | The whole `auto-ok` opted-in pool as well as its named items; `pnpm josh backlog:next` offers them | `backlogrun.md` |
124
124
  | First call of any entry | `#N`: `pnpm josh run:entry <N> --to <command>` (claims the tree, except `kickoff`); `new` (not `kickoff`): `pnpm josh run:hold` | §2k; §2f → `working-tree-hold.md` |
125
- | Same turn as the hold (not a dispatched child, not `kickoff`) | `pnpm josh cost --cut`; `under` continues, `over` stops with a `confirmation` Telegram and the resume command, then `pnpm josh run:release` | `backlogrun-progress.md` → "The hand-off" (shared 135,000 threshold) |
125
+ | Same turn as the hold (not a dispatched child, not `kickoff`) | `pnpm josh cost --cut`; `under` continues, `over` stops — `run:entry` notifies and runs `run:release` (by hand for `new`) | `backlogrun-progress.md` → "The hand-off" (shared 135,000 threshold) |
126
126
  | Before any work starts (every entry) | The split assessment; the default is not to split — separability **and** a scope clearly over one gate must hold together; a `fullrun` / `halfrun` that finds a split files the epic and **stops** | `split-assessment.md` → "The question" |
127
127
  | Another Issue here must land first | A prerequisite is a dependency, not a park (the third of four mid-run discoveries) | §2d → `prerequisite.md` |
128
128
  | Something worth filing, none of the three | File it without asking (Tier A, first-party) and carry on; a delegated child returns it to the parent instead | §2i → `observation-filing.md` |
@@ -28,83 +28,39 @@ command finished.
28
28
 
29
29
  **Never hand-write a `while`/`until` … `sleep` loop over the output file.** The loop does not know
30
30
  when the command finished: a regex that never matches waits to the block limit, and one that matches
31
- a mid-run line exits too early — either way a full-context round trip is spent for nothing. Six lanes
32
- lost about 45 minutes, 13% of their wall clock, to exactly this (joshuafolkken/kit#2371), and in the
33
- worst case a lane grepped a merged PR's output file for 30 minutes after the merge had already
34
- landed. `pnpm josh rule:guard` refuses the poll loop and hands back this route, so the guarantee is a
35
- mechanism rather than prose to remember (`prompts/collaboration-workflow/rule-delivery.md`).
31
+ a mid-run line exits too early. `pnpm josh rule:guard` refuses the poll loop and hands back this route
32
+ (`prompts/collaboration-workflow/rule-delivery.md`). Rationale:
33
+ `docs/maintainers/background-commands-rationale.md` → "Why a poll loop is refused".
36
34
 
37
35
  **To glance at interim output, `Read` the output file once** — the notification names its path — and
38
36
  never in a sleep loop. If what you are waiting on is CI, `pnpm josh followup` waits on it for you in
39
37
  the foreground; a bare `sleep` that arms a progress heartbeat is `run:progress --wait`'s job, refused
40
- in front of a hand-armed timer by `early-heartbeat` (joshuafolkken/kit#1570). This loop is the case
41
- that rule leaves alone by design, because a loop ends on a condition rather than a clock — and here
42
- the condition it ends on is the wrong one.
43
-
44
- ## Decision record
45
-
46
- This document is read at its point of use, not at the entry: it binds only after the first edit —
47
- at the gate, the push, the CI wait and `pnpm josh followup` — so it is fetched in full, in the turn
48
- that reaches the first backgroundable command (`pnpm josh gate`), by the run that has to obey it
49
- (`SKILL.md` → §1, "Five documents are read at the point of use"). `SKILL.md` → §2h is the resident
50
- pointer to it, and carries the one thing it does not: the same rule at a batch's scale.
51
-
52
- **A run's idle time collects in its tail, and the two ways it collects there are one mistake**
53
- (joshuafolkken/kit#1510). `fullrun #1501` ran 45m03s on about 17 minutes of work. Of the 25 minutes
54
- nothing was running, **6m28s** was a `pnpm josh git -y` issued in the **foreground** with a
55
- 900-second tool timeout — above the harness's own 600-second cap, so the harness detached it at the
56
- cap and nothing read the output file for six and a half minutes afterwards — and **6m06s** was bare
57
- CI: the push landed, the turn ended, and the merge started only once the person asked whether it was
58
- merging. Both halves handed the deciding of *when to look back* to something that was never going to
59
- decide it.
60
-
61
- **Issue it in the background, and never give a foreground call a timeout above the harness cap.**
62
- The cap decides how long a foreground call waits, not the number passed to it, so a number above the
63
- cap chooses the detached path without choosing the completion notification that should come with it.
64
- A call issued detached from the start re-invokes the run when it exits — which is what makes the
65
- completion *delivered* rather than something to remember to poll for.
66
-
67
- The measured outliers were a 6m28s foreground `git -y`, 6m06s of unattended CI and a 19m26s push
68
- transport fault. The operational section above maps those waits to background gate/push and a
69
- foreground `followup`; `followup.md` remains its command procedure.
70
-
71
- **A tail does follow the merge, and it is not small** (joshuafolkken/kit#1462). This section first
72
- gave a different reason for that last bullet — that the merge ends the run, leaving no tail to
73
- overlap — and the measurement says otherwise. The run-timing report charges to **`post-run`** exactly what runs
74
- after the last `followup` span ends: **3.0 min, 5.9% of a 50.5-minute run**, in the lane child
75
- `--issue 1599` (PR #1602); **2 min 31 s, 14% of 20m15s**, in the plain `fullrun #1597` (PR #1603);
76
- **3.1 min** hand-measured in run #1441's delegated child, which is where this Issue started. **What
77
- was wrong is the premise, not the conclusion** — `followup` stays foreground, and what changes is
78
- *where the tail's work is done*.
79
-
80
- **So the tail is emptied before `followup` is issued, rather than worked through after it returns.**
38
+ in front of a hand-armed timer by `early-heartbeat`.
39
+
40
+ ## The tail and what overlaps each wait
41
+
42
+ **Never give a foreground call a timeout above the harness cap** — the cap decides how long a
43
+ foreground call waits, so a larger number detaches the call without the completion notification.
44
+ Rationale: `docs/maintainers/background-commands-rationale.md` → "Why the tail collects idle time".
45
+
46
+ **The tail is emptied before `followup` is issued, rather than worked through after it returns.**
81
47
  One question decides each step, and it is asked of the step rather than judged: **does it read the
82
48
  merge result?**
83
49
 
84
50
  - **It does — the step stays after `followup`.** `pnpm josh ms`, `pnpm josh issue:state <N>`,
85
51
  `pnpm josh epic:next`, `pnpm josh backlog:next --exclude <N>`, and `pnpm josh lane:close` /
86
- `pnpm josh lane:list`. Each is a verifier or is keyed to a merge that has to have happened, and
87
- bringing one forward would have it read a state nobody has reached yet. **De-duplicating a step is
88
- not removing it**: the parent reads the child's state from GitHub *because* a summary is not a
89
- verifier ("Each child runs in a delegated unit"), so none of these may be dropped or answered from
90
- memory.
52
+ `pnpm josh lane:list`. **De-duplicating a step is not removing it**: none of these may be dropped or
53
+ answered from memory.
91
54
  - **It does not — the step is composed in the turn that issues `followup`.** The epic progress
92
- comment's counter *values* (children run, Issues filed, consecutive failures, the run's start time)
93
- are all counted inside the run, and the completion report body is already
94
- placed beside `pnpm josh git -y` in the table below. **Only the write follows the merge.** The
95
- comment exists because a compaction takes the counters at a moment nobody chooses (`backlogrun-progress.md` →
96
- "The counters live in the record"), so composing the values earlier moves no write and loses
97
- no counter.
98
- - **`pnpm josh cost --cut` stays after the merge, and reads nothing from it.** It measures
99
- this session's own transcript, so the question above would bring it forward — but its answer grows
100
- with the session, and asking it a call early under-reads the very number the hand-off is decided
101
- on. It is seconds of tail against a guard on session size, so it keeps its documented seam
102
- (`backlogrun-progress.md` → "The check is asked at every merge").
103
-
104
- **What runs beside a backgrounded command is the work that writes nothing to the working tree.** That
105
- is the whole test, and it is the same one that lets the gate and the review overlap (`SKILL.md` →
106
- §2): a step that edits makes the background command's result stale, so it is not something to overlap
107
- with. Applied to the three waits a run actually has:
55
+ comment's counter *values* and the completion report body (placed beside `pnpm josh git -y` in the
56
+ table below). **Only the write follows the merge.**
57
+ - **`pnpm josh cost --cut` stays after the merge, and reads nothing from it** — its answer grows with
58
+ the session, so it keeps its documented seam (`backlogrun-progress.md` → "The check is asked at
59
+ every merge").
60
+
61
+ **What runs beside a backgrounded command is the work that writes nothing to the working tree** — a
62
+ step that edits makes the background command's result stale. Applied to the three waits a run
63
+ actually has:
108
64
 
109
65
  | While this runs | Do this beside it |
110
66
  | --------------- | ----------------- |
@@ -115,26 +71,21 @@ with. Applied to the three waits a run actually has:
115
71
 
116
72
  **The turn never ends at the push.** The completion notification for `pnpm josh git -y` is what
117
73
  resumes the run, and the turn that reads it goes straight through any branch-2 filing and
118
- `pnpm josh epic:bundle` to `pnpm josh followup`. joshuafolkken/kit#1333 had already settled that end
119
- state for a clean second round, and symptom 2 above is its regression — **so the guarantee is a
120
- mechanism and not only the procedure**: `pnpm josh rule:guard` refuses the foreground push step and
121
- states both halves at that call (`prompts/collaboration-workflow/rule-delivery.md`). A turn *ending*
122
- is the absence of a call and no `PreToolUse` hook can see one, so the last call before the seam is
123
- where the rule can be put; the push reissued detached is not refused again, so a run that obeys pays
124
- nothing.
125
-
126
- **One turn does end before the push, and only one: a dispatched lane child's pre-gate cut**
127
- (joshuafolkken/kit#1839). It ends the turn before the gate and relaunches a fresh process in the same
128
- act, so the run continues rather than stalling — the sanctioned boundary distinct from the push
129
- turn-end this section forbids. The boundary, its two commands and the resume verification are the
130
- `pre-gate-cut.md` skill document, its single source.
74
+ `pnpm josh epic:bundle` to `pnpm josh followup`. `pnpm josh rule:guard` refuses the foreground push
75
+ step and states both halves at that call (`prompts/collaboration-workflow/rule-delivery.md`); the push
76
+ reissued detached is not refused again. Rationale: `docs/maintainers/background-commands-rationale.md`
77
+ → "Why the push is guarded".
78
+
79
+ **One turn does end before the push, and only one: a dispatched lane child's pre-gate cut.** It ends
80
+ the turn before the gate and relaunches a fresh process in the same act — `pre-gate-cut.md` → "Taking
81
+ the cut" is its single source.
131
82
 
132
83
  **A headless lane child (`claude -p`) is where "a background command re-invokes the run" does not
133
- hold: its background Bash tasks are killed when its turn ends** (joshuafolkken/kit#2704). It hands the
134
- gate-to-merge region to a foreground `pnpm josh ship --detach` instead (`chain-rule.md` step 0) —
135
- its preflight runs before the supervisor exists, so a backgrounded `ship` dies with the turn
136
- (joshuafolkken/kit#3027); `pnpm josh rule:guard` refuses a backgrounded `josh gate` / `git` /
137
- `followup` / `ship` there, and the `Stop` hook sends a child with a task still running back to wait.
84
+ hold: its background Bash tasks are killed when its turn ends.** It hands the gate-to-merge region to
85
+ a foreground `pnpm josh ship --detach` instead (`chain-rule.md` step 0) — its preflight runs before the
86
+ supervisor exists, so a backgrounded `ship` dies with the turn; `pnpm josh rule:guard` refuses a
87
+ backgrounded `josh gate` / `git` / `followup` / `ship` there, and the `Stop` hook sends a child with a
88
+ task still running back to wait.
138
89
 
139
90
  The operational section above is the single source of the rule. `followup.md`, `chain-rule.md` and
140
91
  `backlogrun-progress.md` → "Progress while the run is quiet" route here for it rather than restating it.
@@ -18,14 +18,16 @@ by that supervisor; an AI parent is started only for a driver branch that requir
18
18
  pnpm josh run:progress --wait --output <the transcript path of each delegated unit> # in the background
19
19
  ```
20
20
 
21
- **`--wait` waits one silence interval out, prints one line and exits** — the long-running form (no
22
- `--wait`) is right only where output is streamed. The parent changes no value on the line. **Every
23
- interval is three moves and no fourth:**
21
+ **Start it once; it reports by itself** (joshuafolkken/kit#3102). Each silence interval appends the
22
+ five labelled lines to the run's event stream as a heartbeat and to the ambient log, prints nothing to
23
+ standard output and keeps running — so a scheduled report never wakes the parent, and the parent
24
+ relays none. **It exits only on one of three, and the command computes which:**
24
25
 
25
- 1. Start `pnpm josh run:progress --wait --output <transcript paths>` **in the background**.
26
- 2. When it exits, **present what it printed as-is** — the command prints the five labelled lines and the
27
- next report time itself.
28
- 3. **In that same turn, start the next one.** The interval is measured from the last report.
26
+ | Exit | What the parent does |
27
+ | ------------------------------------------------------- | ---------------------------------------------------------------------- |
28
+ | Newly runnable backlog work (the `ready #N` line) | Read the ready line, act on it, and restart the watcher in that turn |
29
+ | `pnpm josh followup` removed the run's life record | Nothing — the run has ended |
30
+ | `--hours` elapsed (8 by default for `--wait`) | Restart the watcher in that turn |
29
31
 
30
32
  **`pnpm josh run:watcher:guard` detects a missed restart** (joshuafolkken/kit#2113): it refuses when
31
33
  lane children are in-flight but the watcher's life record has gone stale. **It is wired into
@@ -33,18 +35,18 @@ lane children are in-flight but the watcher's life record has gone stale. **It i
33
35
  until the watcher is restarted — once per run, so the `pnpm josh run:progress --wait` that fixes it is
34
36
  not blocked.
35
37
 
36
- **`run:progress` prints the five labelled lines itself (joshuafolkken/kit#2026); present them as-is.** It emits the observation instant (`at`, local first and UTC beside it), the elapsed figures
38
+ **`run:progress` writes the five labelled lines itself (joshuafolkken/kit#2026).** It emits the observation instant (`at`, local first and UTC beside it), the elapsed figures
37
39
  (`quiet` and `unchanged`), the children in flight, the run state (`lanes`, `load`, `record`), and the
38
- scheduled `next`, each behind its own label and on its own line — so relay the output verbatim and
39
- round, rephrase or re-label nothing. How each field reads when it has nothing to report (`record
40
+ scheduled `next`, each behind its own label and on its own line — so where a session does present them
41
+ (`--once`), it relays the output verbatim and rounds, rephrases or re-labels nothing. How each field reads when it has nothing to report (`record
40
42
  unread`, `no in-progress child yet`, `lanes none`, an absent `next`) is fixed by the command's own unit
41
43
  tests. **A value said that way is still an observation, never a state**: `record unread` means no
42
44
  `--output` path was given, not *not stalled*; `lanes none` is *no lane is open*, not *nothing is
43
45
  running*; and the `next` line is a schedule — the time *if the silence continues*, superseded when a
44
46
  real report resets the clock through `--mark`.
45
47
 
46
- **It exits only when it has a line to hand over**, or when `--hours` runs out having never gone quiet
47
- for a whole interval (that exit says so on standard error). **Nothing in flight keeps it waiting.**
48
+ **A report is not an exit, and neither is a decline** — with no run recorded it keeps waiting, and the
49
+ `--hours` exit says so on standard error.
48
50
 
49
51
  **`--mark` at every real report.** Whenever this loop reports something of its own — a child merged,
50
52
  parked, a stop — run `pnpm josh run:progress --mark` in the same turn to restart the silence clock. The clock is silence, never a timer
@@ -130,21 +132,13 @@ in the same turn as each: **the Step 0 work summary, the pull request opening, e
130
132
  verdict, and any `confirmation` / `failure` / `completion` notification or stop.** **A tool result only
131
133
  you read is not one** — a gate run, a `gh` read, an edit.
132
134
 
133
- **On a quiet tick the turn is the relayed line plus at most two lines of the run's own prose**, saying
134
- only what changed stage and what is being waited on. **A tick is quiet when none of the real reports
135
- happened since the last one**; a gate that went green, a review round, a file edited, a poll answering
136
- `wait` are the run working, not a report. **No table, no re-listing of children, no restating of the
137
- plan** — `epic:next`, the epic body and the progress comment hold all three. **A real report is not
138
- bounded by that**: `--mark` restarts the clock, so a merge, a park or a stop is where the run may be
139
- long.
140
-
141
135
  **`--output` is omitted in a single-issue run, and `record` reads `unread`.** Rationale for the
142
136
  watcher's scope and placement: `docs/maintainers/backlogrun-progress-rationale.md` → "Why the watcher
143
137
  runs where it does".
144
138
 
145
139
  **A run that merges needs no teardown; a run that stops has to end the reporting itself.** **A stop
146
140
  keeps the `in-progress` label on purpose** — so **no further `--wait` is started** after the stop
147
- notification, and any long-running watcher still in the background is stopped in the same turn. That
141
+ notification, and the running `--wait` is stopped in the same turn, since no report ends it. That
148
142
  covers `halfrun`'s stop before commit, a `needs-human-review` stop, a split or prerequisite stop, and
149
143
  a `backlogrun` named issue's failure stop.
150
144
 
@@ -183,10 +177,10 @@ run in lanes".
183
177
  that lane's `lane:open`.
184
178
 
185
179
  **Start the unit without blocking on it — `pnpm josh lane:dispatch <N>` when the child runs in a
186
- lane — and in the same turn start `pnpm josh lane:await <N...>` in the background** (joshuafolkken/kit#2113).
180
+ lane — and in the same turn start `pnpm josh lane:await <N...> --owner "$PPID"` in the background** (joshuafolkken/kit#2113).
187
181
  `lane:await` watches local process presence and exits when any named child confirms-complete. **The
188
182
  re-confirm delay and poll interval are the command's, not the agent's** — pass only the issue
189
- numbers. Without that wake (Codex), hand off instead — "A parent without a completion callback".
183
+ numbers and `--owner` (why: `docs/josh-commands-automation.md` → `josh lane:await`). Without that wake (Codex), hand off instead — "A parent without a completion callback".
190
184
 
191
185
  **A `lane:await` wake is a confirmed-gone process, so classify the ending at once — do not wait out
192
186
  the silent-unit window** (joshuafolkken/kit#2277). On a `lane:await` wake go straight to `pnpm josh run:merge <N> --output <path>` — the composite reads the child's **exit
@@ -275,11 +269,12 @@ run in lanes".
275
269
  a session that cannot measure is read as `over`. **Never read the condition off `pnpm josh delegate
276
270
  epic-child`** ("The check is asked at every merge" below).
277
271
  3. **`wait`** — go back to step 1. **With something of this run's own in flight, that happens on the
278
- wake the progress watcher's exit delivers** and the parent starts no sleep of its own; the 60 s
279
- figure bounds how soon the ask may be repeated. **With nothing in flight the watcher declines and
280
- never exits, so the parent keeps the interval** — the case for "another repository has work but this
281
- one does not", and for a cross-repository publish wait. The table in "The wake exists only while
282
- something is in flight" below decides which.
272
+ wake `lane:await` or the progress watcher's arrival exit delivers** and the parent starts no sleep
273
+ of its own; the 60 s figure bounds how soon the ask may be repeated. **A scheduled report is not one
274
+ of those wakes** (joshuafolkken/kit#3102). **With nothing in flight neither wake comes, so the parent
275
+ keeps the interval** — the case for "another repository has work but this one does not", and for a
276
+ cross-repository publish wait. "The wake exists only while something is in flight" below decides
277
+ which.
283
278
  4. **`stop`** — report the parked children and finish.
284
279
  5. **`complete`** — post the epic summary and finish.
285
280
  6. **Exit code 1** — `epic:next` refused a cyclic or contradictory graph, or could not read a child.
@@ -421,8 +416,8 @@ in the background, in the same turn as that first `lane:list`. The wired `run:wa
421
416
 
422
417
  **A carried-over child finishes in its own detached unit, and the resumed parent does not stand in front
423
418
  of its merge.** A lane handed over at the cut is still running its own `fullrun` — the foreground
424
- `pnpm josh followup` and the CI wait included — in a process of its own (background-commands.md →
425
- "`pnpm josh followup` — foreground": foreground is *within the unit*, the background from the parent).
419
+ `pnpm josh followup` and the CI wait included — in a process of its own (`background-commands.md` →
420
+ "Background the gate and push": foreground is *within the unit*, the background from the parent).
426
421
  **So the parent never runs a carried-over child's `followup` itself**: it polls the handed-over lane and
427
422
  opens new work beside it. **The reads and the dispatches go out together, in one turn** — `pnpm josh lane:list`,
428
423
  `pnpm josh run:liveness`, `epic:next --lanes`, and opening a lane for a child it offers take none of each
@@ -472,7 +467,7 @@ clock". A stale child's label is
472
467
  removed first, so the next poll can offer it. **A graph that has deadlocked on a cycle is not this loop's
473
468
  to untangle**: `epic:next` detects it and exits with an error.
474
469
 
475
- ### The parent keeps no clock of its own — the watcher's exit is the wake
470
+ ### The parent keeps no clock of its own — a child's completion or an arrival is the wake
476
471
 
477
472
  **After hand-off, the supervisor's driver owns the mechanical clock.** `backlog:drive` polls lane
478
473
  completion and the backlog inside the detached process, without waking an AI parent; the parent-turn
@@ -483,12 +478,14 @@ source and `run:report` the final report.
483
478
  `docs/maintainers/backlogrun-progress-rationale.md` → "Why the parent waits on classification, not a
484
479
  clock".
485
480
 
486
- The watcher reports on its own interval, and a judgment session may take its exit as a prompt to act.
481
+ The watcher reports on its own interval to the event stream and exits only on an arrival, so a judgment
482
+ session takes that exit — or a `lane:await` completion — as its prompt to act, never a report.
487
483
 
488
484
  #### The wake exists only while something is in flight
489
485
 
490
- `run:progress --wait` prints only when it has a human-facing line. With no child in flight it may
491
- decline; the driver still polls for new work and enforces the idle and whole-run bounds. A GitHub
486
+ `lane:await` wakes on a child's completion and `run:progress --wait` on an arrival; its reports wake
487
+ nobody. With no child in flight it may decline; the driver still polls for new work and enforces the
488
+ idle and whole-run bounds. A GitHub
492
489
  listing failure stays a retry or unreadable answer from the offer command, never an empty backlog.
493
490
 
494
491
  `epic:next` does not report when a label was applied, so read that from the issue's timeline:
@@ -365,7 +365,7 @@ subsection.
365
365
  | Answer | What to do | Budget answer |
366
366
  | --- | --- | --- |
367
367
  | One or more issue numbers | Start each one as a child, up to the free lanes — `backlogrun-lanes.md` → "Lanes" and "Each child runs in a delegated unit", unchanged. Then **ask the command again**, with the merged numbers added to `--exclude` | `candidates` |
368
- | `wait`, with something of this run's own still in flight | Everything opted in is blocked or already running, so waiting can still change the answer. **Ask the command again on the wake the progress watcher's exit delivers** — the parent starts no sleep of its own, and the interval is a floor on the re-ask rather than a clock (`backlogrun-progress.md` → "Waiting, and never waiting forever" and "The parent keeps no clock of its own") | `blocked` |
368
+ | `wait`, with something of this run's own still in flight | Everything opted in is blocked or already running, so waiting can still change the answer. **Ask the command again on the in-flight wake — `pnpm josh lane:await` or the progress watcher's arrival exit, never a report** — the parent starts no sleep of its own, and the interval is a floor on the re-ask rather than a clock (`backlogrun-progress.md` → "Waiting, and never waiting forever" and "The parent keeps no clock of its own") | `blocked` |
369
369
  | `wait` this checkout can never resolve — the only candidates the command reported on standard error are in other repositories, and this run has nothing of its own in flight | Report those candidates with their checkouts. **Waiting cannot resolve them, but a person opting a new issue in here still can**, so the ending is the idle watch's rather than this row's | `exhausted` |
370
370
  | `stop` | Nothing can proceed without a person. Report the parked issues and finish | `parked` |
371
371
  | `triage` | A candidate lacks `run:solo` / `run:lane` (joshuafolkken/kit#2779). Judge each one stderr names as the pre-start pass does, then **ask again**; running children continue | `untriaged` |
@@ -451,7 +451,7 @@ tracks.
451
451
  | Verdict | What the loop does |
452
452
  | --- | --- |
453
453
  | `run` | Start what `backlog:next` offered, up to the free lanes. The reason names how many more the maximum still allows; start no more than that |
454
- | `watch` | Ask both commands again — at the 5-minute idle poll while the backlog is empty **and** nothing of this run's is in flight, and at the 60-second polling interval in every other case (a blocked backlog, a drain, or a watch that opened while children were still merging). *Whether the parent keeps that interval as a clock of its own* is the in-flight test: with nothing in flight the watcher declines for the whole watch and never exits, so the parent sleeps the interval itself; with something in flight the wake is the watcher's exit and the interval is only a floor on the re-ask (`backlogrun-progress.md` → "The wake exists only while something is in flight"). **Nothing is held while watching** — the working tree's hold and each drained lane were released at the last child's merge, so a watching run blocks no other run |
454
+ | `watch` | Ask both commands again — at the 5-minute idle poll while the backlog is empty **and** nothing of this run's is in flight, and at the 60-second polling interval in every other case (a blocked backlog, a drain, or a watch that opened while children were still merging). *Whether the parent keeps that interval as a clock of its own* is the in-flight test: with nothing in flight the watcher declines for the whole watch and never exits, so the parent sleeps the interval itself; with something in flight the wake is `pnpm josh lane:await` or the watcher's arrival exit — never a report — and the interval is only a floor on the re-ask (`backlogrun-progress.md` → "The wake exists only while something is in flight"). **Nothing is held while watching** — the working tree's hold and each drained lane were released at the last child's merge, so a watching run blocks no other run |
455
455
  | `stop` | Report and finish. The reason it printed **is** the termination reason the completion report carries |
456
456
 
457
457
  **At the drain the retrospective fires before this watch, not after it** (joshuafolkken/kit#2335):
@@ -40,7 +40,11 @@ a temporary probe script and return its output.
40
40
  **The threshold is 3 files, and it is a count, not a forecast.** When the next read reaches it,
41
41
  delegate the unread investigation. Brief the unit on what was already read; do not re-read it there.
42
42
  **A delegation resets the counter rather than spending it**; `pnpm josh investigation:guard` counts
43
- unedited files and refuses the threshold read. `pnpm josh delegate --list` prints the threshold.
43
+ unedited files and search turns and refuses the threshold call. `pnpm josh delegate --list` prints
44
+ the threshold.
45
+
46
+ **Dispatch the unit as the `investigator` agent** (`kit:investigator` in a consumer) — the parent's
47
+ model at a lowered effort, read-only.
44
48
  `docs/josh-commands-automation.md` → "`josh investigation:guard`" carries the command's counting details.
45
49
 
46
50
  **The main line does not idle while the unit reads:** read files this run will edit, then verify the
@@ -32,8 +32,8 @@ call reports `hold`.
32
32
  `pnpm josh run:release`: `gh api -X DELETE repos/{owner}/{repo}/issues/<N>/labels/in-progress
33
33
  2>/dev/null || true`. A `needs-human-review` stop keeps both hold and label.
34
34
  3. **Ask the session boundary in the same turn as the hold** — `pnpm josh cost --cut`. `under`
35
- continues; `over` (or unanswerable) stops before the title with a `confirmation` Telegram carrying
36
- the figure and the resume command (`fullrun #<N>` / `fullrun new`), then `pnpm josh run:release`.
35
+ continues; `over` (or unanswerable) stops before the title — `run:entry` sends this and step 1's
36
+ `confirmation` Telegram and runs `run:release` (`stop notified: …`); by hand only for `new`.
37
37
  **Skip it when dispatched by `backlogrun`** (that batch owns the question). `backlogrun-progress.md`
38
38
  → "The hand-off" is the single source of the check and the shared 135,000 threshold.
39
39
  4. **Gather the mechanical reads — `pnpm josh run:prep <N>`**: `issue:read`'s body/comments
@@ -51,7 +51,7 @@ call reports `hold`.
51
51
  ## The progress step and the lane-child seam
52
52
 
53
53
  - **Start the progress watcher once the hold is claimed** — `pnpm josh run:progress --wait` in the
54
- background, presented as-is when it exits, and `pnpm josh run:progress --mark` in the same turn as
54
+ background, started once and relayed never, and `pnpm josh run:progress --mark` in the same turn as
55
55
  every real report (`backlogrun-progress.md` → "Progress while the run is quiet"). A `fullrun`
56
56
  dispatched as a lane child starts none and never reads that document (`pnpm josh read:set
57
57
  lane-child`). **The watcher is a per-session heartbeat; the run's report surface is not it** — the
@@ -62,7 +62,8 @@ call reports `hold`.
62
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
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.
65
- `pre-gate-cut.md` is the single source of both.
65
+ `pre-gate-cut.md` → "Resuming — the fresh process's entry check" and `pre-gate-cut.md` → "Taking
66
+ the cut" are the single sources of the two.
66
67
  - **`resume: halfrun`** adopted a stopped `halfrun` (#2796): gate in full; `resume: prrun-*` a stopped
67
68
  `prrun` (#3023); `working-tree-hold.md`.
68
69
 
@@ -13,18 +13,19 @@ that holds its detail.
13
13
 
14
14
  1. **Claim the working tree first** — `pnpm josh run:entry <N> --to halfrun` (bare `pnpm josh
15
15
  run:hold` for `new`), ahead of the title and a `new` filing; it folds steps 3 and 5 and starts
16
- at its stage line's `start:` (`SKILL.md` → §2k). `busy` / `unknown` stop with a `confirmation` Telegram. **The stop
16
+ at its stage line's `start:` (`SKILL.md` → §2k). `busy` / `unknown` stop with a `confirmation` Telegram (`run:entry` sends it). **The stop
17
17
  before commit keeps the hold** (a second run would trample the uncommitted work); a stop on a
18
18
  split, prerequisite or third-party target releases it. `working-tree-hold.md` is the single source; a
19
19
  cross-repository target resolves its checkout from `pnpm josh doctor` first (`target-repository.md`).
20
20
  2. **Apply `in-progress` the moment `run:hold` answered `hold`** — the stop before commit keeps it on;
21
21
  a stop that leaves the tree clean removes it in the same turn as `pnpm josh run:release` (`gh api -X
22
22
  DELETE repos/{owner}/{repo}/issues/<N>/labels/in-progress 2>/dev/null || true`).
23
- 3. **Ask the session boundary** — `pnpm josh cost --cut` (folded into `run:entry`); `over` stops with a `confirmation` Telegram and the resume command (`halfrun #<N>` /
24
- `halfrun new`). **Skip it when dispatched by a batch.** `backlogrun-progress.md` → "The hand-off" is
23
+ 3. **Ask the session boundary** — `pnpm josh cost --cut` (folded into `run:entry`); `over` stops — `run:entry` notifies and releases
24
+ (`halfrun new`: by hand). **Skip it when dispatched by a batch.** `backlogrun-progress.md` → "The hand-off" is
25
25
  the single source of the check and the shared 135,000 threshold.
26
26
  4. **Start the progress watcher once the hold is claimed** — `pnpm josh run:progress --wait` in the
27
- background, `--mark` in the same turn as every real report; the stop before commit ends the watcher
27
+ background, started once — it reports to the event stream itself and nothing is relayed — and
28
+ `--mark` in the same turn as every real report; the stop before commit ends the watcher
28
29
  itself (`backlogrun-progress.md` → "Progress while the run is quiet").
29
30
  5. **The mechanical reads** — `run:entry` folds `run:prep` (body/comments — `SKILL.md` → §2g;
30
31
  `human_review`; `latest:scope`); a `new` entry runs `pnpm josh run:prep <N>` once filed.
@@ -64,21 +64,13 @@ merge is CI**: the `Security Audit` job runs on every pull request and is one of
64
64
  whatever this gate answered locally. The local reading is a head start on a failure CI would catch
65
65
  anyway; it was never the only net.
66
66
 
67
- ## Why an elapsed-time window rather than "once per batch"
68
-
69
- The batch entry points had already hoisted the update to the head of a batch, and that hoist is
70
- correct — but it says nothing about a standalone `fullrun`, which **is** the head of its own
71
- one-issue batch and therefore updated on every invocation. A session that runs six issues one at a
72
- time paid the full cost six times while a batch of the same six paid it once, for no difference
73
- anybody chose. An elapsed-time window is the one condition that reads the same at every entry point,
74
- so no entry needs a rule of its own — and the batch hoists survive it unchanged, because a batch's
75
- second child asks the same command and is told `skip`.
76
-
77
- **The lock file the update rewrites still lands with whichever issue ran it.** `josh latest` leaves
78
- `pnpm-lock.yaml` modified and that issue's `pnpm josh git -y` commits it, exactly as before. What the
79
- window removes is the other runs carrying the same bumps; it does not make that one diff clean.
80
- Should the issue then fail CI on a bump rather than on its own change, that is a dependency problem
81
- found once — fix it forward before parking the issue for it.
67
+ ## The lock file lands with the issue that ran the update
68
+
69
+ **The lock file the update rewrites lands with whichever issue ran it.** `josh latest` leaves
70
+ `pnpm-lock.yaml` modified and that issue's `pnpm josh git -y` commits it. Should the issue then fail
71
+ CI on a bump rather than on its own change, fix it forward before parking the issue for it. Rationale
72
+ for the elapsed-time window: `docs/maintainers/latest-gate-rationale.md` → "Why an elapsed-time
73
+ window rather than once per batch".
82
74
 
83
75
  This file is the single source of the rule. `fullrun.md`, `halfrun.md`, `backlogrun.md` and `backlogrun.md`
84
76
  each name `pnpm josh latest:scope` at the point their procedure reaches it and route here for
@@ -69,9 +69,8 @@ pnpm josh run:cut <N>
69
69
  **`cut` is the only verdict that ends the turn.** Every other one — `under-threshold` included — leaves
70
70
  the current process to carry the run on itself.
71
71
 
72
- ### The gate refuses until the cut has been taken (joshuafolkken/kit#1864)
72
+ ### The gate refuses until the cut has been taken
73
73
 
74
- **This step was carried as prose and fired exactly never.** In measured lane children the cut was taken **0 times**, so
75
74
  `pnpm josh rule:guard` **refuses `pnpm josh gate`** while this checkout is a lane and no cut record is
76
75
  carried, handing back the command above. Rationale:
77
76
  `docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
@@ -165,13 +164,10 @@ hand-off". The parent's seam and the child's `run_cut.IMPLEMENTATION_CONTEXT_THR
165
164
  **135_000** `CONTEXT_CUT_THRESHOLD` (aliased so the tests cannot drift). No separate measurement is
166
165
  built for the lane child.
167
166
 
168
- ### It is a guard, fired at the edit that crosses the threshold (joshuafolkken/kit#2310)
167
+ ### It is a guard, fired at the edit that crosses the threshold
169
168
 
170
- **This step was carried as prose and fired exactly never** — the cut fired **0 times** while 33.9% of
171
- lane requests ran past 200,000 tokens. Rationale:
172
- `docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
173
-
174
- So `pnpm josh rule:guard` **refuses an `Edit` / `Write`** while this checkout is a dispatched lane child
169
+ Rationale: `docs/maintainers/pre-gate-cut-rationale.md` → "Why the rules are guards, not prose".
170
+ `pnpm josh rule:guard` **refuses an `Edit` / `Write`** while this checkout is a dispatched lane child
175
171
  whose recent-context cost is over threshold, handing back `pnpm josh run:cut --impl <N> --handoff <path>` — the
176
172
  refusal lands _before_ the edit. `cut` ends the turn, the rest leave this process implementing; a fresh
177
173
  process's `pnpm josh run:cut --resume <N>` then answers **`resume-impl`**, so it **skips the title, plan,
@@ -33,27 +33,13 @@ does. **There is no second criterion.** §2i's observation, the review round cap
33
33
  prerequisite each route a second filing through it, delivered by `pnpm josh rule:guard` at the run's
34
34
  second `pnpm josh issue:file` call, never the first.
35
35
 
36
- ## Why the default is not to split
37
-
38
- **The rules were manufacturing Issues faster than runs could close them.** Execution was never the
39
- bottleneck; arrival was, and this assessment was one of the routes producing it: `route:split`
40
- accounted for **28 of the 119 open Issues (24%)**.
41
-
42
- **Separability is not scarce, which is why a test made only of it splits nearly everything.** Almost
43
- any request can be described as several deliverables that could each ship alone, so the old test bit on
44
- requests worth a handful of lines. The second question is what makes the test bite only where one Issue
45
- would genuinely be unverifiable in one pass. The review round cap's default and the WIP cap changed
46
- alongside it, in one commit.
47
-
48
36
  ## Diff size is not a reason to split, and that is measured
49
37
 
50
38
  **Size is a bar a split has to clear; it is never a reason to split.** Question 2 is a *minimum* — below
51
- it the work stays whole — and nothing makes a large diff a reason to divide one. Splitting an Issue in
52
- two to shorten review round 1 lengthens it, because each Issue pays round 1's fixed cost again while
53
- the size-dependent part is merely divided between the halves (measured in joshuafolkken/kit#1436:
54
- round 1's cost is dominated by a fixed part that two Issues pay twice, so there is no diff size at
55
- which splitting to cut it pays). **A proposal to add a size threshold that splits more is required to
56
- say why that data does not reach it.**
39
+ it the work stays whole — and nothing makes a large diff a reason to divide one. **A proposal to add a
40
+ size threshold that splits more is required to say why the round-1 measurement does not reach it.**
41
+ Rationale: `docs/maintainers/split-assessment-rationale.md` → "Why the default is not to split" and
42
+ `docs/maintainers/split-assessment-rationale.md` → "Why diff size is not a reason to split".
57
43
 
58
44
  **The size half is measured, not eyeballed — `pnpm josh split:assess`.** It counts the branch's
59
45
  changed files and changed lines with test files excluded and answers `split` / `single` on size
@@ -0,0 +1,33 @@
1
+ name: Setup Node.js
2
+ description: >
3
+ Set Node.js up at the version every kit workflow runs on, optionally pointed at a package
4
+ registry.
5
+
6
+ # The one place the Node.js version of kit's workflows is written (joshuafolkken/kit#3084). Every
7
+ # job used to call actions/setup-node with its own `node-version` literal, so a version move had to
8
+ # find all of them. A job that needs a different runtime says so explicitly — `node-26-pnpm` in
9
+ # `ci.yml` passes its own runtime to `.github/actions/setup-pnpm` instead of calling this.
10
+ #
11
+ # Distributed: `josh sync` writes this file into a consumer beside the workflows that call it, the
12
+ # same way as `.github/actions/setup-pnpm`. `workflow_pin_logic` reads this file as a source of
13
+ # canonical action pins.
14
+
15
+ inputs:
16
+ registry-url:
17
+ description: Passed to actions/setup-node's `registry-url`; empty writes no `.npmrc`.
18
+ required: false
19
+ default: ''
20
+ scope:
21
+ description: Passed to actions/setup-node's `scope`, for a scoped registry.
22
+ required: false
23
+ default: ''
24
+
25
+ runs:
26
+ using: composite
27
+ steps:
28
+ - name: Setup Node.js
29
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
30
+ with:
31
+ node-version: '24'
32
+ registry-url: ${{ inputs.registry-url }}
33
+ scope: ${{ inputs.scope }}
@@ -10,10 +10,10 @@ description: >
10
10
  # job that calls this action cannot install without the scan.
11
11
  #
12
12
  # Distributed: `josh sync` writes this file into a consumer beside the workflows that call it
13
- # (`pr-classification.yml`), stamped as managed like they are (joshuafolkken/kit#3013).
14
- # `templates/workflows/ci.yml` still carries the same steps inline — it was written before this file
15
- # was distributed, and moving it onto the action is a change of its own. `workflow_pin_logic` reads
16
- # this file as a source of canonical action pins, so the template's refs still resolve from here.
13
+ # (`ci.yml` and `pr-classification.yml`), stamped as managed like they are (joshuafolkken/kit#3013).
14
+ # The distributed `ci.yml` calls it exactly as kit's own does, so a consumer's CI prepares pnpm on
15
+ # the path kit's CI runs (joshuafolkken/kit#3095). `workflow_pin_logic` reads this file as a source
16
+ # of canonical action pins.
17
17
 
18
18
  inputs:
19
19
  install:
@@ -8,6 +8,7 @@ jobs:
8
8
  create-tag:
9
9
  name: Create Git Tag if Version Changed
10
10
  runs-on: ubuntu-latest
11
+ timeout-minutes: 10
11
12
  permissions:
12
13
  actions: write
13
14
  contents: write
@@ -19,9 +20,7 @@ jobs:
19
20
  fetch-depth: 2
20
21
 
21
22
  - name: Setup Node.js
22
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
23
- with:
24
- node-version: '24'
23
+ uses: ./.github/actions/setup-node
25
24
 
26
25
  - name: Check if package.json changed
27
26
  id: pkg_changed