@joshuafolkken/kit 1.1136.0 → 1.1166.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 (165) 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 +11 -60
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +10 -8
  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 +64 -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 +33 -93
  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/AGENTS.md +1 -6
  38. package/CLAUDE.md +58 -120
  39. package/GEMINI.md +1 -6
  40. package/dist/CLAUDE.md +58 -120
  41. package/dist/hooks/batch-guard.js +4 -4
  42. package/dist/hooks/{chunk-NFZGFGOR.js → chunk-5KTCMCOT.js} +14 -1
  43. package/dist/hooks/{chunk-LJBLGHEH.js → chunk-A6P7PN7P.js} +4 -6
  44. package/dist/hooks/{chunk-FO2I7UT7.js → chunk-ANJ2WGFD.js} +1 -1
  45. package/dist/hooks/{chunk-GDU26K3J.js → chunk-DYAVAM6S.js} +563 -126
  46. package/dist/hooks/{chunk-RFWWUI5F.js → chunk-ENA7P5FB.js} +3 -3
  47. package/dist/hooks/{chunk-KBXZLMRH.js → chunk-GU5MATVE.js} +213 -65
  48. package/dist/hooks/chunk-GYNGP7HI.js +56 -0
  49. package/dist/hooks/{chunk-HBKZLITF.js → chunk-URJA6XY4.js} +4 -4
  50. package/dist/hooks/{chunk-KOKOAV6D.js → chunk-V2BTOZHT.js} +6 -4
  51. package/dist/hooks/{chunk-JKKTVDAG.js → chunk-ZDZ555YH.js} +100 -20
  52. package/dist/hooks/codex-hook-adapter.js +10 -10
  53. package/dist/hooks/duplicate-read-guard.js +5 -5
  54. package/dist/hooks/format-edited.js +2 -2
  55. package/dist/hooks/inputs.json +193 -192
  56. package/dist/hooks/investigation-guard.js +5 -5
  57. package/dist/hooks/pretool-guard.js +11 -10
  58. package/dist/hooks/stop-guard.js +5 -5
  59. package/dist/josh.js +25 -2
  60. package/dist/version/index.js +5 -1
  61. package/package.json +2 -1
  62. package/prompts/collaboration-workflow/file-edits.md +11 -10
  63. package/prompts/collaboration-workflow/gh-rest.md +2 -4
  64. package/prompts/collaboration-workflow/glossary.md +1 -1
  65. package/prompts/collaboration-workflow/issue-citation.md +5 -9
  66. package/prompts/collaboration-workflow/issue-template.md +40 -115
  67. package/prompts/collaboration-workflow/operating-rules.md +4 -5
  68. package/prompts/collaboration-workflow/output-bounds.md +12 -78
  69. package/prompts/collaboration-workflow/overview.md +7 -13
  70. package/prompts/collaboration-workflow/plan-comment.md +11 -39
  71. package/prompts/collaboration-workflow/principles.md +3 -4
  72. package/prompts/collaboration-workflow/report-format.md +28 -74
  73. package/prompts/collaboration-workflow/residency.md +12 -109
  74. package/prompts/collaboration-workflow/rule-delivery.md +98 -97
  75. package/prompts/collaboration-workflow/shell-body.md +3 -13
  76. package/prompts/collaboration-workflow/turn-batching.md +14 -88
  77. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
  78. package/prompts/collaboration-workflow/wip-cap.md +6 -45
  79. package/prompts/collaboration-workflow.md +1 -13
  80. package/prompts/review-rubric.md +8 -71
  81. package/prompts/review.md +25 -55
  82. package/prompts/testing-guide.md +18 -67
  83. package/scripts/agent/agent-role-profile.ts +1 -1
  84. package/scripts/agent/codex-hooks.ts +3 -12
  85. package/scripts/backlog/backlog-budget.ts +1 -1
  86. package/scripts/backlog/backlog-drive-cli.ts +4 -3
  87. package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
  88. package/scripts/backlog/backlog-offer.ts +6 -6
  89. package/scripts/behavior/behavior-rules.ts +11 -4
  90. package/scripts/delegation/investigation-reads.ts +5 -5
  91. package/scripts/discovery/repo-discovery.ts +21 -7
  92. package/scripts/discovery/repo-origin.ts +26 -0
  93. package/scripts/discovery/repo-party.ts +42 -1
  94. package/scripts/document/backlogrun-parent-read-set.ts +14 -15
  95. package/scripts/document/document-byte-budget.ts +34 -20
  96. package/scripts/document/entry-read-budget.ts +21 -9
  97. package/scripts/document/lane-child-read-set.ts +12 -14
  98. package/scripts/document/read-set-cli.ts +4 -4
  99. package/scripts/document/read-set-point-of-use.ts +10 -2
  100. package/scripts/document/resident-budget.ts +47 -0
  101. package/scripts/epic/epic-shape.ts +10 -13
  102. package/scripts/epic/epic-solo.ts +1 -1
  103. package/scripts/exports/unused-members-cli.ts +7 -3
  104. package/scripts/gh/git-gh-api-path.ts +7 -0
  105. package/scripts/hooks/pretool-guard-cli.ts +2 -2
  106. package/scripts/hooks/pretool-guard.ts +45 -2
  107. package/scripts/hooks/run-hook.sh +41 -0
  108. package/scripts/init/hook-command-rewrite.ts +18 -35
  109. package/scripts/init/hook-launch.ts +21 -32
  110. package/scripts/issue/issue-auto-ok.ts +95 -0
  111. package/scripts/issue/issue-file-cli.ts +53 -7
  112. package/scripts/issue/issue-file.ts +28 -5
  113. package/scripts/issue/issue-labels.ts +12 -12
  114. package/scripts/issue/issue-rank.ts +1 -1
  115. package/scripts/issue/issue-read-cli.ts +3 -3
  116. package/scripts/issue/issue-read.ts +2 -2
  117. package/scripts/issue/issue-scout-cli.ts +2 -2
  118. package/scripts/issue/issue-state-cli.ts +1 -1
  119. package/scripts/issue/issue-state.ts +2 -2
  120. package/scripts/issue/issue-wip.ts +70 -0
  121. package/scripts/josh/hook-decision.ts +17 -0
  122. package/scripts/josh/josh-commands-development.ts +10 -0
  123. package/scripts/josh/josh-commands-issue.ts +2 -2
  124. package/scripts/lines/bytes-command.ts +29 -6
  125. package/scripts/observations/observation-ledger-line.ts +1 -1
  126. package/scripts/release/release-progress.ts +167 -0
  127. package/scripts/release/release-publish.ts +19 -8
  128. package/scripts/release/release-tag.ts +22 -8
  129. package/scripts/rules/decision-oracle-stage.ts +1 -1
  130. package/scripts/rules/decision-oracle.ts +6 -6
  131. package/scripts/rules/delivered-rules.ts +11 -11
  132. package/scripts/rules/direct-pr-create.ts +110 -0
  133. package/scripts/rules/file-body.ts +98 -42
  134. package/scripts/rules/filing-cap.ts +2 -2
  135. package/scripts/rules/filing-offer.ts +1 -1
  136. package/scripts/rules/gh-api.ts +18 -1
  137. package/scripts/rules/implementation-cut.ts +3 -1
  138. package/scripts/rules/lane-background.ts +67 -10
  139. package/scripts/rules/permission-guards.ts +10 -2
  140. package/scripts/rules/stop-rules.ts +4 -4
  141. package/scripts/rules/test-declared-commit.ts +1 -1
  142. package/scripts/rules/third-party-write.ts +56 -12
  143. package/scripts/run/run-cut-handoff.ts +21 -1
  144. package/scripts/run/run-entry-cli.ts +11 -3
  145. package/scripts/run/run-hold-cli.ts +16 -2
  146. package/scripts/run/run-label.ts +50 -0
  147. package/scripts/run/run-merge-steps.ts +8 -3
  148. package/scripts/run/run-merge.ts +1 -1
  149. package/scripts/run/run-prep-cli.ts +6 -2
  150. package/scripts/run/run-prep.ts +17 -0
  151. package/scripts/run/run-ship-preflight.ts +34 -3
  152. package/scripts/run/run-ship-review-steps.ts +1 -1
  153. package/scripts/run/run-ship-review.ts +26 -5
  154. package/scripts/run/run-step-cli.ts +20 -7
  155. package/scripts/run/run-wake-driver.ts +24 -1
  156. package/scripts/run/run-wake-session.ts +2 -1
  157. package/scripts/test/test-declared-logic.ts +1 -1
  158. package/scripts/time-runtime/time-batch-guard.ts +52 -16
  159. package/scripts/time-runtime/time-density-hook.ts +20 -2
  160. package/scripts/time-runtime/time-density.ts +4 -3
  161. package/scripts/time-runtime/time-markers.ts +15 -4
  162. package/scripts/version/npm-registry.ts +11 -1
  163. package/templates/workflows/ci.yml +9 -1
  164. package/.claude/skills/workflow-commands/followup-reference.md +0 -118
  165. package/dist/hooks/chunk-HANSKHES.js +0 -464
