@joshuafolkken/kit 1.1136.0 → 1.1178.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 (200) hide show
  1. package/.claude/settings.json +4 -4
  2. package/.claude/skills/epic-commands/SKILL.md +27 -239
  3. package/.claude/skills/epic-commands/epic-bundle.md +115 -0
  4. package/.claude/skills/epic-commands/execution-waves.md +126 -0
  5. package/.claude/skills/workflow-commands/SKILL.md +48 -194
  6. package/.claude/skills/workflow-commands/background-commands.md +1 -1
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +14 -63
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +16 -15
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -173
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +65 -144
  13. package/.claude/skills/workflow-commands/backlogrun.md +5 -4
  14. package/.claude/skills/workflow-commands/chain-rule.md +3 -2
  15. package/.claude/skills/workflow-commands/delegation.md +2 -2
  16. package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
  17. package/.claude/skills/workflow-commands/followup.md +104 -181
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
  19. package/.claude/skills/workflow-commands/fullrun.md +16 -73
  20. package/.claude/skills/workflow-commands/halfrun.md +10 -36
  21. package/.claude/skills/workflow-commands/into-target.md +4 -4
  22. package/.claude/skills/workflow-commands/issue-comments.md +7 -8
  23. package/.claude/skills/workflow-commands/issue-scout.md +6 -6
  24. package/.claude/skills/workflow-commands/kickoff.md +6 -6
  25. package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
  26. package/.claude/skills/workflow-commands/observation-filing.md +21 -172
  27. package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
  28. package/.claude/skills/workflow-commands/pre-gate-cut.md +35 -95
  29. package/.claude/skills/workflow-commands/prerequisite.md +6 -6
  30. package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
  31. package/.claude/skills/workflow-commands/prrun.md +5 -5
  32. package/.claude/skills/workflow-commands/retrospective.md +9 -9
  33. package/.claude/skills/workflow-commands/split-assessment.md +2 -2
  34. package/.claude/skills/workflow-commands/target-repository.md +2 -2
  35. package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
  36. package/.codex/hooks.json +4 -4
  37. package/.github/workflows/ci.yml +15 -2
  38. package/AGENTS.md +1 -6
  39. package/CLAUDE.md +58 -120
  40. package/GEMINI.md +1 -6
  41. package/dist/CLAUDE.md +58 -120
  42. package/dist/hooks/batch-guard.js +4 -4
  43. package/dist/hooks/{chunk-NFZGFGOR.js → chunk-32GQTL4F.js} +14 -1
  44. package/dist/hooks/{chunk-HBKZLITF.js → chunk-DO6NWWHU.js} +4 -4
  45. package/dist/hooks/{chunk-KBXZLMRH.js → chunk-G6KRCKEC.js} +216 -76
  46. package/dist/hooks/{chunk-JKKTVDAG.js → chunk-GDS5N2KB.js} +186 -309
  47. package/dist/hooks/chunk-I4XSL4TT.js +56 -0
  48. package/dist/hooks/{chunk-GDU26K3J.js → chunk-S6X6WYKR.js} +886 -174
  49. package/dist/hooks/{chunk-FO2I7UT7.js → chunk-UAGLLIGZ.js} +1 -1
  50. package/dist/hooks/{chunk-RFWWUI5F.js → chunk-VESKRQOF.js} +3 -3
  51. package/dist/hooks/{chunk-KOKOAV6D.js → chunk-VQLLNFNT.js} +6 -4
  52. package/dist/hooks/{chunk-LJBLGHEH.js → chunk-ZAL3NRGL.js} +4 -6
  53. package/dist/hooks/codex-hook-adapter.js +10 -10
  54. package/dist/hooks/duplicate-read-guard.js +5 -5
  55. package/dist/hooks/format-edited.js +2 -2
  56. package/dist/hooks/inputs.json +178 -177
  57. package/dist/hooks/investigation-guard.js +5 -5
  58. package/dist/hooks/pretool-guard.js +11 -10
  59. package/dist/hooks/stop-guard.js +20 -6
  60. package/dist/josh.js +29 -3
  61. package/dist/version/index.js +5 -1
  62. package/package.json +2 -1
  63. package/prompts/collaboration-workflow/file-edits.md +11 -10
  64. package/prompts/collaboration-workflow/gh-rest.md +2 -4
  65. package/prompts/collaboration-workflow/glossary.md +1 -1
  66. package/prompts/collaboration-workflow/issue-citation.md +5 -9
  67. package/prompts/collaboration-workflow/issue-template.md +40 -115
  68. package/prompts/collaboration-workflow/operating-rules.md +4 -5
  69. package/prompts/collaboration-workflow/output-bounds.md +12 -78
  70. package/prompts/collaboration-workflow/overview.md +7 -13
  71. package/prompts/collaboration-workflow/plan-comment.md +11 -39
  72. package/prompts/collaboration-workflow/principles.md +3 -4
  73. package/prompts/collaboration-workflow/report-format.md +28 -74
  74. package/prompts/collaboration-workflow/residency.md +12 -109
  75. package/prompts/collaboration-workflow/rule-delivery.md +98 -97
  76. package/prompts/collaboration-workflow/shell-body.md +3 -13
  77. package/prompts/collaboration-workflow/turn-batching.md +14 -88
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
  79. package/prompts/collaboration-workflow/wip-cap.md +6 -45
  80. package/prompts/collaboration-workflow.md +1 -13
  81. package/prompts/review-rubric.md +8 -71
  82. package/prompts/review.md +25 -55
  83. package/prompts/testing-guide.md +18 -67
  84. package/scripts/agent/agent-role-profile.ts +1 -1
  85. package/scripts/agent/codex-hooks.ts +3 -12
  86. package/scripts/backlog/backlog-budget.ts +1 -1
  87. package/scripts/backlog/backlog-drive-cli.ts +4 -3
  88. package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
  89. package/scripts/backlog/backlog-next.ts +15 -4
  90. package/scripts/backlog/backlog-offer.ts +6 -6
  91. package/scripts/backlog/backlog-plan-cli.ts +6 -5
  92. package/scripts/backlog/backlog-rank.ts +18 -9
  93. package/scripts/backlog/backlog-restructure.ts +152 -0
  94. package/scripts/backlog/backlog-waves.ts +15 -17
  95. package/scripts/behavior/behavior-rules.ts +11 -4
  96. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  97. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  98. package/scripts/delegation/investigation-reads.ts +5 -5
  99. package/scripts/discovery/repo-discovery.ts +21 -7
  100. package/scripts/discovery/repo-origin.ts +26 -0
  101. package/scripts/discovery/repo-party.ts +42 -1
  102. package/scripts/document/backlogrun-parent-read-set.ts +14 -15
  103. package/scripts/document/document-byte-budget.ts +34 -20
  104. package/scripts/document/entry-read-budget.ts +21 -9
  105. package/scripts/document/lane-child-read-set.ts +12 -14
  106. package/scripts/document/read-set-cli.ts +4 -4
  107. package/scripts/document/read-set-point-of-use.ts +10 -2
  108. package/scripts/document/resident-budget.ts +47 -0
  109. package/scripts/epic/epic-shape.ts +10 -13
  110. package/scripts/epic/epic-solo.ts +1 -1
  111. package/scripts/exports/unused-members-cli.ts +7 -3
  112. package/scripts/followup/git-followup-stages.ts +9 -0
  113. package/scripts/followup/git-pr-followup.ts +1 -1
  114. package/scripts/gh/git-gh-api-path.ts +7 -0
  115. package/scripts/gh/git-gh-issue.ts +5 -3
  116. package/scripts/gh/git-gh-pr-read.ts +4 -0
  117. package/scripts/gh/git-pr-checks-eval.ts +8 -0
  118. package/scripts/gh/git-pr-checks.ts +11 -1
  119. package/scripts/git/constants.ts +10 -0
  120. package/scripts/git/main-merge.ts +57 -21
  121. package/scripts/git/schemas.ts +2 -0
  122. package/scripts/hooks/pretool-guard-cli.ts +2 -2
  123. package/scripts/hooks/pretool-guard.ts +45 -2
  124. package/scripts/hooks/run-hook.sh +41 -0
  125. package/scripts/init/hook-command-rewrite.ts +18 -35
  126. package/scripts/init/hook-launch.ts +21 -32
  127. package/scripts/issue/issue-auto-ok.ts +95 -0
  128. package/scripts/issue/issue-file-cli.ts +53 -7
  129. package/scripts/issue/issue-file.ts +28 -5
  130. package/scripts/issue/issue-labels.ts +12 -12
  131. package/scripts/issue/issue-rank.ts +1 -1
  132. package/scripts/issue/issue-read-cli.ts +3 -3
  133. package/scripts/issue/issue-read.ts +2 -2
  134. package/scripts/issue/issue-scout-cli.ts +2 -2
  135. package/scripts/issue/issue-state-cli.ts +1 -1
  136. package/scripts/issue/issue-state.ts +2 -2
  137. package/scripts/issue/issue-wip.ts +70 -0
  138. package/scripts/josh/hook-decision.ts +17 -0
  139. package/scripts/josh/josh-commands-development.ts +11 -1
  140. package/scripts/josh/josh-commands-issue.ts +2 -2
  141. package/scripts/lane/lane-launch-cli.ts +21 -3
  142. package/scripts/lines/bytes-command.ts +29 -6
  143. package/scripts/notify/telegram-notify.ts +26 -1
  144. package/scripts/observations/observation-ledger-line.ts +1 -1
  145. package/scripts/release/release-progress.ts +167 -0
  146. package/scripts/release/release-publish.ts +19 -8
  147. package/scripts/release/release-tag.ts +22 -8
  148. package/scripts/rules/decision-oracle-stage.ts +1 -1
  149. package/scripts/rules/decision-oracle.ts +6 -6
  150. package/scripts/rules/delivered-rules.ts +11 -11
  151. package/scripts/rules/direct-pr-create.ts +110 -0
  152. package/scripts/rules/file-body.ts +98 -42
  153. package/scripts/rules/filing-cap.ts +2 -2
  154. package/scripts/rules/filing-offer.ts +1 -1
  155. package/scripts/rules/gh-api.ts +18 -1
  156. package/scripts/rules/implementation-cut.ts +3 -1
  157. package/scripts/rules/lane-background.ts +67 -10
  158. package/scripts/rules/permission-guards.ts +10 -2
  159. package/scripts/rules/pre-gate-cut.ts +2 -2
  160. package/scripts/rules/stop-rules.ts +4 -4
  161. package/scripts/rules/test-declared-commit.ts +1 -1
  162. package/scripts/rules/third-party-write.ts +56 -12
  163. package/scripts/run/run-carry.ts +11 -5
  164. package/scripts/run/run-cut-cli.ts +1 -1
  165. package/scripts/run/run-cut-handoff.ts +21 -1
  166. package/scripts/run/run-cut-report.ts +2 -2
  167. package/scripts/run/run-entry-cli.ts +11 -3
  168. package/scripts/run/run-hold-cli.ts +16 -2
  169. package/scripts/run/run-label.ts +50 -0
  170. package/scripts/run/run-merge-steps.ts +8 -3
  171. package/scripts/run/run-merge.ts +1 -1
  172. package/scripts/run/run-prep-cli.ts +6 -2
  173. package/scripts/run/run-prep.ts +17 -0
  174. package/scripts/run/run-ship-cli.ts +27 -6
  175. package/scripts/run/run-ship-next.ts +13 -0
  176. package/scripts/run/run-ship-pre-detach.ts +66 -0
  177. package/scripts/run/run-ship-preflight.ts +34 -3
  178. package/scripts/run/run-ship-return.ts +7 -4
  179. package/scripts/run/run-ship-review-steps.ts +1 -1
  180. package/scripts/run/run-ship-review.ts +26 -5
  181. package/scripts/run/run-ship-stage.ts +13 -0
  182. package/scripts/run/run-ship-steps.ts +10 -2
  183. package/scripts/run/run-ship-sync.ts +114 -0
  184. package/scripts/run/run-ship.ts +6 -0
  185. package/scripts/run/run-step-cli.ts +20 -7
  186. package/scripts/run/run-wake-cli.ts +8 -1
  187. package/scripts/run/run-wake-driver.ts +62 -8
  188. package/scripts/run/run-wake-session.ts +2 -1
  189. package/scripts/test/test-declared-logic.ts +1 -1
  190. package/scripts/test/test-red-logic.ts +17 -2
  191. package/scripts/test/test-red.ts +8 -7
  192. package/scripts/time-runtime/time-batch-guard.ts +52 -16
  193. package/scripts/time-runtime/time-density-hook.ts +20 -2
  194. package/scripts/time-runtime/time-density.ts +4 -3
  195. package/scripts/time-runtime/time-markers.ts +15 -4
  196. package/scripts/time-runtime/time-transcript-line.ts +27 -1
  197. package/scripts/version/npm-registry.ts +11 -1
  198. package/templates/workflows/ci.yml +9 -1
  199. package/.claude/skills/workflow-commands/followup-reference.md +0 -118
  200. package/dist/hooks/chunk-HANSKHES.js +0 -464
