@lemoncode/lemony 0.2.0 → 0.3.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.
@@ -0,0 +1,316 @@
1
+ # Partition — oversized features into independently mergeable parts — Orchestrator companion
2
+
3
+ > Vendor companion to `.claude/agents/orchestrator.md` (the hat). **Read on
4
+ > demand, never at boot**: when a grill closes a PRD that looks like it bundles
5
+ > separable wants (the define's closing question), when RESUME or ORIENT lands on
6
+ > an issue carrying `harness:partition-plan`, or when a sub-agent raises an
7
+ > oversize discovery.
8
+
9
+ The task-fit dial already handles the **floor** — a task too small for the ceremony
10
+ is nudged toward `/triage` or `/bypass`. This companion is the **ceiling**: a
11
+ thorough define can produce one enormous task that, however well its groups are
12
+ cut inside, lands on **one branch and one PR**. When the work splits into several
13
+ **independently mergeable** units, the define should produce several issues —
14
+ each with its own branch, PR, merge gate, and revert — instead of one.
15
+
16
+ ## The boundary: an issue is a merge unit
17
+
18
+ - A **group** (a `tasks.md` step header) is review/checkpoint cadence **inside** one
19
+ merge unit: its tasks share a branch, everything lands in one PR, one revert takes
20
+ it all.
21
+ - An **issue** is an **independent merge unit**: its own branch from its base, its
22
+ own PR, merge gate, CI, and revert. Part 1 can be in the default branch while
23
+ Part 2 is being built.
24
+ - **Promotion rule** — a group cut promotes to an issue cut **iff both sides are
25
+ independently mergeable**: each side leaves the default branch green and shippable
26
+ without the other (or with only earlier chain links merged). A cut that cannot
27
+ produce independently mergeable units is **not a partition**; forcing it buys
28
+ stacked-PR pain with no benefit.
29
+ - **Size is the symptom that triggers looking; mergeability is the criterion that
30
+ decides.** A genuinely inseparable monolith is not partitioned — it is accepted as
31
+ one task, or the feature is renegotiated with the human (an honest define outcome).
32
+ - **The partition is a design decision, always proposed to the human**, who
33
+ approves, amends, rejects — or proposes different cuts. Never partition silently.
34
+
35
+ Partition is an **L1** affair: it rides the PRD → task transition of the full-SDD
36
+ round-trip. A triage (L2) task is small by construction and is never partitioned.
37
+
38
+ ## When to look — soft heuristics, no threshold
39
+
40
+ Judgment, with named cues and **no binding number** (a mechanical trigger would
41
+ measure something other than what matters):
42
+
43
+ - the PRD bundles **separable wants** — capabilities a user could receive one
44
+ without the other;
45
+ - the natural `tasks.md` grouping would exceed **~4–5 groups**;
46
+ - the grill closed decisions that **don't touch each other** — distinct seams, distinct
47
+ files, distinct risks.
48
+
49
+ The human gate absorbs the imprecision: an over-proposal costs one "no, it goes
50
+ together"; an under-proposal is visible at the spec gate (a huge `tasks.md`), where
51
+ the human can ask. The residual failure — never proposed, never asked — is today's
52
+ behaviour; nothing gets worse.
53
+
54
+ ## The closing question of the define
55
+
56
+ After the PRD closes and **before opening the task** (the Orchestrator's §L1
57
+ full-SDD round-trip, between the grill and opening the task):
58
+
59
+ 1. **Look.** Weigh the cues above against the PRD's closed decisions — that is where
60
+ the big seams show. No cue fires → continue the round-trip as today, silently.
61
+ There is no question to ask when nothing looks separable.
62
+ 2. **Propose, at a glance.** When a cut looks real, build the proposal so it can be
63
+ approved without re-reading the PRD: the slices, what each merges independently
64
+ of, what each cut buys, and the **dependency shape** —
65
+ - **siblings** — each part branches from the default branch, any order;
66
+ - **chain** — part k+1 assumes part k merged;
67
+ - **DAG** — a mix; name the edges.
68
+
69
+ Present it as a compact table (the parent body's `## Plan` below is the shape) and
70
+ ask **one** question: _approve this cut, amend it, reject it (one task as today),
71
+ or propose different cuts?_ This is the closing question of the define
72
+ conversation — one more question inside an existing one, not a new gate.
73
+
74
+ 3. **Act on the answer.** Reject → open the single task as today. Approve (or an
75
+ amended cut the human has OK'd) → create the **parent partition-plan issue**
76
+ below, then open **Part 1** as a normal task in this same session (the child
77
+ procedure below) and continue the round-trip from there — Part 1 is the task the
78
+ define hands off.
79
+
80
+ ## The parent partition-plan issue
81
+
82
+ One issue per partitioned feature: the feature's cover sheet and its at-a-glance
83
+ state. Create it with `gh issue create` — title `Partition plan — <feature>`, labels
84
+ `harness:managed` + **`harness:partition-plan`**, and **no** `harness:status:*`, no
85
+ `harness:sdd`: it is **not a task**. It never gets a branch, a spec, `progress.md`,
86
+ or an implementation; the label is the mechanical marker that keeps the task-fit
87
+ assessment and RESUME from ever treating it as implementable. Its body:
88
+
89
+ ```markdown
90
+ # Partition plan — <feature>
91
+
92
+ PRD: `docs/prds/<topic>-<date>.md`
93
+
94
+ ## Plan (approved <YYYY-MM-DD>)
95
+
96
+ | Part | Slice | Merges independently of | Depends on | What the cut buys |
97
+ | ---- | ---------------------- | ----------------------- | -------------- | ----------------- |
98
+ | 1 | <what this part ships> | Parts 2, 3 | — | <the benefit> |
99
+ | 2 | <what this part ships> | Part 3 | Part 1 (chain) | <the benefit> |
100
+
101
+ Dependency shape: <siblings | chain | DAG — the edges are the `Depends on` column>.
102
+
103
+ ## Status
104
+
105
+ - [ ] Part 1 — <slice> (not started)
106
+ - [ ] Part 2 — <slice> (not started)
107
+ ```
108
+
109
+ **Precondition**: the `harness:partition-plan` label must exist on the remote —
110
+ `install`/`update`/`repair` create it during label sync; an install that predates it
111
+ needs one `lemony repair` first (a raw `gh issue create --label` has no self-heal: `gh`
112
+ fails clean on "label not found", nothing half-created).
113
+
114
+ Each `## Status` row is edited in place with `gh issue edit --body-file` and moves through
115
+ these states: `- [ ] Part <k> — <slice> (not started)` → `- [ ] Part <k> — <slice> → #<id>
116
+ <status at opening>` when the part is opened → `- [x] Part <k> — <slice> → #<id> merged`
117
+ at the part's closeout. A part the human drops (an amendment, or partial abandonment)
118
+ becomes `- [x] Part <k> — <slice> — dropped <YYYY-MM-DD> (<why>)` — **ticked, never
119
+ deleted**, so the close rule below still fires and the plan stays readable as history (a
120
+ drop is recorded in the row alone). Amendments — changes to the cut itself — append to
121
+ `## Plan` as `Amended <YYYY-MM-DD>: <what changed and why>`; never silently rewritten. **The rows are a cache, not the truth**: the middle state is the
122
+ status at opening and is refreshed only at closeout, so before a decision that depends
123
+ on a part's state (sequential vs stacked, which part is next) confirm it against GitHub —
124
+ the child's issue state and its PR (`gh pr view <pr> --json state,mergedAt`). **Every
125
+ edit is read-modify-write**: read the parent body immediately before editing
126
+ (`gh issue view <parent> --json body`), replace only the row(s) you own, write, then
127
+ re-read to confirm your change is present — if another row moved underneath you (two
128
+ siblings closing out on two machines), redo the edit **once** on the fresh body; still
129
+ absent → stop and ask (match rows tolerant of a trailing `\r`: a body edited on the web
130
+ may carry CRLF). A parent whose body has no parseable `## Status` is not yours to guess
131
+ at: stop and ask the human.
132
+
133
+ ## Children: normal tasks, opened at their turn
134
+
135
+ A part is opened **when its turn comes**, not at approval — mirroring the lazy spec:
136
+ a row that is never opened is a cheap exit (dropping Part 3 after shipping 1–2
137
+ costs nothing unpaid). Opening a part is the round-trip's "open the task" step
138
+ **exactly as today** — `gh issue create` with the skeleton body, labels
139
+ `harness:managed` + `harness:sdd` + `harness:status:spec-in-progress`, the branch
140
+ `harness/<id>-<slug>` — plus **trace lines at the top of the skeleton body**, which
141
+ the Spec Author's `spec-to-issue` preserves when it replaces the skeleton:
142
+
143
+ ```markdown
144
+ Part <k> of #<parent> — <slice>
145
+ Depends on #<sibling> (merged | stacked — assumes it lands as-is)
146
+ ```
147
+
148
+ (A root part — Part 1, or a sibling with no dependency — carries only the first line.)
149
+ Then update the parent row to `→ #<id> spec-in-progress`. From here the child's
150
+ lifecycle — `harness:status:*` labels, spec gate, implementation, review, merge
151
+ gate, closeout — is **unchanged**.
152
+
153
+ **The spec is authored lazily, at the part's turn**, from the PRD (the shared truth)
154
+ plus the part's **effective base** — the default branch, or, when stacked, the
155
+ previous part's head. Hand the Spec Author, by reference: the PRD path, the parent
156
+ issue, the part's row (its slice), and the effective base. The spec covers **that
157
+ slice only**; PRD decisions outside it are constraints, not scope. No spec exists
158
+ for a part before its turn, so an earlier part's landing can never invalidate one.
159
+
160
+ ## Starting the next part: re-validate the cut
161
+
162
+ At each part's start, **before** opening its issue, re-read the parent and check the cut **against the current base** (cheap): do the remaining
163
+ slices still merge independently? Did an earlier part absorb or obsolete a later
164
+ slice? Did a review move the seam? The cut holds → open the part. It doesn't →
165
+ present the amendment to the human — the plan is theirs, changing it needs their
166
+ OK — and only then edit the parent (`Amended …` line + the `## Status` rows). What
167
+ can be invalidated is the **plan**; the per-part specs don't exist yet.
168
+
169
+ ## Chain start modes — part k+1 while part k is unmerged
170
+
171
+ When the next part's dependency k is at **`in-review`** (its PR open — e.g. waiting on
172
+ another human's merge), the human chooses how to start — ask, don't assume. An earlier k
173
+ (`spec-in-progress`, `in-progress`) has nothing to stack on: there is nothing to start;
174
+ offer to resume it (`/resume #<k-id>`) in this session. A k already merged
175
+ (closeout parked or not — confirm on GitHub) makes k+1 an ordinary sequential part.
176
+
177
+ - **Sequential post-merge** (the safe default): part k+1 starts when part k is in the
178
+ default branch. Nothing special.
179
+ - **Stacked start** (an explicit human choice, with **the assumption named at choice
180
+ time**: stacking bets that part k lands substantially as-is — if k's review forces
181
+ big changes, k+1 is built on sand; the developer takes that bet informed):
182
+ - branch from **k's head**:
183
+ `git fetch && git checkout -b harness/<id>-<slug> origin/harness/<k-id>-<k-slug>`;
184
+ - the lazy spec is authored against the effective base = PRD + k's head;
185
+ - at the review step, while k is still unmerged, the PR targets **k's branch as base**
186
+ — `gh pr create --base harness/<k-id>-<k-slug>` — and the Reviewer is handed **that
187
+ base** for its stale-approve record (the record's base is the PR's base **at review
188
+ time**; a later base change is not by itself a content change — the landing move
189
+ below); if k
190
+ has merged by then, do the landing move below first and open the PR against the
191
+ default branch as usual (`gh pr create --base` on a deleted branch fails);
192
+ - updates from k flow by **merging in, never rebasing** — a rebase needs a force-push on
193
+ a task branch (human-gated by house rules); merge is uglier history but safe. While k
194
+ is unmerged the source is `origin/harness/<k-id>-<k-slug>`. The merge-in is **your**
195
+ move at a checkpoint boundary (or the Implementer's on re-invoke, named in the spawn
196
+ prompt), never inside a staging save-point; under pre-commit review ON it goes right
197
+ after a checkpoint OK commit;
198
+ - record it in the child's trace line (`stacked — assumes it lands as-is`) and the
199
+ parent row (`→ #<id> spec-in-progress (stacked on #<k-id>)` — the status at opening,
200
+ as every row).
201
+
202
+ **The landing move — once k is merged (confirmed on GitHub, never inferred from the PR
203
+ base), at the next boundary the merge-in rule above allows:** (1) if k+1's PR still
204
+ targets `harness/<k-id>-…`, **retarget it
205
+ yourself** — `gh pr edit <pr> --base <default>` (the platform's automatic retarget
206
+ fires only when k's branch is deleted, whenever that happens — at k's closeout finalize,
207
+ or at once if the human merged with auto-delete; do not depend on it either way); (2)
208
+ `git fetch && git merge origin/<default>` into k+1 and push — the executor squashes, so
209
+ k's commits are not ancestors of the default branch and without this merge-in k+1's
210
+ merge-base is the old fork point and the stale-approve guard sees k's content as
211
+ unreviewed; k's squashed changes reconcile as identical hunks; (3) update the child's
212
+ trace line to `Depends on #<k-id> (merged)`. Do this **before** the (re-)review whenever
213
+ k has already merged, so review and gate share a base. The merge-in is an
214
+ **update-branch**: a standing APPROVE stays valid unless the PR's own diff against the
215
+ new base changed (a hand-resolved conflict touched k+1's hunks) — then route to
216
+ re-review; otherwise run the executor and let its stale-approve guard arbitrate.
217
+
218
+ **Merge-gate invariant: a part reaches its merge gate only when its PR base is the
219
+ default branch.** Before surfacing the gate of a stacked child — live, or on a
220
+ `/resume` of an `in-review` one — check `gh pr view <pr> --json baseRefName`. Base still
221
+ `harness/<k-id>-…` and k unmerged → k+1 **waits at `in-review`**, and say so to the
222
+ human: _"Part k+1 is approved but stacked on #<k-id>, still unmerged — it waits; don't
223
+ merge it in the GitHub UI, its base is k's branch"_ (the rule binds your merge; the
224
+ human's click needs the warning). Never merge a part into a sibling's branch: the code
225
+ would never reach the default branch, `Closes #<id>` would not fire, and closeout would
226
+ archive a task that never landed. k merged → the landing move above, then surface the
227
+ gate.
228
+
229
+ If k's review does force big changes, surface it at once — the human decides:
230
+ rework k+1 on the new k, wait, or drop it.
231
+
232
+ ## The discovery safety net
233
+
234
+ The primary split happens at the PRD → task transition, where no artifacts exist yet
235
+ to restructure. The safety net is the existing discovery machinery: when the **Spec
236
+ Author** (structuring a child) or an **Implementer** (mid-flight) finds the scope
237
+ hides **≥2 independently mergeable units the PRD didn't reveal**, that is a
238
+ legitimate discovery — raised with `raise-discovery` as **T2 UNSPECIFIED_DECISION**
239
+ (the plan is silent on the cut; partition or keep-together is the human's call) and
240
+ mediated with `resolve-discovery` as any other (the Orchestrator's §Discovery
241
+ mediation). On **"keep together"**: record the resolution and resume. On
242
+ **"partition"** in an L1 task the current task keeps the **first** unit and you run, in
243
+ this order:
244
+
245
+ 1. **Ask the second half in the same mediation** — later-unit work already on the branch
246
+ (commits; under pre-commit review ON, staged save-points): keep it or drop it? Name
247
+ the commits. (A second fork that only exists because of the first answer: record it in
248
+ the same `**Resolution**` block, no second entry.)
249
+ 2. **Create the parent** partition-plan issue with the current task as Part 1 (row
250
+ `→ #<id> <paused_from>` — `in-progress`, or `spec-in-progress` when the Spec Author
251
+ raised it) and the remaining units as later parts.
252
+ 3. **Add the trace lines at the top of the child's issue body yourself**
253
+ (read-modify-write; the body is the externalized spec — or still the skeleton, when
254
+ the Spec Author raised it at spec time).
255
+ 4. **Route the narrowing to the Spec Author** (`resolve-discovery` step 3, with the
256
+ decision and the parent row): it narrows the spec to the slice and re-runs
257
+ `spec-to-issue`, which preserves the trace lines you just added. In step-by-step mode
258
+ the narrowed `tasks.md` is re-presented at the next checkpoint — no new gate.
259
+ 5. **Record and resume**: the Implementer re-invoke carries the keep/drop answer — it
260
+ performs the `git revert`s by commit (never a force-push), or discards the staged
261
+ later-unit changes under pre-commit review ON — and resumes on the narrowed slice.
262
+
263
+ On an **L2 (triage) task** — no PRD — "partition" means the task was mis-levelled: keep
264
+ the triage on its first unit (you narrow its fix plan in the issue body, read-modify-write)
265
+ and capture the rest as `/spinoff` stubs (or a `/define` when it is a real feature); **no
266
+ parent is opened** — the parent/part mechanics are L1-only. Never partition silently from
267
+ inside a task.
268
+
269
+ ## RESUME and ORIENT on a partition parent
270
+
271
+ The queue listing `/resume` runs with no args includes
272
+ `gh issue list -l harness:partition-plan`. An open parent with unticked rows
273
+ surfaces as **"partition in progress: next part per the plan — Part <k>: <slice>"**, or
274
+ as **"partition in progress — waiting on #<id> (Part <j>)"** when the next row is blocked
275
+ by an in-flight child (add `, in-review — stacked start possible` when that child's PR is
276
+ open). The next part is the first row still **`(not started)`** whose
277
+ `Depends on` parts are merged — confirmed on GitHub, not from the row
278
+ (`gh issue view <id> --json state` and the child's PR, recovered from its branch
279
+ `harness/<id>-*`) — or at `in-review`, when the human takes a stacked start (above). A
280
+ row carrying `→ #<id>` is an **in-flight child, never re-opened**: it resumes **as today**
281
+ by its own id — offer to resume it (`/resume #<id>`) in this session; the parent is only
282
+ the map. If that child's issue is closed and its PR merged, the row missed its closeout
283
+ tick: if its
284
+ closeout never ran (label still `in-review`, no `_archive/<id>/` on the base) run
285
+ `task-closeout` for it first — it ticks the row itself; otherwise tick it now (and close
286
+ the parent if that was the last row) and move on. **No eligible row → nothing to start
287
+ from the parent**: say so, name the blocking child,
288
+ offer to resume it. Resuming the parent itself means **starting the next part**: the
289
+ re-validation above, then the child procedure, then the round-trip continues from its
290
+ UI-design gate and Spec Author dispatch onward, unchanged.
291
+
292
+ ## Closing the parent
293
+
294
+ - **Mechanical, at the last child's closeout.** `task-closeout`'s finalize step, on
295
+ a task whose body carries `Part <k> of #<parent>`, ticks that row and — when every row
296
+ is ticked, merged or dropped — closes the parent with a summary comment. No human
297
+ prompt: each merge already passed the human gate. The skill owns the mechanics
298
+ (read-modify-write, idempotent on a retried finalize, closed-parent no-op).
299
+ - **Partial abandonment — a human close.** When the human drops the remaining
300
+ parts, mark their rows dropped and close the parent yourself recording what shipped
301
+ and what was dropped and why
302
+ (`gh issue close <parent> --comment "Shipped: parts … · Dropped: parts … — <why>"`).
303
+ Unopened rows cost nothing. An opened-but-unmerged child that is dropped is closed
304
+ like any abandoned task: close its issue with a comment recording the drop, delete
305
+ its branch, no closeout (there is nothing merged to record).
306
+
307
+ ## Human touchpoints — zero new gate machinery
308
+
309
+ 1. **Partition approval** — the only new touch: one more question at the define's
310
+ close (cuts + dependency shape).
311
+ 2. **Spec gate per child** — unchanged, at each child's turn.
312
+ 3. **Stacked-start choice** — with the assumption named.
313
+ 4. **Plan amendments** — re-validation or discovery → presented, human approves.
314
+ 5. **Abandonment / parent close** — human; the mechanical close rides the last
315
+ merge gate.
316
+ 6. **Merge gates per child** — unchanged.