@@ -62,7 +62,7 @@
62
62
  "hooks": [
63
63
  {
64
64
  "type": "command",
65
- "command": "if project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null)\"; then unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR GIT_DIR GIT_INDEX_FILE GIT_NAMESPACE GIT_OBJECT_DIRECTORY GIT_WORK_TREE; else project_root=\"$(git rev-parse --show-toplevel)\" || exit 1; fi; cd \"$project_root\" && if node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts; then node dist/hooks/session-lang.js; else pnpm josh session:lang; fi",
65
+ "command": "project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null || git rev-parse --show-toplevel)\" && cd \"$project_root\" && sh scripts/hooks/run-hook.sh session-lang",
66
66
  "timeout": 10
67
67
  }
68
68
  ]
@@ -74,7 +74,7 @@
74
74
  "hooks": [
75
75
  {
76
76
  "type": "command",
77
- "command": "if project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null)\"; then unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR GIT_DIR GIT_INDEX_FILE GIT_NAMESPACE GIT_OBJECT_DIRECTORY GIT_WORK_TREE; else project_root=\"$(git rev-parse --show-toplevel)\" || exit 1; fi; cd \"$project_root\" && if node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts; then node dist/hooks/pretool-guard.js; else pnpm josh pretool:guard; fi",
77
+ "command": "project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null || git rev-parse --show-toplevel)\" && cd \"$project_root\" && sh scripts/hooks/run-hook.sh pretool-guard",
78
78
  "timeout": 20
79
79
  }
80
80
  ]
@@ -86,7 +86,7 @@
86
86
  "hooks": [
87
87
  {
88
88
  "type": "command",
89
- "command": "if project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null)\"; then unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR GIT_DIR GIT_INDEX_FILE GIT_NAMESPACE GIT_OBJECT_DIRECTORY GIT_WORK_TREE; else project_root=\"$(git rev-parse --show-toplevel)\" || exit 1; fi; cd \"$project_root\" && if node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts; then node dist/hooks/format-edited.js; else pnpm josh format:edited; fi",
89
+ "command": "project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null || git rev-parse --show-toplevel)\" && cd \"$project_root\" && sh scripts/hooks/run-hook.sh format-edited",
90
90
  "timeout": 90
91
91
  }
92
92
  ]
@@ -98,7 +98,7 @@
98
98
  "hooks": [
99
99
  {
100
100
  "type": "command",
101
- "command": "if project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null)\"; then unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR GIT_DIR GIT_INDEX_FILE GIT_NAMESPACE GIT_OBJECT_DIRECTORY GIT_WORK_TREE; else project_root=\"$(git rev-parse --show-toplevel)\" || exit 1; fi; cd \"$project_root\" && if node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts; then node dist/hooks/stop-guard.js; else pnpm josh stop:guard; fi",
101
+ "command": "project_root=\"$(env -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_COMMON_DIR -u GIT_DIR -u GIT_INDEX_FILE -u GIT_NAMESPACE -u GIT_OBJECT_DIRECTORY -u GIT_WORK_TREE git rev-parse --show-toplevel 2>/dev/null || git rev-parse --show-toplevel)\" && cd \"$project_root\" && sh scripts/hooks/run-hook.sh stop-guard",
102
102
  "timeout": 20
103
103
  }
104
104
  ]
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: epic-commands
3
- description: The procedures for the `josh epic:*` commands that make an epic runnable without a person watching — `epic:audit` (find contradictions across the children), `epic:next` (what is runnable, per repository), and `epic:bundle` (does a newly filed issue belong with one already in the backlog). Also how an epic spans repositories and why a cross-repository dependency waits for a publish. Read this before running any of those commands, before writing an epic that tracks a child in another repository, and right after filing an issue.
3
+ description: The procedures for the `josh epic:*` commands that make an epic runnable without a person watching — `epic:audit` (find contradictions across the children), `epic:next` (what is runnable, per repository), and `epic:bundle` (does a newly filed issue belong with one already in the backlog). Also how an epic spans repositories and why a cross-repository dependency waits for a publish. Read this before running any of those commands, before writing an epic that tracks a child in another repository, and when `epic:bundle` places a filed issue (anything but `Nothing to bundle.`).
4
4
  ---