@@ -15,7 +15,7 @@ authorization, whether it came from a named epic or from the opted-in pool:
15
15
  - **A stop that would end the run parks one issue and the run continues** — → "park
16
16
  and continue", which is that rule's single source, including what happens to the issue's lane.
17
17
  - **A prerequisite discovered mid-run is recorded as a dependency rather than parked** —
18
- → "A prerequisite discovered mid-run", and `SKILL.md` → §2d for the three-way
18
+ → "A prerequisite discovered mid-run", and `prerequisite.md` for the three-way
19
19
  distinction between a prerequisite, a split and an upstream defect. **One thing is genuinely
20
20
  different**: with no epic, there is no `pnpm josh epic --add` to record the ordering into, so the
21
21
  prerequisite is filed with `route:tier-a` and the blocked issue is parked with `needs-decision`
@@ -58,8 +58,8 @@ untouched**, for the same reason. **Name the lane directory in the stop report a
58
58
  person told to look at a working tree and not told which one has been told nothing. The lanes already in
59
59
  flight finish; no new lane is opened.
60
60
 
61
- **Never apply or remove the label** — `auto-ok`'s rule, at `auto-ok`'s strength. Full definition and the
62
- `needs-decision` comparison: `SKILL.md` → §2z, which is the single source.
61
+ **Never apply or remove the label** — a person's alone. Full definition and the
62
+ `needs-decision` comparison: `needs-human-review.md`, which is the single source.
63
63
 