5
5
 
6
6
  # The `josh epic:*` commands
@@ -22,21 +22,19 @@ Front-loading every `needs-decision` in one pass is `backlogrun`'s job, not a co
22
22
  `backlogrun-steps.md` → "Resolve what the plan can resolve, before starting". A `backlogrun #E --only`'s
23
23
  pre-check is `epic:audit`.
24
24
 
25
- ## Front-loading the decisions — now `backlogrun`'s
25
+ **Two references are read only when their case arises**: `execution-waves.md` when an epic's children
26
+ need an order between groups (a wave boundary, or one child that must run alone), and
27
+ `epic-bundle.md` when a filing's `epic:bundle` answer places the issue.
26
28
 
27
- `epic:plan` once printed every child as one JSON document so a person could answer every
28
- `needs-decision` in one batch before the run started. That front-loading is retired as a command of
29
- its own (joshuafolkken/kit#1965): **`backlogrun` does it at the start of every run**, and
30
- `backlogrun-steps.md` → "Resolve what the plan can resolve, before starting" is its single source. A
31
- `backlogrun #E --only`'s only pre-check is `epic:audit` below, run without being asked.
29
+ ## Recording a decision
32
30
 
33
- **A decision is still recorded in two places, and one without the other loses half of it.** The epic's
31
+ **A decision is recorded in two places, and one without the other loses half of it.** The epic's
34
32
  `## Decisions` log carries the decision; a comment on each child it applies to carries the reasoning
35
33
  for that child's reader. **Recording a decision removes that child's `needs-decision` label** (Tier A);
36
34
  the label-clearing rule itself is `backlogrun-park.md` → "park and continue".
37
35
 
38
36
  - **A decision taken *as* a child joins the epic** goes through `pnpm josh epic --add … --decision-file`
39
- — the `epic:bundle` section below is that flag's single source, and it writes both halves at once.
37
+ — `epic-bundle.md` is that flag's single source, and it writes both halves at once.
40
38
  - **A decision about a child the epic already tracks** cannot use that flag: an insertion with nothing
41
39
  to add is refused outright. Until joshuafolkken/kit#1162 adds an entry point for already-tracked
42
40
  children, write the child comments with
@@ -57,6 +55,23 @@ the label-clearing rule itself is `backlogrun-park.md` → "park and continue".
57
55
  - 決定日: <YYYY-MM-DD>
58
56
  ```
59
57
 
58
+ ## Creating an epic
59
+
60
+ A split into two or more issues always gets an epic (`prompts/collaboration-workflow/issue-template.md`).
61
+ Create it with the command, never by hand-writing the body:
62
+
63
+ ```bash
64
+ pnpm josh epic "<epic-title>" <N1> <N2> ... # order-free batch
65
+ pnpm josh epic "<epic-title>" <N1> <N2> ... --ordered # argument order is the dependency order
66
+ # --rationale-file <path|-> fills Split rationale; --origin <owner/repo#N> adds the backlink
67
+ ```
68
+
69
+ The body format is the command's output, and `pnpm josh epic:check <E>` answers whether a body still
70
+ meets it (the parser auto-close reads; exit 1 on a miss) — run it after any hand edit. `pnpm josh
71
+ followup` closes the epic once every task-list child is closed; one tracking a cross-repository child
72
+ is closed by hand. The by-hand fallback where `josh` is unavailable: `issue-template.md`; the format's
73
+ reasons and the history: `docs/maintainers/epic-commands-rationale.md` → "Creating an epic by hand".
74
+
60
75
  ## `josh epic:audit <E>` — contradictions across the children
61
76
 
62
77
  `epic:check` verifies one epic's *format*. Nothing verified that the children agree, and a hand audit
@@ -201,128 +216,6 @@ Two things stop the command rather than being worked around: a **circular depend
201
216
  recorded them, a recording that failed, or a relation hand-added since). Only a line that is
202
217
  *nothing but* a chain counts as a declaration — a prose line recommending an execution order is a suggestion, not a dependency.
203
218
 
204
- ## Execution waves — parallel, then one alone, then the rest
205
-
206
- **A wave is not a mechanism, and there is nothing to add for one.** "Run #A, #B and #C together, then
207
- #D by itself, then the rest together" is already expressible with the dependency declaration this
208
- skill has described above, and wanting it only became common once parallel lanes
209
- (joshuafolkken/kit#1170) let several children run at once. What was missing was never the mechanism —
210
- it was that nobody had written down how to build one, or when not to (joshuafolkken/kit#1584).
211
-
212
- ### The shape
213
-
214
- **A wave boundary is drawn by chaining every child of the later wave to every child of the earlier
215
- one.** Wave 1 `{#A, #B, #C}` → wave 2 `{#D}` → wave 3 `{#X, #Y, #Z}` is written in the epic's
216
- `## Dependencies` as three chain lines:
217
-
218
- ```
219
- #A -> #D -> #X
220
- #B -> #D -> #Y
221
- #C -> #D -> #Z
222
- ```
223
-
224
- - **#A #B #C are blocked by nobody, so they start together** — as many at once as there are free lanes.
225
- - **#D is `time` until all three have closed**, and becomes runnable the moment the last one does.
226
- - **#X #Y #Z are blocked by #D, so #D necessarily runs alone.**
227
- - **#D closing releases wave 3**, which again runs as wide as the lanes allow.
228
-
229
- **The block above is an illustration, not something to paste.** `parse_dependency_chains` strips
230
- fenced blocks before it matches, so a `## Dependencies` section whose chains sit inside a fence reads
231
- as **no declaration at all** rather than as an error — and `#A` / `#B` are not issue numbers anyway.
232
- The lines go into the epic body unfenced, with real numbers.
233
-
234
- **Deleting a declared order is `pnpm josh epic --remove <E> <M> <N> [<N2> …]`, and never a hand edit
235
- either** (joshuafolkken/kit#1712). The arguments are a path, so each consecutive pair is one order and
236
- a whole chain goes in one call; the body's declaration and the `blocked-by` relations are written from
237
- that one input, exactly as `--add` writes them. **The ends are never reconnected** — `#A -> #B -> #C`
238
- minus `#B -> #C` leaves `#A -> #B`, and taking out a middle child's two links leaves `#A` and `#C`
239
- unordered rather than declaring `#A -> #C`. Deleting the last link writes the unordered sentence, so
240
- the section never ends up with no machine-readable declaration. Pass `--decision-file` to record why;
241
- the record lands on the epic's `## Decisions` and on both ends of every deleted order. Doing either
242
- half by hand is the `declaration_mismatch` below, arrived at from the other direction.
243
-
244
- **Write the boundary with `pnpm josh epic --add`, not by hand.** A declared link with no recorded
245
- `blocked-by` relation is the `declaration_mismatch` that `find_anomalies` reports, and it stops
246
- `epic:next` and the run that consumes it outright; the rule further down — record the order in `blocked-by` **and**
247
- in `Dependencies` — is what a wave has to satisfy, and editing the body alone satisfies half of it.
248
- **`--before <hub>` and `--after <hub>` are both refused while the hub sits in more than one chain
249
- with nothing after it** — which is exactly what a hub is the moment the earlier wave has been chained
250
- into it. `chains_containing` finds several indices; `is_branching_after` does not hold, because it
251
- requires a successor in **every** chain naming the target and a hub has none in any of them; so
252
- `ambiguous_position_error` is raised (`scripts/epic/epic-chains.ts`). **The first child of the
253
- next wave therefore goes into the declaration by hand**, with its `blocked-by` recorded to match.
254
- Once the hub has a successor, `--after <hub>` branches a new line and the rest of that wave goes in
255
- with it.
256
-
257
- **Nothing in the parsing or the consistency check forbids this.** `DECLARED_CHAIN_LINE` in `scripts/epic/epic-parse.ts`
258
- asks only that a line be *nothing but* a chain, so there may be any number of chain lines and **one
259
- child may be an endpoint of several of them** — `parse_dependency_links` expands each line
260
- independently and flattens the result, dropping only self-loops. `find_anomalies` in
261
- `scripts/epic/epic-graph.ts` rejects exactly two things, a **cycle** and a **disagreement between the
262
- declaration and the recorded `blocked-by`**; a fan-in and a fan-out are neither.
263
-
264
- **The number of lines is the size of the widest wave**, because a one-child wave is a hub and the
265
- chains through it collapse into one line each. Where both sides of a boundary hold several children
266
- the count grows — and that is the moment to ask whether the boundary is real, rather than to go
267
- looking for a shorter notation.
268
-
269
- ### Running one child alone is a wave of one
270
-
271
- **There is no `solo` mechanism, and none is needed.** "This one must not run beside anything" is the
272
- shape above with a wave of size one: chain into it every child that must not overlap it, and out of
273
- it every child that must not precede it. Adding a third axis — an exclusivity label, an
274
- `## Exclusive` section — would put a second way to say what the dependency declaration already says,
275
- and a second consistency check to keep the two agreeing.
276
-
277
- ### When a wave may be declared
278
-
279
- **Declare a boundary only where the later wave needs the earlier wave's artifact.** That is the whole
280
- condition, and it is narrower than it sounds:
281
-
282
- - **A mere preference of order is not a dependency.** Wanting one child to go first — to see its
283
- result early, to get the risky one out of the way — is presentation order, and joshuafolkken/kit#1583
284
- is where that belongs. Substituting a dependency for it buys the order and pays with the stall below.
285
- **Write it with `--order-before <M>` / `--order-after <M>`** (joshuafolkken/kit#1738), which move the
286
- task-list row and declare nothing; `--before` / `--after` are for an order that really is a
287
- dependency. Neither of the order-only flags helps at a **wave boundary**, which is a dependency by
288
- definition — the hub case below stays what it is.
289
- - **Two children that would edit the same file** are the case with no better answer today: separating
290
- them into different waves is how it is done, and the price — a fixed order, plus the stall — is
291
- accepted knowingly rather than by default.
292
- - **No reason to wait means no declaration.** An epic whose children are mostly independent says so in
293
- prose and leaves the graph empty.
294
-
295
- ### What a jam does, and how it is cleared
296
-
297
- **Declare a boundary and one stuck child in the earlier wave stops every wave behind it.**
298
- `from_blockers` in `scripts/epic/epic-classify.ts` propagates `human`: a wave-1 child labelled
299
- `needs-decision` makes every child behind it "waiting on a person" rather than "waiting on time".
300
-
301
- **The park itself is not the halt, and reading it as one hides how the cost arrives.** `backlogrun` parks
302
- that child and goes on running the rest of wave 1 exactly as it always does; the session stops once
303
- those close and nothing is runnable, because every later wave has been classified `human`. So a
304
- boundary is paid for at the *end* of the wave rather than at the moment one child parks — which is
305
- what makes one easy to declare and expensive to have declared. **Clearing it is removing that child's
306
- `needs-decision`** once the decision has been recorded — the removal recording a decision already
307
- requires — after which the waves behind it become runnable on the next round.
308
-
309
- This is why the answer to "how do I build waves" is a paragraph rather than a feature. **A wave is
310
- declared for a real wait and removed when the wait ends; it is not the standing shape of an epic.**
311
-
312
- ### The worked example — joshuafolkken/kit#1474
313
-
314
- That epic has grown to **46 children while its declared chains have stayed at three**, and its body
315
- says why:
316
-
317
- > **鎖として宣言していない。** 19 件はほぼ独立しており、順序を依存として宣言すると 1 件詰まっただけ
318
- > で残り全部が止まる。以下は推奨であって制約ではない。
319
-
320
- The nineteen that paragraph was written about are ordered in **prose — a group, not a graph** — so a
321
- reader gets the intent and no run is stopped by it, and none of the children added since has needed a
322
- chain either. joshuafolkken/kit#1262 is the same lesson from the other
323
- side: **24 false dependencies invented by `epic --add` have been removed from it**, each one an order
324
- nobody needed that could have stopped everything behind it.
325
-
326
219
  ## Epics that span repositories
327
220
 
328
221
  Write cross-repository children in the task list as `owner/repo#N` or a full issue URL. Their state
@@ -369,38 +262,6 @@ It runs right after an issue is filed: by `kickoff` / `fullrun` / `halfrun`, or
369
262
  mid-implementation, including inside a `backlogrun`. **`pnpm josh issue:file` runs it as its last step**;
370
263
  run it by hand only when that step printed `⚠`. **It recommends; it writes nothing.**
371
264
 
372
- "Two or more always means an epic" only fires when one request is split on the spot. Two issues filed
373
- days apart that turn out to be the front and back of one job are executed separately, in whatever
374
- order, with the reasoning recorded nowhere.
375
-
376
- **Only two things count as a signal**: the issues referring to each other in prose, or a `blocked-by`
377
- already recorded between them. **A similar title never counts on its own** — "related" expands
378
- without limit, and the threshold is what keeps an unrelated issue out.
379
-
380
- **The search is not limited to the open backlog.** Every issue number the subject's body names is read
381
- on its own, whatever its state — otherwise the command answers correctly only in the minutes between
382
- a follow-up issue being filed and its parent closing (joshuafolkken/kit#947). A **closed** reference
383
- counts only when an open epic already tracks it, since the answer worth recovering is `add_to_epic`;
384
- an epic created over a closed issue has nothing left to run. An **open** reference counts either way.
385
- A read that fails — and a reference the per-issue cap never reached — is reported as a gap, never
386
- folded into "no strong signal". A number that answers with a **pull request** is not a candidate at
387
- all: `repos/{owner}/{repo}/issues/<N>` serves one too, and a merged PR does not report `CLOSED`.
388
-
389
- **A number that does not exist is not a gap.** A typo, or another repository's number quoted in
390
- prose, is dropped in silence — neither a candidate nor something the command reports it could not
391
- read. Reported as a gap it puts `⚠ Could not read #N.` above the verdict, and the could-not-answer rule
392
- (`prompts/review.md` → "Three-way disposition after the cap") stops an unattended run on exactly that, for a reference that never existed (joshuafolkken/kit#957). **The two
393
- are told apart by HTTP status, never by `gh`'s wording**: 404 is nothing at that number, 403 and 429
394
- are a rate limit. GitHub answers 404 for an issue the token may not see as well, so as not to leak
395
- its existence — which does not reach this command, because it probes the repository whose open issues
396
- it has just listed. The probe costs one REST request and runs **only** when a read has already
397
- failed, and only on the path that needs the distinction: the backlog's own relation reads, up to two
398
- hundred of them, never pay it, and the referenced-number reads are capped at twenty. **Only the
399
- subject's own body is followed** — the reverse direction would scan every closed issue, and a
400
- follow-up already names its parent. The reference parsing is the same implementation as
401
- `epic:audit`'s implicit-dependency check, applied to the backlog instead of one epic. The open backlog
402
- is small enough to scan whole, so there is no index or cache.
403
-
404
265
  | Candidates | Do | Tier |
405
266
  | --- | --- | --- |
406
267
  | **The new issue itself already has an epic** | Nothing — an issue belongs to at most one | — |
@@ -411,81 +272,8 @@ is small enough to scan whole, so there is no index or cache.
411
272
  | No strong signal | Nothing | — |
412
273
  | **The epic listing was cut short** | **Nothing** — every placing row above is withheld | — |
413
274
 
414
- **`none` is a legitimate answer** — a self-review outside any workflow has no current issue to point
415
- at, so the issue stays in the backlog: what the procedure requires is running the command and
416
- following its answer, not landing every issue in an epic. The review-cap follow-up's filing and the
417
- answer table it acts on are `prompts/review.md` → "Three-way disposition after the cap".
418
-
419
- **Every row that *places* the issue asserts a negative, so a cut epic listing withholds all of them**
420
- (joshuafolkken/kit#1697). "No epic already tracks this issue" — which `create_epic` asserts about the
421
- candidates too — is only as good as the listing it was read from, and an epic past the cut tracks its
422
- children invisibly. **`add_to_epic` rests on it as much as `create_epic` does**: adding the issue to
423
- the epic a *candidate* sits in, while an unseen epic already tracks the issue itself, is the same
424
- duplicate by another route. The cut was already on standard error
425
- (`⚠ The epic listing …`) while standard output went on printing an executable
426
- instruction such as `Create an epic for these (Tier A — do it).` — and the rule that reads a warning as "could not answer" (`prompts/review.md` → "Three-way
427
- disposition after the cap") is written for one above
428
- `Nothing to bundle.`, so it never reached this verdict. Acted on as Tier A, that is a **second epic
429
- over an already-tracked issue**, which the auto-close and `epic:next` cannot both be right about
430
- (joshuafolkken/kit#943). The verdict now says so itself:
431
-
432
- ```text
433
- Could not confirm which epic already tracks these — do not place this issue in one.
434
- the epics were not read in full, so an epic already tracking one of these may never have been seen
435
- Related: #1662
436
- ```
437
-
438
- **The children and the order are deliberately absent** — they are the recipe for the placement that
439
- line says not to make. The exit code stays `0`, as it does for every other "do nothing" answer. **What
440
- survives the cut is a membership that *was* found**: the first row above names the epic it read
441
- tracking the issue, and epics past the cut cannot unseat it.
442
-
443
- **Placing an issue is not merging epics, and reading it as one is what used to stop runs.** Bundling
444
- is reversible — one `epic --add` moves an issue to a different epic — so choosing between two
445
- candidate epics is Tier A: take the one you recommend, and write the decision (what was taken, what
446
- was rejected, why, and the date) on **both** the issue and the epic's `## Decisions`. **Merging two
447
- epics is a different action, and nothing here proposes it.**
448
-
449
- **An epic and its own parent no longer produce the spread verdict at all** (joshuafolkken/kit#1079).
450
- They were never two peers to choose between — the parent already contains the child — so the pair is
451
- narrowed to the inner epic and the issue is added there. The verdict had recorded three such false
452
- positives, one of which stopped a whole batch over an issue whose implementation was finished and
453
- whose pull request was mergeable. **The narrowing drops parents, never peers**: one unrelated epic
454
- beside a nested chain still asks, and so does a cyclic declaration, where there is no inner epic to
455
- pick. Stop only where the epics left are genuinely too close to separate, which is the toss-up
456
- Tier B is for and is rare.
457
-
458
- **The decision record is what pays for the autonomy.** Skipping it is not a shortcut past a
459
- formality — it is the half that makes an unattended choice auditable, and without it the run has
460
- simply taken a decision nobody can find afterwards.
461
-
462
- **Write both halves in the one call that places the issue: `pnpm josh epic --add <E> <N...>
463
- --decision-file <path|->`** (joshuafolkken/kit#1350). It appends the record to the epic's
464
- `## Decisions` inside the body edit the insertion already makes — so the epic half costs no round trip
465
- — and posts the same text as a comment on each child added. **Never hand-edit the epic body to add the
466
- entry**: that is the operation `--add` exists to remove, and paying for it by hand is why the entry got
467
- skipped. Two constraints on the record's own text, both refused before anything is written:
468
-
469
- - **No line that is *nothing but* a `#A -> #B` chain.** Such a line is read as part of the epic's
470
- declaration wherever it sits in the body, so the record would declare an order nobody decided. Quote
471
- the order inside backticks, or put it in a fenced block; either is accepted.
472
- - **The record must say something, and the path must be readable.** An empty file is refused, and so is
473
- `--decision-file` with no usable path — otherwise the insertion lands, no record is written anywhere,
474
- and the command still exits 0.
475
-
476
- Answers about children the epic already tracks cannot use this flag; the "Front-loading the
477
- decisions" section above says what to do instead.
478
-
479
- **Its sibling runs before the filing, not after it: `pnpm josh issue:scout "<title>"`.** That command
480
- answers the same epic question for an issue that does not exist yet — this decision, called rather
481
- than restated — and beside it the one thing this one deliberately refuses: whether the work has
482
- already been filed, from a title comparison (joshuafolkken/kit#1252). **Both run**, and neither
483
- replaces the other: `pnpm josh issue:file` runs the scout before it creates the issue, and
484
- `epic:bundle` afterwards, from the real number and the relations recorded against it.
485
- Full behavior: `docs/josh-commands-automation.md` → "`josh issue:file`".
486
-
487
- **When the relation carries an order, record it** in `blocked-by` and in the epic's `Dependencies` —
488
- on an addition as much as on a new epic. Without it the batch survives and the reason for it does
489
- not. An order **nobody declared is not invented**.
275
+ **A placing answer is acted on with `epic-bundle.md`** — what counts as a signal, the cut listing,
276
+ choosing between epics, and the `--decision-file` record that pays for the choice. `Nothing to
277
+ bundle.` needs no further read.
490
278
 
491
279
  History: `docs/maintainers/epic-commands-rationale.md` → "`epic:bundle` — why issues filed apart are bundled afterwards".
@@ -0,0 +1,115 @@
1
+ # `epic:bundle` — the detail behind the answer
2
+
3
+ **Read this when a filing's `epic:bundle` answer places the issue** — `add_to_epic`, `create_epic`,
4
+ or a choice between epics — or when the answer is one the summary in the `epic-commands` skill
5
+ (`SKILL.md` → "`josh epic:bundle <N>` — does this new issue belong with one already filed?") does not
6
+ settle. `Nothing to bundle.` needs neither.
7
+
8
+ "Two or more always means an epic" only fires when one request is split on the spot. Two issues filed
9
+ days apart that turn out to be the front and back of one job are executed separately, in whatever
10
+ order, with the reasoning recorded nowhere.
11
+
12
+ **Only two things count as a signal**: the issues referring to each other in prose, or a `blocked-by`
13
+ already recorded between them. **A similar title never counts on its own** — "related" expands
14
+ without limit, and the threshold is what keeps an unrelated issue out.
15
+
16
+ **The search is not limited to the open backlog.** Every issue number the subject's body names is read
17
+ on its own, whatever its state — otherwise the command answers correctly only in the minutes between
18
+ a follow-up issue being filed and its parent closing (joshuafolkken/kit#947). A **closed** reference
19
+ counts only when an open epic already tracks it, since the answer worth recovering is `add_to_epic`;
20
+ an epic created over a closed issue has nothing left to run. An **open** reference counts either way.
21
+ A read that fails — and a reference the per-issue cap never reached — is reported as a gap, never
22
+ folded into "no strong signal". A number that answers with a **pull request** is not a candidate at
23
+ all: `repos/{owner}/{repo}/issues/<N>` serves one too, and a merged PR does not report `CLOSED`.
24
+
25
+ **A number that does not exist is not a gap.** A typo, or another repository's number quoted in
26
+ prose, is dropped in silence — neither a candidate nor something the command reports it could not
27
+ read. Reported as a gap it puts `⚠ Could not read #N.` above the verdict, and the could-not-answer rule
28
+ (`prompts/review.md` → "Three-way disposition after the cap") stops an unattended run on exactly that, for a reference that never existed (joshuafolkken/kit#957). **The two
29
+ are told apart by HTTP status, never by `gh`'s wording**: 404 is nothing at that number, 403 and 429
30
+ are a rate limit. GitHub answers 404 for an issue the token may not see as well, so as not to leak
31
+ its existence — which does not reach this command, because it probes the repository whose open issues
32
+ it has just listed. The probe costs one REST request and runs **only** when a read has already
33
+ failed, and only on the path that needs the distinction: the backlog's own relation reads, up to two
34
+ hundred of them, never pay it, and the referenced-number reads are capped at twenty. **Only the
35
+ subject's own body is followed** — the reverse direction would scan every closed issue, and a
36
+ follow-up already names its parent. The reference parsing is the same implementation as
37
+ `epic:audit`'s implicit-dependency check, applied to the backlog instead of one epic. The open backlog
38
+ is small enough to scan whole, so there is no index or cache.
39
+
40
+ **`none` is a legitimate answer** — a self-review outside any workflow has no current issue to point
41
+ at, so the issue stays in the backlog: what the procedure requires is running the command and
42
+ following its answer, not landing every issue in an epic. The review-cap follow-up's filing and the
43
+ answer table it acts on are `prompts/review.md` → "Three-way disposition after the cap".
44
+
45
+ **Every row that *places* the issue asserts a negative, so a cut epic listing withholds all of them**
46
+ (joshuafolkken/kit#1697). "No epic already tracks this issue" — which `create_epic` asserts about the
47
+ candidates too — is only as good as the listing it was read from, and an epic past the cut tracks its
48
+ children invisibly. **`add_to_epic` rests on it as much as `create_epic` does**: adding the issue to
49
+ the epic a *candidate* sits in, while an unseen epic already tracks the issue itself, is the same
50
+ duplicate by another route. The cut was already on standard error
51
+ (`⚠ The epic listing …`) while standard output went on printing an executable
52
+ instruction such as `Create an epic for these (Tier A — do it).` — and the rule that reads a warning as "could not answer" (`prompts/review.md` → "Three-way
53
+ disposition after the cap") is written for one above
54
+ `Nothing to bundle.`, so it never reached this verdict. Acted on as Tier A, that is a **second epic
55
+ over an already-tracked issue**, which the auto-close and `epic:next` cannot both be right about
56
+ (joshuafolkken/kit#943). The verdict now says so itself:
57
+
58
+ ```text
59
+ Could not confirm which epic already tracks these — do not place this issue in one.
60
+ the epics were not read in full, so an epic already tracking one of these may never have been seen
61
+ Related: #1662
62
+ ```
63
+
64
+ **The children and the order are deliberately absent** — they are the recipe for the placement that
65
+ line says not to make. The exit code stays `0`, as it does for every other "do nothing" answer. **What
66
+ survives the cut is a membership that *was* found**: the table's first row names the epic it read
67
+ tracking the issue, and epics past the cut cannot unseat it.
68
+
69
+ **Placing an issue is not merging epics, and reading it as one is what used to stop runs.** Bundling
70
+ is reversible — one `epic --add` moves an issue to a different epic — so choosing between two
71
+ candidate epics is Tier A: take the one you recommend, and write the decision (what was taken, what
72
+ was rejected, why, and the date) on **both** the issue and the epic's `## Decisions`. **Merging two
73
+ epics is a different action, and nothing here proposes it.**
74
+
75
+ **An epic and its own parent no longer produce the spread verdict at all** (joshuafolkken/kit#1079).
76
+ They were never two peers to choose between — the parent already contains the child — so the pair is
77
+ narrowed to the inner epic and the issue is added there. The verdict had recorded three such false
78
+ positives, one of which stopped a whole batch over an issue whose implementation was finished and
79
+ whose pull request was mergeable. **The narrowing drops parents, never peers**: one unrelated epic
80
+ beside a nested chain still asks, and so does a cyclic declaration, where there is no inner epic to
81
+ pick. Stop only where the epics left are genuinely too close to separate, which is the toss-up
82
+ Tier B is for and is rare.
83
+
84
+ **The decision record is what pays for the autonomy.** Skipping it is not a shortcut past a
85
+ formality — it is the half that makes an unattended choice auditable, and without it the run has
86
+ simply taken a decision nobody can find afterwards.
87
+
88
+ **Write both halves in the one call that places the issue: `pnpm josh epic --add <E> <N...>
89
+ --decision-file <path|->`** (joshuafolkken/kit#1350). It appends the record to the epic's
90
+ `## Decisions` inside the body edit the insertion already makes — so the epic half costs no round trip
91
+ — and posts the same text as a comment on each child added. **Never hand-edit the epic body to add the
92
+ entry**: that is the operation `--add` exists to remove, and paying for it by hand is why the entry got
93
+ skipped. Two constraints on the record's own text, both refused before anything is written:
94
+
95
+ - **No line that is *nothing but* a `#A -> #B` chain.** Such a line is read as part of the epic's
96
+ declaration wherever it sits in the body, so the record would declare an order nobody decided. Quote
97
+ the order inside backticks, or put it in a fenced block; either is accepted.
98
+ - **The record must say something, and the path must be readable.** An empty file is refused, and so is
99
+ `--decision-file` with no usable path — otherwise the insertion lands, no record is written anywhere,
100
+ and the command still exits 0.
101
+
102
+ Answers about children the epic already tracks cannot use this flag; `SKILL.md` → "Recording a
103
+ decision" says what to do instead.
104
+
105
+ **Its sibling runs before the filing, not after it: `pnpm josh issue:scout "<title>"`.** That command
106
+ answers the same epic question for an issue that does not exist yet — this decision, called rather
107
+ than restated — and beside it the one thing this one deliberately refuses: whether the work has
108
+ already been filed, from a title comparison (joshuafolkken/kit#1252). **Both run**, and neither
109
+ replaces the other: `pnpm josh issue:file` runs the scout before it creates the issue, and
110
+ `epic:bundle` afterwards, from the real number and the relations recorded against it.
111
+ Full behavior: `docs/josh-commands-automation.md` → "`josh issue:file`".
112
+
113
+ **When the relation carries an order, record it** in `blocked-by` and in the epic's `Dependencies` —
114
+ on an addition as much as on a new epic. Without it the batch survives and the reason for it does
115
+ not. An order **nobody declared is not invented**.
@@ -0,0 +1,126 @@
1
+ # Execution waves — parallel, then one alone, then the rest
2
+
3
+ **Read this when an epic's children need an order between groups** — a wave boundary, or one child
4
+ that must run alone. It is a reference of the `epic-commands` skill (`SKILL.md`), which carries the
5
+ commands themselves; the dependency declaration this file builds on is `SKILL.md` → "`josh epic:next
6
+ <E…>` — what is runnable".
7
+
8
+ **A wave is not a mechanism, and there is nothing to add for one.** "Run #A, #B and #C together, then
9
+ #D by itself, then the rest together" is already expressible with the dependency declaration
10
+ `SKILL.md` describes, and wanting it only became common once parallel lanes
11
+ (joshuafolkken/kit#1170) let several children run at once. What was missing was never the mechanism —
12
+ it was that nobody had written down how to build one, or when not to (joshuafolkken/kit#1584).
13
+
14
+ ## The shape
15
+
16
+ **A wave boundary is drawn by chaining every child of the later wave to every child of the earlier
17
+ one.** Wave 1 `{#A, #B, #C}` → wave 2 `{#D}` → wave 3 `{#X, #Y, #Z}` is written in the epic's
18
+ `## Dependencies` as three chain lines:
19
+
20
+ ```
21
+ #A -> #D -> #X
22
+ #B -> #D -> #Y
23
+ #C -> #D -> #Z
24
+ ```
25
+
26
+ - **#A #B #C are blocked by nobody, so they start together** — as many at once as there are free lanes.
27
+ - **#D is `time` until all three have closed**, and becomes runnable the moment the last one does.
28
+ - **#X #Y #Z are blocked by #D, so #D necessarily runs alone.**
29
+ - **#D closing releases wave 3**, which again runs as wide as the lanes allow.
30
+
31
+ **The block above is an illustration, not something to paste.** `parse_dependency_chains` strips
32
+ fenced blocks before it matches, so a `## Dependencies` section whose chains sit inside a fence reads
33
+ as **no declaration at all** rather than as an error — and `#A` / `#B` are not issue numbers anyway.
34
+ The lines go into the epic body unfenced, with real numbers.
35
+
36
+ **Deleting a declared order is `pnpm josh epic --remove <E> <M> <N> [<N2> …]`, and never a hand edit
37
+ either** (joshuafolkken/kit#1712). The arguments are a path, so each consecutive pair is one order and
38
+ a whole chain goes in one call; the body's declaration and the `blocked-by` relations are written from
39
+ that one input, exactly as `--add` writes them. **The ends are never reconnected** — `#A -> #B -> #C`
40
+ minus `#B -> #C` leaves `#A -> #B`, and taking out a middle child's two links leaves `#A` and `#C`
41
+ unordered rather than declaring `#A -> #C`. Deleting the last link writes the unordered sentence, so
42
+ the section never ends up with no machine-readable declaration. Pass `--decision-file` to record why;
43
+ the record lands on the epic's `## Decisions` and on both ends of every deleted order. Doing either
44
+ half by hand is the `declaration_mismatch` below, arrived at from the other direction.
45
+
46
+ **Write the boundary with `pnpm josh epic --add`, not by hand.** A declared link with no recorded
47
+ `blocked-by` relation is the `declaration_mismatch` that `find_anomalies` reports, and it stops
48
+ `epic:next` and the run that consumes it outright; the rule `epic-bundle.md` ends on — record the order in `blocked-by` **and**
49
+ in `Dependencies` — is what a wave has to satisfy, and editing the body alone satisfies half of it.
50
+ **`--before <hub>` and `--after <hub>` are both refused while the hub sits in more than one chain
51
+ with nothing after it** — which is exactly what a hub is the moment the earlier wave has been chained
52
+ into it. `chains_containing` finds several indices; `is_branching_after` does not hold, because it
53
+ requires a successor in **every** chain naming the target and a hub has none in any of them; so
54
+ `ambiguous_position_error` is raised (`scripts/epic/epic-chains.ts`). **The first child of the
55
+ next wave therefore goes into the declaration by hand**, with its `blocked-by` recorded to match.
56
+ Once the hub has a successor, `--after <hub>` branches a new line and the rest of that wave goes in
57
+ with it.
58
+
59
+ **Nothing in the parsing or the consistency check forbids this.** `DECLARED_CHAIN_LINE` in `scripts/epic/epic-parse.ts`
60
+ asks only that a line be *nothing but* a chain, so there may be any number of chain lines and **one
61
+ child may be an endpoint of several of them** — `parse_dependency_links` expands each line
62
+ independently and flattens the result, dropping only self-loops. `find_anomalies` in
63
+ `scripts/epic/epic-graph.ts` rejects exactly two things, a **cycle** and a **disagreement between the
64
+ declaration and the recorded `blocked-by`**; a fan-in and a fan-out are neither.
65
+
66
+ **The number of lines is the size of the widest wave**, because a one-child wave is a hub and the
67
+ chains through it collapse into one line each. Where both sides of a boundary hold several children
68
+ the count grows — and that is the moment to ask whether the boundary is real, rather than to go
69
+ looking for a shorter notation.
70
+
71
+ ## Running one child alone is a wave of one
72
+
73
+ **There is no `solo` mechanism, and none is needed.** "This one must not run beside anything" is the
74
+ shape above with a wave of size one: chain into it every child that must not overlap it, and out of
75
+ it every child that must not precede it. Adding a third axis — an exclusivity label, an
76
+ `## Exclusive` section — would put a second way to say what the dependency declaration already says,
77
+ and a second consistency check to keep the two agreeing.
78
+
79
+ ## When a wave may be declared
80
+
81
+ **Declare a boundary only where the later wave needs the earlier wave's artifact.** That is the whole
82
+ condition, and it is narrower than it sounds:
83
+
84
+ - **A mere preference of order is not a dependency.** Wanting one child to go first — to see its
85
+ result early, to get the risky one out of the way — is presentation order, and joshuafolkken/kit#1583
86
+ is where that belongs. Substituting a dependency for it buys the order and pays with the stall below.
87
+ **Write it with `--order-before <M>` / `--order-after <M>`** (joshuafolkken/kit#1738), which move the
88
+ task-list row and declare nothing; `--before` / `--after` are for an order that really is a
89
+ dependency. Neither of the order-only flags helps at a **wave boundary**, which is a dependency by
90
+ definition — the hub case below stays what it is.
91
+ - **Two children that would edit the same file** are the case with no better answer today: separating
92
+ them into different waves is how it is done, and the price — a fixed order, plus the stall — is
93
+ accepted knowingly rather than by default.
94
+ - **No reason to wait means no declaration.** An epic whose children are mostly independent says so in
95
+ prose and leaves the graph empty.
96
+
97
+ ## What a jam does, and how it is cleared
98
+
99
+ **Declare a boundary and one stuck child in the earlier wave stops every wave behind it.**
100
+ `from_blockers` in `scripts/epic/epic-classify.ts` propagates `human`: a wave-1 child labelled
101
+ `needs-decision` makes every child behind it "waiting on a person" rather than "waiting on time".
102
+
103
+ **The park itself is not the halt, and reading it as one hides how the cost arrives.** `backlogrun` parks
104
+ that child and goes on running the rest of wave 1 exactly as it always does; the session stops once
105
+ those close and nothing is runnable, because every later wave has been classified `human`. So a
106
+ boundary is paid for at the *end* of the wave rather than at the moment one child parks — which is
107
+ what makes one easy to declare and expensive to have declared. **Clearing it is removing that child's
108
+ `needs-decision`** once the decision has been recorded — the removal recording a decision already
109
+ requires — after which the waves behind it become runnable on the next round.
110
+
111
+ This is why the answer to "how do I build waves" is a paragraph rather than a feature. **A wave is
112
+ declared for a real wait and removed when the wait ends; it is not the standing shape of an epic.**
113
+
114
+ ## The worked example — joshuafolkken/kit#1474
115
+
116
+ That epic has grown to **46 children while its declared chains have stayed at three**, and its body
117
+ says why:
118
+
119
+ > **鎖として宣言していない。** 19 件はほぼ独立しており、順序を依存として宣言すると 1 件詰まっただけ
120
+ > で残り全部が止まる。以下は推奨であって制約ではない。
121
+
122
+ The nineteen that paragraph was written about are ordered in **prose — a group, not a graph** — so a
123
+ reader gets the intent and no run is stopped by it, and none of the children added since has needed a
124
+ chain either. joshuafolkken/kit#1262 is the same lesson from the other
125
+ side: **24 false dependencies invented by `epic --add` have been removed from it**, each one an order
126
+ nobody needed that could have stopped everything behind it.