64
64
  ## park and continue
65
65
 
@@ -75,13 +75,11 @@ pnpm josh issue:comment <N> --body-file <path> # what needs deciding, and the
75
75
  `needs-decision` precedence over `in-progress`, so the next child is offered normally. Then return to
76
76
  step 1; the other children are unaffected unless they depend on this one.
77
77
 
78
- **What happens to a parked child's lane depends on whether it had committed, and the two answers are
79
- opposite.** **Parked before its commit**, the lane is stashed and closed:
80
- `git -C <dir> stash push -u -m "backlogrun: parked #<N>"`, the stash recorded on the Issue, then
81
- `pnpm josh lane:close <N>` (`epic:next` counts a parked child's lane as released, and `lane:close`
82
- removes the work tree by force). **Parked after its commit and push**, the lane is *kept*: nothing to
83
- stash, and closing would delete the local branch the resume needs. **A lost merge race is not one of
84
- these rows** — it resolves in its lane ("Conflicts are not predicted" above), and parks only under that
78
+ **A parked child's lane is kept, whether or not it had committed.** **Parked before its commit**, the
79
+ uncommitted work stays in the lane's tree — never in the stash, which every work tree shares — and
80
+ `lane:close` would remove it by force. **Parked after its commit and push**, closing would delete the
81
+ local branch the resume needs. **A lost merge race is not one of
82
+ these rows** — it resolves in its lane (`backlogrun-recovery.md` → "Conflicts are not predicted"), and parks only under that
85
83
  section's four conditions, which take the after-commit row. Both rows and the `pnpm josh run:release
86
84
  <N>` every parked ending owes are "What happens to a lane" above, the single source.
87
85
 
@@ -101,7 +99,8 @@ gh api -X DELETE repos/{owner}/{repo}/issues/<N>/labels/needs-decision 2>/dev/nu
101
99
 
102
100
  **The released child goes back to a lane, never into the parent's own context.** A parent that is
103
101
  *already* running, and clears a label mid-run because a person just answered, dispatches it the same way
104
- rather than picking up its diff itself — `pnpm josh lane:open <N>` then `pnpm josh lane:dispatch <N>`. A
102
+ rather than picking up its diff itself — `pnpm josh lane:launch <N>`, which dispatches into the lane the
103
+ park kept, or opens one where none is. A
105
104
  parent that implements the released child inline is the failure the orchestrator rule is written against:
106
105
  "The parent orchestrates and never implements a child in its own context" above.
107
106
 
@@ -110,8 +109,10 @@ human-in-the-loop cycle, not an optional tidy-up.
110
109
 
111
110
  ## `in-progress` is removed by whoever finds it stale
112
111
 
113
- Nothing in the codebase removes `in-progress`; a normal finish closes the issue. An interrupted run
114
- leaves it behind, and a child that carries it is excluded from every future `epic:next` — permanently.
112
+ A run's own ending removes `in-progress` — `pnpm josh followup` on a merge, `pnpm josh run:release <N>`
113
+ on a stop. An interrupted run reaches neither and leaves it behind, and a child that carries it is
114
+ excluded from every future `epic:next` — permanently. The removal below is the one hand-written call
115
+ left, for a label no live run owns: another tree's record is not this tree's to release.
115
116
  **A session that detects a stale child removes the label itself** (Tier A) and reports it, before
116
117
  continuing the loop.
117
118
 
@@ -140,7 +141,7 @@ gh api -X DELETE repos/{owner}/{repo}/issues/<N>/labels/in-progress 2>/dev/null
140
141
 
141
142
  Finding that something else in **this** repository has to land first is not a split, and not an upstream
142
143
  defect. The child in hand is still one deliverable; it just needs another one before it. The three-way
143
- distinction, the `route:tier-a` filing command and the filing ceiling are `SKILL.md` → §2d, the single
144
+ distinction, the `route:tier-a` filing command and the filing ceiling are `prerequisite.md`, the single
144
145
  source; what follows is this entry's branch.
145
146
 
146
147
  `<M>` below is the child being implemented when the prerequisite turned up; `<N>` is the new Issue.
@@ -166,7 +167,7 @@ source; what follows is this entry's branch.
166
167
  3. `pnpm josh epic --add <E> <N> --before <M>` — one command writes the task-list row, the declaration
167
168
  and the `blocked-by` relation together. Never edit the body by hand: the declaration and the relations
168
169
  then disagree, `epic:next` returns `error`, and the unattended run stops.
169
- 4. **Remove `in-progress` from `<M>`** — `gh api -X DELETE repos/{owner}/{repo}/issues/<M>/labels/in-progress 2>/dev/null || true`.
170
+ 4. **Remove `in-progress` from `<M>`** — `pnpm josh run:release <M>`, which removes the label with the hold.
170
171
  This is what lets `<M>` run again: `epic:next` classifies a child carrying `in-progress` as waiting on
171
172
  time **before** it looks at any blocker, so a child left labelled is never offered again.
172
173
  5. **Do not park.** Go back to step 1 of the loop. `epic:next` classifies the original child as resolving
@@ -1,148 +1,18 @@
1
1
  # `backlogrun` — progress, the hand-off, resume and waiting
2
2
 
3
- **Read this file in full before the progress watcher starts** (`pnpm josh run:progress --wait`) and
4
- before the hand-off check at a child's merge (`pnpm josh cost --cut`). It is a point-of-use
5
- document, never an entry read: the entry procedure is `backlogrun.md`, which points here at those
6
- steps (joshuafolkken/kit#2010). This file is the single source of the heartbeat, the session hand-off
7
- and cut/resume, waiting without waiting forever, and the end-of-run summary and propagate.
3
+ **Read this file in full before the first child is dispatched** and before the hand-off check at a
4
+ child's merge (`pnpm josh cost --cut`). It is a point-of-use document, never an entry read: the entry
5
+ procedure is `backlogrun.md`, which points here at those steps (joshuafolkken/kit#2010). This file is
6
+ the single source of the session hand-off and cut/resume, waiting without waiting forever, and the
7
+ end-of-run summary and propagate. **The heartbeat is not here** — it binds every implementing run, so
8
+ its single source is `progress-watcher.md` → "Progress while the run is quiet" (joshuafolkken/kit#3172),
9
+ read before the watcher starts.
8
10
 
9
11
  After a carry hand-off, the detached `run:wake` supervisor runs the deterministic `backlog:drive`
10
12
  loop. References below to a parent's mechanical polling and lane collection describe work now done
11
13
  by that supervisor; an AI parent is started only for a driver branch that requires judgment.
12
14
 
13
- ## Progress while the run is quiet
14
-
15
- **Start the progress step before step 1 of the loop, and do it without being asked.**
16
-
17
- ```bash
18
- pnpm josh run:progress --wait --output <the transcript path of each delegated unit> # in the background
19
- ```
20
-
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:**
25
-
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 |
31
-
32
- **`pnpm josh run:watcher:guard` detects a missed restart** (joshuafolkken/kit#2113): it refuses when
33
- lane children are in-flight but the watcher's life record has gone stale. **It is wired into
34
- `pretool-guard`, the one `PreToolUse` hook** (joshuafolkken/kit#2353), so the next tool call is refused
35
- until the watcher is restarted — once per run, so the `pnpm josh run:progress --wait` that fixes it is
36
- not blocked.
37
-
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
39
- (`quiet` and `unchanged`), the children in flight, the run state (`lanes`, `load`, `record`), and the
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
42
- unread`, `no in-progress child yet`, `lanes none`, an absent `next`) is fixed by the command's own unit
43
- tests. **A value said that way is still an observation, never a state**: `record unread` means no
44
- `--output` path was given, not *not stalled*; `lanes none` is *no lane is open*, not *nothing is
45
- running*; and the `next` line is a schedule — the time *if the silence continues*, superseded when a
46
- real report resets the clock through `--mark`.
47
-
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.
50
-
51
- **`--mark` at every real report.** Whenever this loop reports something of its own — a child merged,
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
53
- (`docs/josh-commands-automation.md` → "`josh run:progress`").
54
-
55
- **Do not keep a progress clock of your own** — the hook refuses the arm rather than asking
56
- (`scripts/rules/early-heartbeat.ts` → `decide`), though **a single correctly-spaced arm is allowed**. It
57
- is still not the way to report; the step above prints without arming anything, and `--wait` is not a
58
- wait timer. Rationale: `docs/maintainers/backlogrun-progress-rationale.md` → "Why the heartbeat reads
59
- as it does".
60
-
61
- **The default interval is twenty minutes, overridable — by the person, not the run.**
62
- `JOSH_PROGRESS_INTERVAL_MINUTES` moves both sides (the guard reads it through the watcher's reader);
63
- `josh` → `progress_interval_minutes` in `package.json` sits one step below the variable. **`--interval`
64
- moves the watcher alone** — it makes the watcher quieter than the floor, never the guard stricter.
65
-
66
- **An explicit ask is not a heartbeat, and it is exempt by construction.**
67
- **Every unscheduled progress statement is answered by `pnpm josh run:progress --once`** — presented in
68
- the same five field lines, the reply to an explicit ask and the note just after a run starts alike.
69
- **Never write a clock time the command did not print** (not an approximation, not a placeholder — where
70
- a field is missing, say so). **`--once` records the report**, so no `--mark` beside it.
71
-
72
- **Every report opens with the time the observation was taken** — not only how long it has been quiet: an
73
- absolute instant, `at YYYY-MM-DD HH:MM±HH:MM / YYYY-MM-DDTHH:MMZ`. **It is added, never substituted for
74
- the elapsed figure**, and **the presented line needs no stamp computed for it** — the command prints the
75
- `at` stamp and the `next` field.
76
-
77
- **The line carries observations, never "still running", and nothing in it is a verification result** —
78
- no gate, CI or check rollup, because the command reads none of them. Rationale:
79
- `docs/maintainers/backlogrun-progress-rationale.md` → "Why the heartbeat reads as it does".
80
-
81
- **The invariant is a tier, not a mechanism** (joshuafolkken/kit#2156, joshuafolkken/kit#2207). Three
82
- tiers say how much of a person's attention a signal takes: **interrupt** reaches for it now (a Telegram),
83
- **ambient** is seen without being asked for, and **requested** is read only when a person types for it.
84
- **The heartbeat sits at the ambient tier, and stays there across a `backlogrun` session cut — never
85
- promoted to interrupt, never demoted to requested.**
86
-
87
- **The report surface belongs to the run, not to the session** (joshuafolkken/kit#2207). The run's
88
- session-facing events — a plan posted, a child launched, a PR opened, a review round, a park, a cut, a
89
- stop, a merge — are appended to one ordered stream keyed to the run's identity (`pnpm josh run:event`).
90
- **Every session is a writer;
91
- the reader is a script in a pane of the person's own, never a conversation** (#2492):
92
- `pnpm josh run:event --watch` prints each event the moment it lands, in the session language, and the
93
- `heartbeat` events say the run is alive when quiet. **No session relays the stream** — name the watch command once, at the first cut or when
94
- asked. **It is the same reader before and after the cut**: a cut moves who executes, never where the
95
- stream lives.
96
-
97
- **Interrupt is withheld on purpose**: no Telegram for the heartbeat. **`pnpm josh run:wake --list` is the requested tier** — the last-event read of the stream the
98
- watch pane reads, for a person who types for one line rather than watching. **`tail -F` on the
99
- raw stream file is a recovery path, not the ambient surface** — the watch pane is how progress is seen
100
- without asking, and the raw `tail` is the fallback when the pane has stopped, named for that role in
101
- `--list`'s own output.
102
-
103
- **A stop is an interrupt, and only a stop** (joshuafolkken/kit#2136). A run that has _stopped_ — every
104
- remaining child blocked behind a parked one, the backlog drained of anything runnable, the failure
105
- streak tripped — is pushed to the interrupt tier: `pnpm josh run:carry --end --stopped "<reason>"` sends one ⏸️ confirmation
106
- as it ends the record, and because the record is gone by the second `--end` the same stop never notifies
107
- twice. A parked child is pushed by the child itself, which records the park and sends its own
108
- `confirmation` before it stops (`backlogrun-park.md`). Rationale for the tiers:
109
- `docs/maintainers/backlogrun-progress-rationale.md` → "Why the signal tiers are split this way".
110
-
111
- **A heartbeat is emitted from the moment a run has started, even before any child carries
112
- `in-progress`** — "a run has started" is read from a mechanical record (a registered lane, a held work
113
- tree, or a carried budget), and the line names that stage as an observed fact (`no in-progress child
114
- yet`). **Only a checkout with no run recorded at all stays silent.**
115
-
116
- **The scope is every implementing run, not this command alone.** `fullrun`, `halfrun` and `backlogrun`
117
- start the same watcher under the same rules, and this section is the single source for all four; `kickoff`
118
- starts none. **`halfrun` is included.**
119
-
120
- **One watcher per run, and the outermost invocation is the one that starts it.** A `fullrun` running as
121
- a `backlogrun` named issue or an `backlogrun` child starts none: the brief names the invocation it descends
122
- from. `backlogrun` starts one for the whole batch. **A dispatched lane child is refused a watcher by its
123
- `JOSH_LANE_CHILD` mark** — it still runs `--mark` for the parent's clock, but every reporting form
124
- (`--wait`, `--once`, the default watch) exits at once with a notice.
125
-
126
- **In a single-issue run it starts immediately after `pnpm josh run:hold` succeeds.** **It is started in the target repository's checkout, and
127
- `--mark` is run there too.**
128
-
129
- **What counts as a real report when the run has one issue.** A `fullrun` has exactly one child, so the
130
- unit is **any turn that puts a progress statement in front of the person** — four of them, `--mark` run
131
- in the same turn as each: **the Step 0 work summary, the pull request opening, each review round's
132
- verdict, and any `confirmation` / `failure` / `completion` notification or stop.** **A tool result only
133
- you read is not one** — a gate run, a `gh` read, an edit.
134
-
135
- **`--output` is omitted in a single-issue run, and `record` reads `unread`.** Rationale for the
136
- watcher's scope and placement: `docs/maintainers/backlogrun-progress-rationale.md` → "Why the watcher
137
- runs where it does".
138
-
139
- **A run that merges needs no teardown; a run that stops has to end the reporting itself.** **A stop
140
- keeps the `in-progress` label on purpose** — so **no further `--wait` is started** after the stop
141
- notification, and the running `--wait` is stopped in the same turn, since no report ends it. That
142
- covers `halfrun`'s stop before commit, a `needs-human-review` stop, a split or prerequisite stop, and
143
- a `backlogrun` named issue's failure stop.
144
-
145
- ### Running a named epic's children
15
+ ## Running a named epic's children
146
16
 
147
17
  `josh epic:next <E> --repo <this repository> --lanes` prints **one issue number per line** on standard
148
18
  output — as many as that repository has free lanes — or, when there is no child to run, the verdict as a
@@ -196,39 +66,11 @@ run in lanes".
196
66
  through the same `run:merge --output` classification. **The flag is what you saw, never what kind of
197
67
  child it is.**
198
68
 
199
- When the unit reports back, **confirm the child from GitHub before believing it**:
200
-
201
- ```bash
202
- pnpm josh issue:state <N> # a child in this repository
203
- pnpm josh issue:state <N> --repo <owner/repo> # a child in another one
204
- # state: CLOSED
205
- # labels: (none)
206
- # human_review: no
207
- ```
208
-
209
- **`--repo` is not optional for a cross-repository child.** Without it the read resolves `<N>` against
210
- the repository this session runs in.
211
-
212
- `state: CLOSED` is the only answer that means the child finished. **Read the `human_review:` and
213
- `labels:` lines before calling anything else a failure.** **A non-zero exit is not `OPEN`**: the command exits non-zero
214
- without a state when the number resolves to nothing (`does not resolve`) or the read failed (`could
215
- not read`, a rate limit or expired auth). Re-read before deciding.
216
-
217
- - **Open, carrying `needs-decision`** — the unit **parked** it. Not a failure: leave the label on, do
218
- **not** count it against the consecutive-failure guard, and go back to step 1.
219
- - **Open, and `human_review: yes`** — the child **stopped before its commit**, the run's own ending
220
- (§2z). **`Open` is part of the test** — a CLOSED child carrying the label finished and merged.
221
- **Read that line, not the `labels:` one.** Leave
222
- `in-progress` **on**, do not park it, do not count it, and do **not** go back to step 1. Finish the
223
- session and report. **Do not send a second `confirmation` Telegram** — the unit already sent one.
224
- Where the child ran in this session's own context, that first notification is yours to send.
225
- - **Open, without `needs-decision`** — it failed. Remove the stale `in-progress` here (Tier A), count
226
- it against the consecutive-failure guard, and **park it**.
227
-
228
- **Never ask `epic:next` in place of this read.**
229
-
230
- **A merge is one event, and one call of the parent's — `pnpm josh run:merge <N>`**
231
- (joshuafolkken/kit#2024). The composite command confirms the child from GitHub, does the post-merge steps, folds in the hand-off check, and prints
69
+ **When the unit reports back, a merge is one event, and one call of the parent's — `pnpm josh
70
+ run:merge <N>`** (joshuafolkken/kit#2024). It is the only reading of how the child ended: never
71
+ classify `pnpm josh issue:state` by hand, and never ask `epic:next` in its place. Rationale:
72
+ `docs/maintainers/backlogrun-progress-rationale.md` → "Why a child's ending is read by `run:merge` alone".
73
+ The composite command confirms the child from GitHub, does the post-merge steps, folds in the hand-off check, and prints
232
74
  the next child number — or a control verdict — for the parent to read. Pass the merged child's
233
75
  number and the offer's source — `--epic <E> --repo <owner/repo>` for a named epic,
234
76
  nothing for the opted-in backlog — with `--owner "$PPID"` so it counts into the carry record under
@@ -257,7 +99,9 @@ run in lanes".
257
99
  **Beyond the offer `epic:next` prints** (`run` becomes numbers; `wait` / `stop` / `complete` /
258
100
  `error` pass through), the composite adds six verdict tokens: `over` — the merge crossed the budget,
259
101
  so hand the lanes over and take the cut ("The hand-off" below); `human-review` — the child stopped
260
- before its commit, the run's own ending (SKILL.md → §2z), so stop; `stop` — the consecutive-failure
102
+ before its commit, the run's own ending (needs-human-review.md), so stop — leave `in-progress` on, and send
103
+ no second `confirmation` Telegram, since the unit already sent one (where the child ran in this
104
+ session's own context, that first notification is yours to send); `stop` — the consecutive-failure
261
105
  guard tripped; `environment` — the consecutive-**outage** guard tripped, so the API is down and the
262
106
  run stops as an environment failure rather than the children's (joshuafolkken/kit#2240); `resumed` —
263
107
  an unadopted cut was relaunched; `lane:await <N>` it again (joshuafolkken/kit#2484); and `retry` — the child's state could not be read, so re-read it.
@@ -522,7 +366,7 @@ closing with the release tail below. Its output is the Telegram body too — pas
522
366
  `pnpm josh notify --body-file`.
523
367
 
524
368
  **That same session asks `pnpm josh release:scope` once, after the last child has merged** — in the
525
- primary checkout, after the last lane is closed, and never once per child. `followup-reference.md` →
369
+ primary checkout, after the last lane is closed, and never once per child. `followup.md` →
526
370
  "When `pnpm josh release` runs" is the single source for the position and the three answers. `run:report`
527
371
  appends that answer as the summary's closing line: on `required` the request and the exact command, on
528
372
  `unknown` the word `unknown`, never rounded to `skip`.
@@ -0,0 +1,155 @@
1
+ # `backlogrun` — recovering a child that went wrong
2
+
3
+ **Read the section you need at the moment it is needed, never at the entry or before the first child**
4
+ (joshuafolkken/kit#3175). Both sections below are reached only on failure — a unit that went silent, a
5
+ lane whose pull request conflicts with `main` — so a run in which nothing fails never pays for them.
6
+ `backlogrun-child.md`, `backlogrun-lanes.md` and `chain-rule.md` point here at those triggers.
7
+
8
+ ## A delegated unit that stopped without reporting
9
+
10
+ **A unit can be stopped from outside, and a stop leaves no notification behind** — and a stopped unit
11
+ trips none of `backlogrun-child.md`'s guards, which all assume it is running. **So the parent checks
12
+ rather than waiting — which means it must not be waiting.** **Hand the child to the unit without
13
+ blocking on its return, and poll** at the loop's polling interval; ask once the unit's output has been
14
+ unchanged for the silent-unit window (`| Silent delegated unit | 30 min |` in `backlogrun-progress.md`).
15
+
16
+ **Note where the unit writes at hand-off; the modification time is read from the file, not carried.**
17
+ **Where the child runs in a lane, that note goes into the lane rather than the conversation** —
18
+ `pnpm josh lane:output <N> <path>`, in the same turn as the dispatch, so it comes back from
19
+ `pnpm josh lane:output <N>` in any session.
20
+
21
+ **Ask the command rather than combining the traces yourself.** Traces are read **in the checkout the
22
+ unit was given** — this session's own unless the unit was handed a separate work tree, and the stash in
23
+ the recovery below is taken there too.
24
+
25
+ ```bash
26
+ pnpm josh run:liveness <N> --output <path> --process none
27
+ pnpm josh run:liveness <N> --output <path> --process alive
28
+ pnpm josh run:liveness <N> --output <path> --process none --window 45 --repo <owner/repo>
29
+ ```
30
+
31
+ | Answer | What it found | What the parent does |
32
+ | --- | --- | --- |
33
+ | `alive` | The output moved, or a process of the child is running | Keep polling; touch nothing |
34
+ | `stopped` | The output has been frozen past the window and no process of the child is alive | The recovery below |
35
+ | `settled` | The child closed, or the unit parked it with `needs-decision` | Re-read it with `pnpm josh run:status <N>` — one read-only call whose state section says which branch and whose carry counters beside it feed the failure-streak decision — and take the branch its state section says |
36
+ | `undetermined` | A trace could not be read | Read the trace that failed and ask again — and see the two-in-a-row rule below |
37
+
38
+ **Two `undetermined` answers in a row is a fault in the check, not a slow unit.** The second
39
+ consecutive `undetermined` on the same child ends the polling: send a `confirmation` Telegram naming
40
+ the trace that failed, and stop. **It is never escalated to a `stopped`** — nothing was read.
41
+
42
+ **Silence and no process together — never either one alone.** **A trace that could not be read answers
43
+ `undetermined`, never `stopped`**, and a process trace nobody gave is an unasked question rather than
44
+ "no process". **Output that moved answers `alive` on its own; a live process is weighed only once every
45
+ trace has answered.** Rationale: `docs/maintainers/backlogrun-recovery-rationale.md` → "Why liveness needs
46
+ silence and no process together".
47
+
48
+ **The path passed to `--output` is absolute** — the command refuses a relative one. **The process
49
+ trace is the one the command does not read for itself**: run it in the unit's checkout and read the
50
+ command lines rather than the names — `pgrep -laf 'josh gate'` etc. — looking for one naming **that
51
+ checkout's path**, since several kit projects may run at once. **Read the file the path points at, not
52
+ the link**: a transcript path is a symlink whose own modification time never changes, so a `stat` typed
53
+ by hand needs `-L` (`run:liveness` follows the link itself).
54
+
55
+ **A clean checkout is not evidence that the unit is alive** — a stop can come while the unit is still
56
+ reading the skill and the issue. The checkout is read only for whether there is work to stash before
57
+ the child is parked; **"nothing was ever opened for the child" is `pnpm josh run:hold`'s preflight check
58
+ at the start of the next child, not this one's.**
59
+
60
+ **What follows is what a failed child already gets.** Re-read the child first with
61
+ `pnpm josh run:status <N>` — its state section carries the same `state:` / `labels:` /
62
+ `human_review:` lines `issue:state` prints, and folds in the carry counters this recovery reads
63
+ anyway; then, while it is still `state: OPEN` and not carrying `needs-decision`.
64
+ **A re-read carrying `needs-decision`** means the unit parked the child and then stopped, so fall
65
+ through to the loop's park branch: leave the label on, count nothing against the consecutive-failure
66
+ guard, and go back to step 1.
67
+
68
+ 1. **Stash the half-finished work** — `git stash push -u -m "backlogrun: stopped unit for #<N>"` — and
69
+ record it on the Issue. `-u` is not optional, and the comment is what gets the stash popped — by
70
+ message, `pnpm josh stash:pop "backlogrun: stopped unit for #<N>"`, never a positional
71
+ `git stash pop` that a shared stack lets another lane divert.
72
+ 2. **Classify how the child ended and act, in one call** — `pnpm josh run:merge <N> --output <path>`
73
+ (add `--epic <E> --repo <owner/repo> --owner "$PPID"` for a named epic). **It is the same composite a
74
+ returned child takes** (`backlogrun-progress.md` → "Running a named epic's children"), reached here
75
+ from the poll rather than from a return — **one** decision, never a second copy of it. It **reads the
76
+ child's exit record without waiting for the unit to return**, telling an `outage` — a child that
77
+ could not reach the API — apart from an `abandoned` one that stopped mid-implementation, and it drops
78
+ the stale `in-progress` itself, so there is no separate label-removal step:
79
+ - **outage** — the child never reached the API, so it is counted into its own outage streak and
80
+ **re-dispatched in the same run** by being offered again, **not** parked with `needs-decision` and
81
+ **not** counted against the consecutive-failure guard. **The re-dispatch resumes the child's
82
+ session**: `lane:dispatch` reads the `session_id` off the exit record and relaunches with
83
+ `--resume`; with no session id it falls back to a fresh `fullrun`, and the report says which path
84
+ it took. **Outages inside a two-minute window count once** toward the streak. The re-dispatch is
85
+ bounded by `CONSECUTIVE_OUTAGE_LIMIT` in `scripts/run/run-merge.ts`: *distinct* outages trip the
86
+ separate outage guard, at which point the command prints `environment` and the run stops. It never
87
+ re-dispatches into a dead API forever.
88
+ - **abandoned** — counted against the consecutive-failure guard and parked with `needs-decision`,
89
+ exactly as a failed child. Never retried silently.
90
+ 3. **Read the token it printed and take that branch** — a next child number to run (the re-dispatched
91
+ outage child among them), `environment` / `stop` to end the run, or `busy` / `retry` to re-read —
92
+ the same tokens the merge event reads (`backlogrun-progress.md` → "Running a named epic's children").
93
+ Then go back to step 1 of the loop.
94
+
95
+ Rationale: `docs/maintainers/backlogrun-recovery-rationale.md` → "Why the poll routes through `run:merge`".
96
+
97
+ ## Conflicts are not predicted
98
+
99
+ **The child merges `origin/main` into its lane before its gate now, so this path is the fallback rather
100
+ than the first line.** Every `fullrun` runs `pnpm josh main:merge` ahead of the gate
101
+ (`chain-rule.md` → "origin/main is merged in before the gate", the single source), so an overlap
102
+ already on `main` is resolved before the gate reads the tree. What this section covers is an overlap
103
+ that lands on `main` *after* it, which `followup` still reports as a conflict.
104
+
105
+ **Nothing here forecasts which children will overlap.** The overlap surfaces where GitHub already
106
+ reports it: a pull request that conflicts with its base comes back `mergeStateStatus: DIRTY`, which
107
+ `git-pr-checks-eval.ts` reads as a **failure** rather than polling through it — so `pnpm josh followup`
108
+ ends that child with a named conflict in about ten seconds.
109
+
110
+ **That child does not stop: it resolves the conflict in its own lane.** It is **not** counted against
111
+ the consecutive-failure guard either way.
112
+
113
+ **The merge direction is `origin/main` into the lane's branch, never a rebase**, and no step below
114
+ rewrites a pushed commit. Rationale: `docs/maintainers/backlogrun-recovery-rationale.md` → "Why a conflict
115
+ is resolved in the lane rather than handed to a person".
116
+
117
+ 1. **Resolve in the lane.** `git fetch origin main`, merge `origin/main` into the lane's branch, resolve
118
+ in place. Do not close the lane, open another, or switch its branch.
119
+ 2. **Re-run the whole gate.** The tree changed, so the green recorded before the conflict is void:
120
+ `pnpm josh lint:related` and `pnpm josh test:related`, then `pnpm josh gate`.
121
+ 3. **Review the resolution, one round.** Brief it with `pnpm josh review:brief` and run `/code-review`
122
+ over the resolution diff, then require `pnpm josh review:attest --check` to answer `ok`. That round
123
+ is a different subject and does not spend one of the two the cap allows — `prompts/review.md` →
124
+ "Review round cap" carries the exception.
125
+ 4. **Conclude the merge and push it, with `pnpm josh git -y`** — the only sanctioned way; without it
126
+ nothing changes on `origin` and condition 3 parks the child for good. **Record the resolution on the
127
+ Issue in this same step** — a comment naming the conflicting paths — because condition 3 counts
128
+ resolutions off the Issue comments, not off memory.
129
+ 5. **Merge**, by re-running `pnpm josh followup` exactly as before.
130
+
131
+ **The safeguard is the re-run verification, not who holds the pen** — a conflict the run can resolve is
132
+ not routed to a person.
133
+
134
+ **The run steps back under these four conditions, and under no others. The list is exhaustive and
135
+ carries no judgement.**
136
+
137
+ 1. **The resolution requires deleting the other side's change.** That decides intended behavior, not
138
+ text. It goes to the user as a **specification** question — "behavior A or behavior B" — never as a
139
+ diff.
140
+ 2. **Both sides rewrote the same lines** — overlapping, not adjacent. Read off the structure of the
141
+ conflict hunks, so it needs no interpretation.
142
+ 3. **A second conflict on the same child.** One resolution per child; the count is read off the Issue
143
+ comments step 4 writes, so it survives an interrupt.
144
+ 4. **The re-run gate did not come back green, or the resolution review returned a High.** Not only a
145
+ High: a lint error, a failing test or a spell-check hit that merging a moved `main` introduced is
146
+ this condition too.
147
+
148
+ Meeting any of the four, the child is parked — `needs-decision` plus a comment naming which of the four
149
+ it was — and its lane is **kept**, because the pushed branch is the resume path
150
+ (`backlogrun-lanes.md` → "What happens to a lane").
151
+
152
+ **Leave the tree clean before parking: `git merge --abort` precedes a park under conditions 1, 2 or 4.**
153
+ Conditions 1 and 2 are read mid-merge with conflict markers still in the tree, condition 4 after a merge
154
+ that is resolved but not committed; condition 3 fires before any merge is started, so there is nothing
155
+ to abort.