flow-workflows 0.89.0 → 0.91.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 (35) hide show
  1. package/adapters/codex/CORE.md +5 -3
  2. package/adapters/codex/skills/flow-bug-ship/SKILL.md +3 -3
  3. package/adapters/codex/skills/flow-feat-review/SKILL.md +2 -0
  4. package/adapters/codex/skills/flow-feat-ship/SKILL.md +38 -12
  5. package/adapters/codex/skills/flow-work-README/SKILL.md +1 -1
  6. package/adapters/codex/skills/flow-work-green/SKILL.md +20 -5
  7. package/adapters/codex/skills/flow-work-query/SKILL.md +3 -0
  8. package/adapters/codex/skills/flow-work-run/SKILL.md +3 -2
  9. package/adapters/gemini/CORE.md +5 -3
  10. package/adapters/gemini/commands/flow/bug/ship.toml +3 -3
  11. package/adapters/gemini/commands/flow/feat/review.toml +2 -0
  12. package/adapters/gemini/commands/flow/feat/ship.toml +38 -12
  13. package/adapters/gemini/commands/flow/work/README.toml +1 -1
  14. package/adapters/gemini/commands/flow/work/green.toml +20 -5
  15. package/adapters/gemini/commands/flow/work/query.toml +3 -0
  16. package/adapters/gemini/commands/flow/work/run.toml +3 -2
  17. package/adapters/hermes/CORE.md +5 -3
  18. package/adapters/hermes/skills/flow-bug-ship/SKILL.md +3 -3
  19. package/adapters/hermes/skills/flow-feat-review/SKILL.md +2 -0
  20. package/adapters/hermes/skills/flow-feat-ship/SKILL.md +38 -12
  21. package/adapters/hermes/skills/flow-work-README/SKILL.md +1 -1
  22. package/adapters/hermes/skills/flow-work-green/SKILL.md +20 -5
  23. package/adapters/hermes/skills/flow-work-query/SKILL.md +3 -0
  24. package/adapters/hermes/skills/flow-work-run/SKILL.md +3 -2
  25. package/adapters/opencode/CORE.md +5 -3
  26. package/adapters/opencode/commands/flow-bug-ship.md +3 -3
  27. package/adapters/opencode/commands/flow-feat-review.md +2 -0
  28. package/adapters/opencode/commands/flow-feat-ship.md +38 -12
  29. package/adapters/opencode/commands/flow-work-README.md +1 -1
  30. package/adapters/opencode/commands/flow-work-green.md +20 -5
  31. package/adapters/opencode/commands/flow-work-query.md +3 -0
  32. package/adapters/opencode/commands/flow-work-run.md +3 -2
  33. package/package.json +1 -1
  34. package/plugins/flow/.claude-plugin/plugin.json +1 -1
  35. package/plugins/flow/CHANGELOG.md +37 -0
@@ -17,7 +17,7 @@ Every `$flow-*` command assumes these rules. They are stated once, here, so a co
17
17
  carries what is specific to its phase. Read this once per session; a command that says "load
18
18
  `flow-core`" means this file.
19
19
 
20
- **This file belongs to flow `0.89.0`.** Compare it once, at the start of the session, against
20
+ **This file belongs to flow `0.91.0`.** Compare it once, at the start of the session, against
21
21
  `version` in `~/.claude/flow/.claude-plugin/plugin.json`. The two differing means the session
22
22
  is running a **mixture** — the commands from one copy of the plugin, these shared rules from another
23
23
  — which is exactly what happens when a branch or a release candidate is loaded over an installed
@@ -244,11 +244,12 @@ configuration; these orders are the whole interface, and nothing else about the
244
244
  assumed:
245
245
 
246
246
  ```
247
- <cmd> why → start | study | answer | review | pipeline | merged
247
+ <cmd> why → start | study | answer | review | pipeline | merged | conflict
248
248
  <cmd> ticket --json → {number, title, body, comments[{author, at, text}], base, kind}
249
249
  <cmd> answer → one option's exact text, or free text; exit 2 = no reply
250
250
  <cmd> events --json → {"comments":[{thread,kind,id,author,at,body,url,path?,line?}]}
251
251
  | {"sha":…, "failed":[{name,conclusion,url,log_tail}]}
252
+ | {"conflict":{"base":…, "base_sha":…}}
252
253
  <cmd> ask --question-file F --options-file F [--recommended N] [--free-text] [--gate NAME]
253
254
  <cmd> publish --title-file F --body-file F [--part N --of M]
254
255
  <cmd> blocked --reason-file F
@@ -308,7 +309,7 @@ run has.
308
309
  runs the study phases as any run does, and three rules make it stop where it should:
309
310
 
310
311
  - **The door to code.** The first step of `$flow-feat-build` and `$flow-bug-fix` pre-flight, in
311
- `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review` or `pipeline` → go on;
312
+ `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review`, `pipeline` or `conflict` → go on;
312
313
  `study` → the study ends here; anything else, or the order failing → `blocked`. Every path from a
313
314
  study to code crosses that door, an answered question that resumes straight into `build`
314
315
  included, so the phases before it carry no clause. The run kind is read there, never stored: the
@@ -351,6 +352,7 @@ person asking for changes, a ticket that already has code — is `$flow-work-run
351
352
  | The next MR/PR of a train (`ship` close) | record — not started: one MR/PR per unattended run |
352
353
  | `build` with no startable MR/PR (its `depends_on` not `merged` in `meta.json`) | blocked — the reason names what must merge; an unattended `build` never stacks a branch on another, and never creates a remote branch |
353
354
  | The parent of a stacked MR/PR merged — rebase and force-push | blocked — never force-push unattended |
355
+ | Integrating the base into the MR/PR's branch (`green`) | on `why = conflict`, record — `git merge origin/<base>` as the orchestrator left it, never rebase, each conflict resolved on its merits (`$flow-work-green` §5.C); on any other `why`, blocked — the orchestrator brings the base in |
354
356
  | Any offer about the work's own bookkeeping — a tracker issue, the first `KNOWLEDGE.md` or a knowledge save, a FLOW convention, an agent lesson or a saved specialist, the follow-up survey, carrying files over from the main checkout, a contract handoff, archiving the work | record — `Later`; nothing written outside the work folder |
355
357
  | A step the command cannot complete (a red suite it cannot fix, a tool missing, auth failing, the same check failing a second time) | blocked |
356
358
  | Anything `auto` already decides on its own (size, flow mechanics, WIP commits, the next command) | as `auto` |
@@ -185,9 +185,9 @@ With an "all threads resolved before merge" policy, the MR/PR cannot be merged o
185
185
 
186
186
  ### 3.3 Performance comment
187
187
 
188
- `$flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` and benchmark rows from
189
- `05-validation.md`, posted as a comment on the MR/PR, confirmed in every mode, nothing posted when
190
- nothing was measured. A slowness bug is the case where this comment *is* the proof the fix worked.
188
+ `$flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` — a modified query that was not
189
+ measured included, as `not measured (<reason>)` — and benchmark rows from `05-validation.md`,
190
+ posted as comments anchored on the changed lines of the MR/PR, confirmed in every mode. A slowness bug is the case where this comment *is* the proof the fix worked.
191
191
 
192
192
  ## 4. Close
193
193
 
@@ -212,6 +212,8 @@ Judging rules: **no number, no win** (unresolved is recorded unresolved, never s
212
212
 
213
213
  **A modified query is judged against its own past, not only against its rivals.** For every query the diff *changes* (as opposed to adds), the duel carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, measured in the same sandbox with the same seed (`$flow-work-query` §4). Without it the table cannot say whether this diff made things better or worse, which is the one thing a reviewer wants from it.
214
214
 
215
+ **A modified query with no plan behind its verdict is recorded `ok (plan not measured)` — and says what would measure it (`data.explain_cmd`, a data set) — never a bare `ok`.** Ship publishes that row on the MR/PR (`$flow-feat-ship` §4.3); a reviewer who finds a plain "ok" and no number has no way to know nobody looked.
216
+
215
217
  Verdicts as findings: **change** = blocker, through §6; **regressed** = blocker unless `03-design.md` justifies the trade, in which case it is stated and published; **schema / follow-up** → proposed ticket in the output **and a `followups[]` entry** (flow-core §7); **unresolved** → recorded literally with the open question (as §3.5). A `change` verdict resting on a measured plan is already confirmed — no §6 skeptic.
216
218
 
217
219
  ## 4. Over-engineering audit (fit + YAGNI)
@@ -240,22 +240,40 @@ Thread body: the SQL block under "Pre-deploy: run this SQL on the server BEFORE
240
240
 
241
241
  With the repo policy "all threads resolved before merge", the MR/PR cannot merge until the SQL is executed and the thread **resolved**. **One single thread even with multiple statements.** Tell the user it is left open on purpose.
242
242
 
243
- ### 4.3 Performance comment (only when there are numbers)
244
-
245
- `06-review.md` §"Data-access duel" has rows for modified queries, or `07-validation.md` §3.7 has
246
- benchmark rows → post them as a **comment** on the MR/PR just created (`gh pr comment` /
247
- `glab mr note`). Nothing measured → post nothing; an empty performance comment is noise.
248
-
249
- A comment, not a description section, because it is **re-posted on every push that changes a
250
- measured path** (`$flow-work-green`, `$flow-work-respond`), and a description rewritten each round
243
+ ### 4.3 Performance comment (every modified query, measured or not)
244
+
245
+ Post one when `06-review.md` §"Data-access duel" has a row for a **modified** query — **whatever its
246
+ verdict, measured or not** — or `07-validation.md` §3.7 has benchmark rows. New queries only, or
247
+ renames and reformats only, and nothing benchmarked → post nothing.
248
+
249
+ **A modified query that was never measured is the case this comment exists for.** Silence reads as
250
+ "nothing to see"; a reviewer cannot tell a query that was measured and is fine from one that nobody
251
+ looked at. The row says `not measured (<reason>)` and what would settle it (`data.explain_cmd`, a
252
+ data set, the entry point to run), so the reviewer knows what was *not* checked.
253
+
254
+ **Where it goes: on the query, not under the title.** One **inline thread anchored to the changed
255
+ lines of that query in the MR/PR diff** (a resolvable GitLab discussion with a `position`; a GitHub
256
+ review comment on `path` + `line`), one per modified query. The entry-point rows anchor to the entry
257
+ point's changed line the same way. Anchoring fails (the line is not in the diff, the forge refuses
258
+ the position) → a general MR/PR comment that names the file and line, and say so. A general comment
259
+ is the fallback, never the default: a number a reviewer meets on the line they are reading is read;
260
+ one at the bottom of the page is not.
261
+
262
+ Still a comment and not a description section, because it is **re-posted on every push that changes
263
+ a measured path** (`$flow-work-green`, `$flow-work-respond`), and a description rewritten each round
251
264
  loses what the numbers were two rounds ago. The history is the point.
252
265
 
266
+ For each modified query the comment carries the **plan before and after** — the `data.explain_cmd`
267
+ output on the merge base and on the branch, same data, trimmed to access type, index, rows read — next
268
+ to the numbers:
269
+
253
270
  ```markdown
254
- ## Performance — base `<sha>` → `<sha>`
271
+ ## Performance — `FooRepository::findBar()`, base `<sha>` → `<sha>`
255
272
 
256
- | Query | Rows read | Time (min–max, 3 runs) | Verdict |
257
- |---|---|---|---|
258
- | `FooRepository::findBar()` | 41k → 750 | 120–131 ms → 8–9 ms | improved |
273
+ | | Plan (access · index · rows read) | Rows returned | Time (min–max, 3 runs) | Verdict |
274
+ |---|---|---|---|---|
275
+ | base | `ref` · `idx_a` · 41k | 15 | 120–131 ms | |
276
+ | now | `range` · `idx_b` · 750 | 15 | 8–9 ms | improved |
259
277
 
260
278
  | Entry point | Time | Peak memory | Verdict |
261
279
  |---|---|---|---|
@@ -265,6 +283,14 @@ Measured on <data set>, <n> runs each. A difference inside the run-to-run spread
265
283
  no measurable change, not a percentage.
266
284
  ```
267
285
 
286
+ - **The query, ready to run where the data is real.** Every performance comment ends with the exact
287
+ query (as the engine receives it, parameters filled with representative values) and the read-only
288
+ plan command for it — `EXPLAIN`, never `EXPLAIN ANALYZE` on a write — in a code block, so the
289
+ author can run it on production or a replica and answer in the thread. This is a complement to the
290
+ sandbox measurement, never a substitute: the flow does not touch production.
291
+ - **Memory is the entry point's, not the query's.** A modified query also gets the entry-point row
292
+ that exercises it, through `quality.bench_cmd` on both sides (`$flow-*-validate` §3.7). No
293
+ `bench_cmd` → the entry-point row says `not measured (no quality.bench_cmd)`; never an estimate.
268
294
  - **Verdicts**: `improved` · `no measurable change` · `regressed` · `not measured (<reason>)`.
269
295
  - **A `regressed` row is a review blocker** unless `03-design.md` justifies the trade — in which
270
296
  case the justification goes in the comment, in one line, next to the row.
@@ -50,7 +50,7 @@ This system **orchestrates** the project's existing sub-agents and skills (it do
50
50
  - **Understand before starting**: design-affecting doubts left after ticket + knowledge search + code → one `AskUserQuestion` with all of them before closing `$flow-feat-start` or `$flow-feat-design` §1.5.
51
51
  - **Reuse before creating**: in `$flow-feat-design`, check the affected and neighbouring modules before any new entity, column, repository, service or event; justify intentional duplication in `03-design.md`; never force a poor fit.
52
52
  - **Solve the real project problem, not the generic one (fit + YAGNI)**: before any defensive mechanism, **two questions with evidence** — **(a) Does it fit? Can this scenario actually happen given how this project works?** (knowledge search + code, **not** textbook patterns; already prevented → **unnecessary**); **(b) Do we need it now, for what the ticket asks?** (hypothetical → **don't add it**, "idea for a separate ticket"). Bias **remove, not add** — `$flow-feat-design` challenger ("Fit and need") and the defensive mechanisms table of `03-design.md`.
53
- - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`$flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`$flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers are posted as an MR/PR **comment** (`ship` §4.3), re-posted whenever a later push changes a measured path.
53
+ - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`$flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`$flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers — or, for a modified query nobody could measure, `not measured` and why — are posted as **inline comments on the changed query** (`ship` §4.3), re-posted whenever a later push changes a measured path.
54
54
  - **A query is judged on its plan, not on its prose**: cost lives in the **execution plan**, not the diff. `$flow-work-query`: facts, a challenger blinded to the design's rationale, the main agent judges — **no number, no win**, **no dogma in either direction**, **the objector's variant measured next to yours**. **Any size, XS included.** A performance objection is answered with a plan; an unmeasured plan is never reported as measured ("schema only" / "not measured" are legitimate verdicts; a fixture-sized test database is not evidence).
55
55
  - **Size is revisable**: a later phase (`design`, `plan`, `investigate`) seeing a clear mismatch **proposes reclassifying to the user** and updates `meta.json.size`.
56
56
  - **If the implementation invalidates the design, return to design**: in `$flow-feat-build`, 2+ significant deviations in `05-implementation.md`, or one changing an ADR-light decision → **pause and return to `$flow-feat-design`** (what `$flow-feat-review` and `$flow-feat-validate` read). Same for `$flow-bug-fix` vs `$flow-bug-investigate`.
@@ -45,19 +45,34 @@ Read `~/.claude/flow/CORE.codex.md` first (shared rules: `FLOW.md` step 0, model
45
45
  4. **Integrating the base branch** — any `git merge`/`git rebase` of `git.default_base` into the branch, and any history rewrite or force-push it implies (§5.C).
46
46
  5. **Green-washing — NEVER, in any mode:** no rerun of a failed check hoping it passes; never disable, skip, `@skip`/`xit`, loosen a threshold, or delete a test/lint rule to force green; never resolve a conflict by blindly taking one side (`--ours`/`--theirs`, `checkout --theirs`, discarding the base's changes) — resolve by understanding both sides.
47
47
 
48
- **In `unattended`** (flow-core §2.1) — launched by `$flow-work-run` on `why = pipeline` — this
49
- command asks nothing and never touches the forge. Gate 5 holds exactly as written; the others
50
- resolve this way:
48
+ **In `unattended`** (flow-core §2.1) — launched by `$flow-work-run` on `why = pipeline` or
49
+ `why = conflict` — this command never touches the forge. Gate 5 holds exactly as written; the
50
+ others resolve this way:
51
51
 
52
52
  - **§1–§2** — no MR/PR is resolved and no forge is read: the failures are `<cmd> events --json` →
53
53
  `{"sha", "failed": [{name, conclusion, url, log_tail}]}`, and `log_tail` is the evidence. The order
54
54
  failing → `blocked`; `failed` empty → `done`, the summary saying so. `sha` not `HEAD` → `blocked`:
55
55
  the result is about another commit, and fixing it here would be fixing a guess.
56
+ On `why = conflict` the events are `{"conflict": {"base", "base_sha"}}` instead: the MR/PR
57
+ conflicts with `base`, which the orchestrator left in the checkout as `origin/<base>` at
58
+ `base_sha`. That is the only blocker of the round — category C, nothing to triage. `origin/<base>`
59
+ missing or not at `base_sha` → `blocked`: the run would merge a base nobody named.
56
60
  - **§3** — triage as written, recorded in `09-ci.md`. **K** (flaky / infra) → `blocked` with the
57
61
  evidence: the run has no rerun order, and the orchestrator owns reruns and the red-round limit.
58
- **C** (conflict, behind base) and anything needing a rebase or force-push → `blocked`: the
59
- orchestrator brings the base in. A **human** blocker → `blocked`, naming who. **S** → fixed within
62
+ **C** (conflict, behind base) on `why = pipeline`, and anything needing a rebase or force-push →
63
+ `blocked`: the orchestrator brings the base in, and relaunches with `why = conflict` when it
64
+ conflicts. A **human** blocker → `blocked`, naming who. **S** → fixed within
60
65
  scope, otherwise `blocked` with the justification.
66
+ - **§5.C on `why = conflict`** — gate 4 is recorded (`unattended:integrate-base`), never asked:
67
+ the orchestrator launched the run for exactly this. No fetch: the base is `origin/<base>` as the
68
+ orchestrator left it, never `git.default_base` read from the config. Always `git merge
69
+ origin/<base>`, never a rebase. Each conflict is resolved on its merits as step 3 says; step 6's
70
+ "intent of either side unclear" → `git merge --abort`, then question `decision` when the two
71
+ sides want things that contradict each other (a product decision: which intent wins), `blocked`
72
+ otherwise. The base it merges goes into this round of `09-ci.md` before the merge starts; the
73
+ answer re-enters at §5.C, reads the base there, and merges again from `origin/<base>` — an
74
+ unfinished merge does not survive the run. Step 4's design change that is more than a tweak →
75
+ `blocked`. The merge commit, after the full local gate, is the round's commit.
61
76
  - **§5** — fixes and the local verification as written; a local gate it cannot get green →
62
77
  `blocked`. A fix adding behaviour gets its brief recorded, not asked; a fix that needs a schema
63
78
  change, a design invalidation that is more than a tweak, or a high-severity finding from the
@@ -149,6 +149,9 @@ Per query, exactly one of:
149
149
 
150
150
  - **ok** — access supported and bounded; state the index used and rows read. For a modified query,
151
151
  say how it compares to the base row: *improved*, *no measurable change*, or *regressed*.
152
+ **No plan was read → it is `ok (plan not measured)`**, with the reason, never a bare `ok`: a
153
+ modified query reaches the MR/PR through `$flow-feat-ship` §4.3 and the reviewer must be able to
154
+ tell it was never measured.
152
155
  - **regressed** — measurably worse than the base version, and the design does not justify it. A
153
156
  blocker, the same as `change`. Justified in `03-design.md` (a correctness fix that costs time, a
154
157
  deliberate trade) → not a blocker, but it is stated in the verdict and it reaches the MR/PR.
@@ -36,7 +36,7 @@ of truth, and an argument that disagrees with it is ignored and named in the log
36
36
  other mode a person is the router (`$flow-next`).
37
37
  - `autonomy.orchestrator_cmd` empty → flow-core §2.1: no phase runs.
38
38
  - Run `<cmd> why`. It fails, or prints anything but `start`, `study`, `answer`, `review`,
39
- `pipeline` or `merged` → print what it returned and its stderr in the §3 header and end. No closing order: an
39
+ `pipeline`, `merged` or `conflict` → print what it returned and its stderr in the §3 header and end. No closing order: an
40
40
  orchestrator that cannot say why it launched the run cannot take one either.
41
41
  - Run `<cmd> ticket --json` and keep `number`, `title`, `base` and `kind` (`kind` absent or `null` → `feat`: the tracker could not tell, so flow decides). `kind` is re-read on every run; only a run that starts the work uses it — once the work exists, its folder says feat or bug.
42
42
  It fails or prints no `number` → `blocked` (flow-core §2.1).
@@ -64,7 +64,8 @@ of truth, and an argument that disagrees with it is ignored and named in the log
64
64
  | `merged` | none, or nothing published | `blocked` — nothing of this ticket was published from here |
65
65
  | `review` | exists | `$flow-work-respond` — the threads come from `<cmd> events --json` |
66
66
  | `pipeline` | exists | `$flow-work-green` — the failed jobs come from `<cmd> events --json` |
67
- | `review` · `pipeline` | none | `blocked` — there is no work of this ticket to answer for |
67
+ | `conflict` | exists | `$flow-work-green` — the MR/PR conflicts with its base; which base, and where it stands, come from `<cmd> events --json` |
68
+ | `review` · `pipeline` · `conflict` | none | `blocked` — there is no work of this ticket to answer for |
68
69
 
69
70
  **The work's branch, on `start` and `study`.** The current branch is `meta.json.branch` → nothing to do. It is
70
71
  not — a study made on another machine arrives on the ticket's `base`, a run that died before its
@@ -16,7 +16,7 @@ Every `/flow:*` command assumes these rules. They are stated once, here, so a co
16
16
  carries what is specific to its phase. Read this once per session; a command that says "load
17
17
  `flow-core`" means this file.
18
18
 
19
- **This file belongs to flow `0.89.0`.** Compare it once, at the start of the session, against
19
+ **This file belongs to flow `0.91.0`.** Compare it once, at the start of the session, against
20
20
  `version` in `~/.claude/flow/.claude-plugin/plugin.json`. The two differing means the session
21
21
  is running a **mixture** — the commands from one copy of the plugin, these shared rules from another
22
22
  — which is exactly what happens when a branch or a release candidate is loaded over an installed
@@ -243,11 +243,12 @@ configuration; these orders are the whole interface, and nothing else about the
243
243
  assumed:
244
244
 
245
245
  ```
246
- <cmd> why → start | study | answer | review | pipeline | merged
246
+ <cmd> why → start | study | answer | review | pipeline | merged | conflict
247
247
  <cmd> ticket --json → {number, title, body, comments[{author, at, text}], base, kind}
248
248
  <cmd> answer → one option's exact text, or free text; exit 2 = no reply
249
249
  <cmd> events --json → {"comments":[{thread,kind,id,author,at,body,url,path?,line?}]}
250
250
  | {"sha":…, "failed":[{name,conclusion,url,log_tail}]}
251
+ | {"conflict":{"base":…, "base_sha":…}}
251
252
  <cmd> ask --question-file F --options-file F [--recommended N] [--free-text] [--gate NAME]
252
253
  <cmd> publish --title-file F --body-file F [--part N --of M]
253
254
  <cmd> blocked --reason-file F
@@ -307,7 +308,7 @@ run has.
307
308
  runs the study phases as any run does, and three rules make it stop where it should:
308
309
 
309
310
  - **The door to code.** The first step of `/flow:feat:build` and `/flow:bug:fix` pre-flight, in
310
- `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review` or `pipeline` → go on;
311
+ `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review`, `pipeline` or `conflict` → go on;
311
312
  `study` → the study ends here; anything else, or the order failing → `blocked`. Every path from a
312
313
  study to code crosses that door, an answered question that resumes straight into `build`
313
314
  included, so the phases before it carry no clause. The run kind is read there, never stored: the
@@ -350,6 +351,7 @@ person asking for changes, a ticket that already has code — is `/flow:work:run
350
351
  | The next MR/PR of a train (`ship` close) | record — not started: one MR/PR per unattended run |
351
352
  | `build` with no startable MR/PR (its `depends_on` not `merged` in `meta.json`) | blocked — the reason names what must merge; an unattended `build` never stacks a branch on another, and never creates a remote branch |
352
353
  | The parent of a stacked MR/PR merged — rebase and force-push | blocked — never force-push unattended |
354
+ | Integrating the base into the MR/PR's branch (`green`) | on `why = conflict`, record — `git merge origin/<base>` as the orchestrator left it, never rebase, each conflict resolved on its merits (`/flow:work:green` §5.C); on any other `why`, blocked — the orchestrator brings the base in |
353
355
  | Any offer about the work's own bookkeeping — a tracker issue, the first `KNOWLEDGE.md` or a knowledge save, a FLOW convention, an agent lesson or a saved specialist, the follow-up survey, carrying files over from the main checkout, a contract handoff, archiving the work | record — `Later`; nothing written outside the work folder |
354
356
  | A step the command cannot complete (a red suite it cannot fix, a tool missing, auth failing, the same check failing a second time) | blocked |
355
357
  | Anything `auto` already decides on its own (size, flow mechanics, WIP commits, the next command) | as `auto` |
@@ -181,9 +181,9 @@ With an "all threads resolved before merge" policy, the MR/PR cannot be merged o
181
181
 
182
182
  ### 3.3 Performance comment
183
183
 
184
- `/flow:feat:ship` §4.3, unchanged: query rows from `06-review.md` and benchmark rows from
185
- `05-validation.md`, posted as a comment on the MR/PR, confirmed in every mode, nothing posted when
186
- nothing was measured. A slowness bug is the case where this comment *is* the proof the fix worked.
184
+ `/flow:feat:ship` §4.3, unchanged: query rows from `06-review.md` — a modified query that was not
185
+ measured included, as `not measured (<reason>)` — and benchmark rows from `05-validation.md`,
186
+ posted as comments anchored on the changed lines of the MR/PR, confirmed in every mode. A slowness bug is the case where this comment *is* the proof the fix worked.
187
187
 
188
188
  ## 4. Close
189
189
 
@@ -208,6 +208,8 @@ Judging rules: **no number, no win** (unresolved is recorded unresolved, never s
208
208
 
209
209
  **A modified query is judged against its own past, not only against its rivals.** For every query the diff *changes* (as opposed to adds), the duel carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, measured in the same sandbox with the same seed (`/flow:work:query` §4). Without it the table cannot say whether this diff made things better or worse, which is the one thing a reviewer wants from it.
210
210
 
211
+ **A modified query with no plan behind its verdict is recorded `ok (plan not measured)` — and says what would measure it (`data.explain_cmd`, a data set) — never a bare `ok`.** Ship publishes that row on the MR/PR (`/flow:feat:ship` §4.3); a reviewer who finds a plain "ok" and no number has no way to know nobody looked.
212
+
211
213
  Verdicts as findings: **change** = blocker, through §6; **regressed** = blocker unless `03-design.md` justifies the trade, in which case it is stated and published; **schema / follow-up** → proposed ticket in the output **and a `followups[]` entry** (flow-core §7); **unresolved** → recorded literally with the open question (as §3.5). A `change` verdict resting on a measured plan is already confirmed — no §6 skeptic.
212
214
 
213
215
  ## 4. Over-engineering audit (fit + YAGNI)
@@ -236,22 +236,40 @@ Thread body: the SQL block under "Pre-deploy: run this SQL on the server BEFORE
236
236
 
237
237
  With the repo policy "all threads resolved before merge", the MR/PR cannot merge until the SQL is executed and the thread **resolved**. **One single thread even with multiple statements.** Tell the user it is left open on purpose.
238
238
 
239
- ### 4.3 Performance comment (only when there are numbers)
240
-
241
- `06-review.md` §"Data-access duel" has rows for modified queries, or `07-validation.md` §3.7 has
242
- benchmark rows → post them as a **comment** on the MR/PR just created (`gh pr comment` /
243
- `glab mr note`). Nothing measured → post nothing; an empty performance comment is noise.
244
-
245
- A comment, not a description section, because it is **re-posted on every push that changes a
246
- measured path** (`/flow:work:green`, `/flow:work:respond`), and a description rewritten each round
239
+ ### 4.3 Performance comment (every modified query, measured or not)
240
+
241
+ Post one when `06-review.md` §"Data-access duel" has a row for a **modified** query — **whatever its
242
+ verdict, measured or not** — or `07-validation.md` §3.7 has benchmark rows. New queries only, or
243
+ renames and reformats only, and nothing benchmarked → post nothing.
244
+
245
+ **A modified query that was never measured is the case this comment exists for.** Silence reads as
246
+ "nothing to see"; a reviewer cannot tell a query that was measured and is fine from one that nobody
247
+ looked at. The row says `not measured (<reason>)` and what would settle it (`data.explain_cmd`, a
248
+ data set, the entry point to run), so the reviewer knows what was *not* checked.
249
+
250
+ **Where it goes: on the query, not under the title.** One **inline thread anchored to the changed
251
+ lines of that query in the MR/PR diff** (a resolvable GitLab discussion with a `position`; a GitHub
252
+ review comment on `path` + `line`), one per modified query. The entry-point rows anchor to the entry
253
+ point's changed line the same way. Anchoring fails (the line is not in the diff, the forge refuses
254
+ the position) → a general MR/PR comment that names the file and line, and say so. A general comment
255
+ is the fallback, never the default: a number a reviewer meets on the line they are reading is read;
256
+ one at the bottom of the page is not.
257
+
258
+ Still a comment and not a description section, because it is **re-posted on every push that changes
259
+ a measured path** (`/flow:work:green`, `/flow:work:respond`), and a description rewritten each round
247
260
  loses what the numbers were two rounds ago. The history is the point.
248
261
 
262
+ For each modified query the comment carries the **plan before and after** — the `data.explain_cmd`
263
+ output on the merge base and on the branch, same data, trimmed to access type, index, rows read — next
264
+ to the numbers:
265
+
249
266
  ```markdown
250
- ## Performance — base `<sha>` → `<sha>`
267
+ ## Performance — `FooRepository::findBar()`, base `<sha>` → `<sha>`
251
268
 
252
- | Query | Rows read | Time (min–max, 3 runs) | Verdict |
253
- |---|---|---|---|
254
- | `FooRepository::findBar()` | 41k → 750 | 120–131 ms → 8–9 ms | improved |
269
+ | | Plan (access · index · rows read) | Rows returned | Time (min–max, 3 runs) | Verdict |
270
+ |---|---|---|---|---|
271
+ | base | `ref` · `idx_a` · 41k | 15 | 120–131 ms | |
272
+ | now | `range` · `idx_b` · 750 | 15 | 8–9 ms | improved |
255
273
 
256
274
  | Entry point | Time | Peak memory | Verdict |
257
275
  |---|---|---|---|
@@ -261,6 +279,14 @@ Measured on <data set>, <n> runs each. A difference inside the run-to-run spread
261
279
  no measurable change, not a percentage.
262
280
  ```
263
281
 
282
+ - **The query, ready to run where the data is real.** Every performance comment ends with the exact
283
+ query (as the engine receives it, parameters filled with representative values) and the read-only
284
+ plan command for it — `EXPLAIN`, never `EXPLAIN ANALYZE` on a write — in a code block, so the
285
+ author can run it on production or a replica and answer in the thread. This is a complement to the
286
+ sandbox measurement, never a substitute: the flow does not touch production.
287
+ - **Memory is the entry point's, not the query's.** A modified query also gets the entry-point row
288
+ that exercises it, through `quality.bench_cmd` on both sides (`/flow:*:validate` §3.7). No
289
+ `bench_cmd` → the entry-point row says `not measured (no quality.bench_cmd)`; never an estimate.
264
290
  - **Verdicts**: `improved` · `no measurable change` · `regressed` · `not measured (<reason>)`.
265
291
  - **A `regressed` row is a review blocker** unless `03-design.md` justifies the trade — in which
266
292
  case the justification goes in the comment, in one line, next to the row.
@@ -46,7 +46,7 @@ This system **orchestrates** the project's existing sub-agents and skills (it do
46
46
  - **Understand before starting**: design-affecting doubts left after ticket + knowledge search + code → one `AskUserQuestion` with all of them before closing `/flow:feat:start` or `/flow:feat:design` §1.5.
47
47
  - **Reuse before creating**: in `/flow:feat:design`, check the affected and neighbouring modules before any new entity, column, repository, service or event; justify intentional duplication in `03-design.md`; never force a poor fit.
48
48
  - **Solve the real project problem, not the generic one (fit + YAGNI)**: before any defensive mechanism, **two questions with evidence** — **(a) Does it fit? Can this scenario actually happen given how this project works?** (knowledge search + code, **not** textbook patterns; already prevented → **unnecessary**); **(b) Do we need it now, for what the ticket asks?** (hypothetical → **don't add it**, "idea for a separate ticket"). Bias **remove, not add** — `/flow:feat:design` challenger ("Fit and need") and the defensive mechanisms table of `03-design.md`.
49
- - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow:work:query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow:*:validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers are posted as an MR/PR **comment** (`ship` §4.3), re-posted whenever a later push changes a measured path.
49
+ - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow:work:query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow:*:validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers — or, for a modified query nobody could measure, `not measured` and why — are posted as **inline comments on the changed query** (`ship` §4.3), re-posted whenever a later push changes a measured path.
50
50
  - **A query is judged on its plan, not on its prose**: cost lives in the **execution plan**, not the diff. `/flow:work:query`: facts, a challenger blinded to the design's rationale, the main agent judges — **no number, no win**, **no dogma in either direction**, **the objector's variant measured next to yours**. **Any size, XS included.** A performance objection is answered with a plan; an unmeasured plan is never reported as measured ("schema only" / "not measured" are legitimate verdicts; a fixture-sized test database is not evidence).
51
51
  - **Size is revisable**: a later phase (`design`, `plan`, `investigate`) seeing a clear mismatch **proposes reclassifying to the user** and updates `meta.json.size`.
52
52
  - **If the implementation invalidates the design, return to design**: in `/flow:feat:build`, 2+ significant deviations in `05-implementation.md`, or one changing an ADR-light decision → **pause and return to `/flow:feat:design`** (what `/flow:feat:review` and `/flow:feat:validate` read). Same for `/flow:bug:fix` vs `/flow:bug:investigate`.
@@ -41,19 +41,34 @@ Read `~/.claude/flow/CORE.gemini.md` first (shared rules: `FLOW.md` step 0, mode
41
41
  4. **Integrating the base branch** — any `git merge`/`git rebase` of `git.default_base` into the branch, and any history rewrite or force-push it implies (§5.C).
42
42
  5. **Green-washing — NEVER, in any mode:** no rerun of a failed check hoping it passes; never disable, skip, `@skip`/`xit`, loosen a threshold, or delete a test/lint rule to force green; never resolve a conflict by blindly taking one side (`--ours`/`--theirs`, `checkout --theirs`, discarding the base's changes) — resolve by understanding both sides.
43
43
 
44
- **In `unattended`** (flow-core §2.1) — launched by `/flow:work:run` on `why = pipeline` — this
45
- command asks nothing and never touches the forge. Gate 5 holds exactly as written; the others
46
- resolve this way:
44
+ **In `unattended`** (flow-core §2.1) — launched by `/flow:work:run` on `why = pipeline` or
45
+ `why = conflict` — this command never touches the forge. Gate 5 holds exactly as written; the
46
+ others resolve this way:
47
47
 
48
48
  - **§1–§2** — no MR/PR is resolved and no forge is read: the failures are `<cmd> events --json` →
49
49
  `{"sha", "failed": [{name, conclusion, url, log_tail}]}`, and `log_tail` is the evidence. The order
50
50
  failing → `blocked`; `failed` empty → `done`, the summary saying so. `sha` not `HEAD` → `blocked`:
51
51
  the result is about another commit, and fixing it here would be fixing a guess.
52
+ On `why = conflict` the events are `{"conflict": {"base", "base_sha"}}` instead: the MR/PR
53
+ conflicts with `base`, which the orchestrator left in the checkout as `origin/<base>` at
54
+ `base_sha`. That is the only blocker of the round — category C, nothing to triage. `origin/<base>`
55
+ missing or not at `base_sha` → `blocked`: the run would merge a base nobody named.
52
56
  - **§3** — triage as written, recorded in `09-ci.md`. **K** (flaky / infra) → `blocked` with the
53
57
  evidence: the run has no rerun order, and the orchestrator owns reruns and the red-round limit.
54
- **C** (conflict, behind base) and anything needing a rebase or force-push → `blocked`: the
55
- orchestrator brings the base in. A **human** blocker → `blocked`, naming who. **S** → fixed within
58
+ **C** (conflict, behind base) on `why = pipeline`, and anything needing a rebase or force-push →
59
+ `blocked`: the orchestrator brings the base in, and relaunches with `why = conflict` when it
60
+ conflicts. A **human** blocker → `blocked`, naming who. **S** → fixed within
56
61
  scope, otherwise `blocked` with the justification.
62
+ - **§5.C on `why = conflict`** — gate 4 is recorded (`unattended:integrate-base`), never asked:
63
+ the orchestrator launched the run for exactly this. No fetch: the base is `origin/<base>` as the
64
+ orchestrator left it, never `git.default_base` read from the config. Always `git merge
65
+ origin/<base>`, never a rebase. Each conflict is resolved on its merits as step 3 says; step 6's
66
+ "intent of either side unclear" → `git merge --abort`, then question `decision` when the two
67
+ sides want things that contradict each other (a product decision: which intent wins), `blocked`
68
+ otherwise. The base it merges goes into this round of `09-ci.md` before the merge starts; the
69
+ answer re-enters at §5.C, reads the base there, and merges again from `origin/<base>` — an
70
+ unfinished merge does not survive the run. Step 4's design change that is more than a tweak →
71
+ `blocked`. The merge commit, after the full local gate, is the round's commit.
57
72
  - **§5** — fixes and the local verification as written; a local gate it cannot get green →
58
73
  `blocked`. A fix adding behaviour gets its brief recorded, not asked; a fix that needs a schema
59
74
  change, a design invalidation that is more than a tweak, or a high-severity finding from the
@@ -145,6 +145,9 @@ Per query, exactly one of:
145
145
 
146
146
  - **ok** — access supported and bounded; state the index used and rows read. For a modified query,
147
147
  say how it compares to the base row: *improved*, *no measurable change*, or *regressed*.
148
+ **No plan was read → it is `ok (plan not measured)`**, with the reason, never a bare `ok`: a
149
+ modified query reaches the MR/PR through `/flow:feat:ship` §4.3 and the reviewer must be able to
150
+ tell it was never measured.
148
151
  - **regressed** — measurably worse than the base version, and the design does not justify it. A
149
152
  blocker, the same as `change`. Justified in `03-design.md` (a correctness fix that costs time, a
150
153
  deliberate trade) → not a blocker, but it is stated in the verdict and it reaches the MR/PR.
@@ -32,7 +32,7 @@ of truth, and an argument that disagrees with it is ignored and named in the log
32
32
  other mode a person is the router (`/flow:next`).
33
33
  - `autonomy.orchestrator_cmd` empty → flow-core §2.1: no phase runs.
34
34
  - Run `<cmd> why`. It fails, or prints anything but `start`, `study`, `answer`, `review`,
35
- `pipeline` or `merged` → print what it returned and its stderr in the §3 header and end. No closing order: an
35
+ `pipeline`, `merged` or `conflict` → print what it returned and its stderr in the §3 header and end. No closing order: an
36
36
  orchestrator that cannot say why it launched the run cannot take one either.
37
37
  - Run `<cmd> ticket --json` and keep `number`, `title`, `base` and `kind` (`kind` absent or `null` → `feat`: the tracker could not tell, so flow decides). `kind` is re-read on every run; only a run that starts the work uses it — once the work exists, its folder says feat or bug.
38
38
  It fails or prints no `number` → `blocked` (flow-core §2.1).
@@ -60,7 +60,8 @@ of truth, and an argument that disagrees with it is ignored and named in the log
60
60
  | `merged` | none, or nothing published | `blocked` — nothing of this ticket was published from here |
61
61
  | `review` | exists | `/flow:work:respond` — the threads come from `<cmd> events --json` |
62
62
  | `pipeline` | exists | `/flow:work:green` — the failed jobs come from `<cmd> events --json` |
63
- | `review` · `pipeline` | none | `blocked` — there is no work of this ticket to answer for |
63
+ | `conflict` | exists | `/flow:work:green` — the MR/PR conflicts with its base; which base, and where it stands, come from `<cmd> events --json` |
64
+ | `review` · `pipeline` · `conflict` | none | `blocked` — there is no work of this ticket to answer for |
64
65
 
65
66
  **The work's branch, on `start` and `study`.** The current branch is `meta.json.branch` → nothing to do. It is
66
67
  not — a study made on another machine arrives on the ticket's `base`, a run that died before its
@@ -17,7 +17,7 @@ Every `/flow-*` command assumes these rules. They are stated once, here, so a co
17
17
  carries what is specific to its phase. Read this once per session; a command that says "load
18
18
  `flow-core`" means this file.
19
19
 
20
- **This file belongs to flow `0.89.0`.** Compare it once, at the start of the session, against
20
+ **This file belongs to flow `0.91.0`.** Compare it once, at the start of the session, against
21
21
  `version` in `~/.claude/flow/.claude-plugin/plugin.json`. The two differing means the session
22
22
  is running a **mixture** — the commands from one copy of the plugin, these shared rules from another
23
23
  — which is exactly what happens when a branch or a release candidate is loaded over an installed
@@ -244,11 +244,12 @@ configuration; these orders are the whole interface, and nothing else about the
244
244
  assumed:
245
245
 
246
246
  ```
247
- <cmd> why → start | study | answer | review | pipeline | merged
247
+ <cmd> why → start | study | answer | review | pipeline | merged | conflict
248
248
  <cmd> ticket --json → {number, title, body, comments[{author, at, text}], base, kind}
249
249
  <cmd> answer → one option's exact text, or free text; exit 2 = no reply
250
250
  <cmd> events --json → {"comments":[{thread,kind,id,author,at,body,url,path?,line?}]}
251
251
  | {"sha":…, "failed":[{name,conclusion,url,log_tail}]}
252
+ | {"conflict":{"base":…, "base_sha":…}}
252
253
  <cmd> ask --question-file F --options-file F [--recommended N] [--free-text] [--gate NAME]
253
254
  <cmd> publish --title-file F --body-file F [--part N --of M]
254
255
  <cmd> blocked --reason-file F
@@ -308,7 +309,7 @@ run has.
308
309
  runs the study phases as any run does, and three rules make it stop where it should:
309
310
 
310
311
  - **The door to code.** The first step of `/flow-feat-build` and `/flow-bug-fix` pre-flight, in
311
- `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review` or `pipeline` → go on;
312
+ `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review`, `pipeline` or `conflict` → go on;
312
313
  `study` → the study ends here; anything else, or the order failing → `blocked`. Every path from a
313
314
  study to code crosses that door, an answered question that resumes straight into `build`
314
315
  included, so the phases before it carry no clause. The run kind is read there, never stored: the
@@ -351,6 +352,7 @@ person asking for changes, a ticket that already has code — is `/flow-work-run
351
352
  | The next MR/PR of a train (`ship` close) | record — not started: one MR/PR per unattended run |
352
353
  | `build` with no startable MR/PR (its `depends_on` not `merged` in `meta.json`) | blocked — the reason names what must merge; an unattended `build` never stacks a branch on another, and never creates a remote branch |
353
354
  | The parent of a stacked MR/PR merged — rebase and force-push | blocked — never force-push unattended |
355
+ | Integrating the base into the MR/PR's branch (`green`) | on `why = conflict`, record — `git merge origin/<base>` as the orchestrator left it, never rebase, each conflict resolved on its merits (`/flow-work-green` §5.C); on any other `why`, blocked — the orchestrator brings the base in |
354
356
  | Any offer about the work's own bookkeeping — a tracker issue, the first `KNOWLEDGE.md` or a knowledge save, a FLOW convention, an agent lesson or a saved specialist, the follow-up survey, carrying files over from the main checkout, a contract handoff, archiving the work | record — `Later`; nothing written outside the work folder |
355
357
  | A step the command cannot complete (a red suite it cannot fix, a tool missing, auth failing, the same check failing a second time) | blocked |
356
358
  | Anything `auto` already decides on its own (size, flow mechanics, WIP commits, the next command) | as `auto` |
@@ -185,9 +185,9 @@ With an "all threads resolved before merge" policy, the MR/PR cannot be merged o
185
185
 
186
186
  ### 3.3 Performance comment
187
187
 
188
- `/flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` and benchmark rows from
189
- `05-validation.md`, posted as a comment on the MR/PR, confirmed in every mode, nothing posted when
190
- nothing was measured. A slowness bug is the case where this comment *is* the proof the fix worked.
188
+ `/flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` — a modified query that was not
189
+ measured included, as `not measured (<reason>)` — and benchmark rows from `05-validation.md`,
190
+ posted as comments anchored on the changed lines of the MR/PR, confirmed in every mode. A slowness bug is the case where this comment *is* the proof the fix worked.
191
191
 
192
192
  ## 4. Close
193
193
 
@@ -212,6 +212,8 @@ Judging rules: **no number, no win** (unresolved is recorded unresolved, never s
212
212
 
213
213
  **A modified query is judged against its own past, not only against its rivals.** For every query the diff *changes* (as opposed to adds), the duel carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, measured in the same sandbox with the same seed (`/flow-work-query` §4). Without it the table cannot say whether this diff made things better or worse, which is the one thing a reviewer wants from it.
214
214
 
215
+ **A modified query with no plan behind its verdict is recorded `ok (plan not measured)` — and says what would measure it (`data.explain_cmd`, a data set) — never a bare `ok`.** Ship publishes that row on the MR/PR (`/flow-feat-ship` §4.3); a reviewer who finds a plain "ok" and no number has no way to know nobody looked.
216
+
215
217
  Verdicts as findings: **change** = blocker, through §6; **regressed** = blocker unless `03-design.md` justifies the trade, in which case it is stated and published; **schema / follow-up** → proposed ticket in the output **and a `followups[]` entry** (flow-core §7); **unresolved** → recorded literally with the open question (as §3.5). A `change` verdict resting on a measured plan is already confirmed — no §6 skeptic.
216
218
 
217
219
  ## 4. Over-engineering audit (fit + YAGNI)
@@ -240,22 +240,40 @@ Thread body: the SQL block under "Pre-deploy: run this SQL on the server BEFORE
240
240
 
241
241
  With the repo policy "all threads resolved before merge", the MR/PR cannot merge until the SQL is executed and the thread **resolved**. **One single thread even with multiple statements.** Tell the user it is left open on purpose.
242
242
 
243
- ### 4.3 Performance comment (only when there are numbers)
244
-
245
- `06-review.md` §"Data-access duel" has rows for modified queries, or `07-validation.md` §3.7 has
246
- benchmark rows → post them as a **comment** on the MR/PR just created (`gh pr comment` /
247
- `glab mr note`). Nothing measured → post nothing; an empty performance comment is noise.
248
-
249
- A comment, not a description section, because it is **re-posted on every push that changes a
250
- measured path** (`/flow-work-green`, `/flow-work-respond`), and a description rewritten each round
243
+ ### 4.3 Performance comment (every modified query, measured or not)
244
+
245
+ Post one when `06-review.md` §"Data-access duel" has a row for a **modified** query — **whatever its
246
+ verdict, measured or not** — or `07-validation.md` §3.7 has benchmark rows. New queries only, or
247
+ renames and reformats only, and nothing benchmarked → post nothing.
248
+
249
+ **A modified query that was never measured is the case this comment exists for.** Silence reads as
250
+ "nothing to see"; a reviewer cannot tell a query that was measured and is fine from one that nobody
251
+ looked at. The row says `not measured (<reason>)` and what would settle it (`data.explain_cmd`, a
252
+ data set, the entry point to run), so the reviewer knows what was *not* checked.
253
+
254
+ **Where it goes: on the query, not under the title.** One **inline thread anchored to the changed
255
+ lines of that query in the MR/PR diff** (a resolvable GitLab discussion with a `position`; a GitHub
256
+ review comment on `path` + `line`), one per modified query. The entry-point rows anchor to the entry
257
+ point's changed line the same way. Anchoring fails (the line is not in the diff, the forge refuses
258
+ the position) → a general MR/PR comment that names the file and line, and say so. A general comment
259
+ is the fallback, never the default: a number a reviewer meets on the line they are reading is read;
260
+ one at the bottom of the page is not.
261
+
262
+ Still a comment and not a description section, because it is **re-posted on every push that changes
263
+ a measured path** (`/flow-work-green`, `/flow-work-respond`), and a description rewritten each round
251
264
  loses what the numbers were two rounds ago. The history is the point.
252
265
 
266
+ For each modified query the comment carries the **plan before and after** — the `data.explain_cmd`
267
+ output on the merge base and on the branch, same data, trimmed to access type, index, rows read — next
268
+ to the numbers:
269
+
253
270
  ```markdown
254
- ## Performance — base `<sha>` → `<sha>`
271
+ ## Performance — `FooRepository::findBar()`, base `<sha>` → `<sha>`
255
272
 
256
- | Query | Rows read | Time (min–max, 3 runs) | Verdict |
257
- |---|---|---|---|
258
- | `FooRepository::findBar()` | 41k → 750 | 120–131 ms → 8–9 ms | improved |
273
+ | | Plan (access · index · rows read) | Rows returned | Time (min–max, 3 runs) | Verdict |
274
+ |---|---|---|---|---|
275
+ | base | `ref` · `idx_a` · 41k | 15 | 120–131 ms | |
276
+ | now | `range` · `idx_b` · 750 | 15 | 8–9 ms | improved |
259
277
 
260
278
  | Entry point | Time | Peak memory | Verdict |
261
279
  |---|---|---|---|
@@ -265,6 +283,14 @@ Measured on <data set>, <n> runs each. A difference inside the run-to-run spread
265
283
  no measurable change, not a percentage.
266
284
  ```
267
285
 
286
+ - **The query, ready to run where the data is real.** Every performance comment ends with the exact
287
+ query (as the engine receives it, parameters filled with representative values) and the read-only
288
+ plan command for it — `EXPLAIN`, never `EXPLAIN ANALYZE` on a write — in a code block, so the
289
+ author can run it on production or a replica and answer in the thread. This is a complement to the
290
+ sandbox measurement, never a substitute: the flow does not touch production.
291
+ - **Memory is the entry point's, not the query's.** A modified query also gets the entry-point row
292
+ that exercises it, through `quality.bench_cmd` on both sides (`/flow-*-validate` §3.7). No
293
+ `bench_cmd` → the entry-point row says `not measured (no quality.bench_cmd)`; never an estimate.
268
294
  - **Verdicts**: `improved` · `no measurable change` · `regressed` · `not measured (<reason>)`.
269
295
  - **A `regressed` row is a review blocker** unless `03-design.md` justifies the trade — in which
270
296
  case the justification goes in the comment, in one line, next to the row.
@@ -50,7 +50,7 @@ This system **orchestrates** the project's existing sub-agents and skills (it do
50
50
  - **Understand before starting**: design-affecting doubts left after ticket + knowledge search + code → one `AskUserQuestion` with all of them before closing `/flow-feat-start` or `/flow-feat-design` §1.5.
51
51
  - **Reuse before creating**: in `/flow-feat-design`, check the affected and neighbouring modules before any new entity, column, repository, service or event; justify intentional duplication in `03-design.md`; never force a poor fit.
52
52
  - **Solve the real project problem, not the generic one (fit + YAGNI)**: before any defensive mechanism, **two questions with evidence** — **(a) Does it fit? Can this scenario actually happen given how this project works?** (knowledge search + code, **not** textbook patterns; already prevented → **unnecessary**); **(b) Do we need it now, for what the ticket asks?** (hypothetical → **don't add it**, "idea for a separate ticket"). Bias **remove, not add** — `/flow-feat-design` challenger ("Fit and need") and the defensive mechanisms table of `03-design.md`.
53
- - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers are posted as an MR/PR **comment** (`ship` §4.3), re-posted whenever a later push changes a measured path.
53
+ - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers — or, for a modified query nobody could measure, `not measured` and why — are posted as **inline comments on the changed query** (`ship` §4.3), re-posted whenever a later push changes a measured path.
54
54
  - **A query is judged on its plan, not on its prose**: cost lives in the **execution plan**, not the diff. `/flow-work-query`: facts, a challenger blinded to the design's rationale, the main agent judges — **no number, no win**, **no dogma in either direction**, **the objector's variant measured next to yours**. **Any size, XS included.** A performance objection is answered with a plan; an unmeasured plan is never reported as measured ("schema only" / "not measured" are legitimate verdicts; a fixture-sized test database is not evidence).
55
55
  - **Size is revisable**: a later phase (`design`, `plan`, `investigate`) seeing a clear mismatch **proposes reclassifying to the user** and updates `meta.json.size`.
56
56
  - **If the implementation invalidates the design, return to design**: in `/flow-feat-build`, 2+ significant deviations in `05-implementation.md`, or one changing an ADR-light decision → **pause and return to `/flow-feat-design`** (what `/flow-feat-review` and `/flow-feat-validate` read). Same for `/flow-bug-fix` vs `/flow-bug-investigate`.
@@ -45,19 +45,34 @@ Read `~/.claude/flow/CORE.hermes.md` first (shared rules: `FLOW.md` step 0, mode
45
45
  4. **Integrating the base branch** — any `git merge`/`git rebase` of `git.default_base` into the branch, and any history rewrite or force-push it implies (§5.C).
46
46
  5. **Green-washing — NEVER, in any mode:** no rerun of a failed check hoping it passes; never disable, skip, `@skip`/`xit`, loosen a threshold, or delete a test/lint rule to force green; never resolve a conflict by blindly taking one side (`--ours`/`--theirs`, `checkout --theirs`, discarding the base's changes) — resolve by understanding both sides.
47
47
 
48
- **In `unattended`** (flow-core §2.1) — launched by `/flow-work-run` on `why = pipeline` — this
49
- command asks nothing and never touches the forge. Gate 5 holds exactly as written; the others
50
- resolve this way:
48
+ **In `unattended`** (flow-core §2.1) — launched by `/flow-work-run` on `why = pipeline` or
49
+ `why = conflict` — this command never touches the forge. Gate 5 holds exactly as written; the
50
+ others resolve this way:
51
51
 
52
52
  - **§1–§2** — no MR/PR is resolved and no forge is read: the failures are `<cmd> events --json` →
53
53
  `{"sha", "failed": [{name, conclusion, url, log_tail}]}`, and `log_tail` is the evidence. The order
54
54
  failing → `blocked`; `failed` empty → `done`, the summary saying so. `sha` not `HEAD` → `blocked`:
55
55
  the result is about another commit, and fixing it here would be fixing a guess.
56
+ On `why = conflict` the events are `{"conflict": {"base", "base_sha"}}` instead: the MR/PR
57
+ conflicts with `base`, which the orchestrator left in the checkout as `origin/<base>` at
58
+ `base_sha`. That is the only blocker of the round — category C, nothing to triage. `origin/<base>`
59
+ missing or not at `base_sha` → `blocked`: the run would merge a base nobody named.
56
60
  - **§3** — triage as written, recorded in `09-ci.md`. **K** (flaky / infra) → `blocked` with the
57
61
  evidence: the run has no rerun order, and the orchestrator owns reruns and the red-round limit.
58
- **C** (conflict, behind base) and anything needing a rebase or force-push → `blocked`: the
59
- orchestrator brings the base in. A **human** blocker → `blocked`, naming who. **S** → fixed within
62
+ **C** (conflict, behind base) on `why = pipeline`, and anything needing a rebase or force-push →
63
+ `blocked`: the orchestrator brings the base in, and relaunches with `why = conflict` when it
64
+ conflicts. A **human** blocker → `blocked`, naming who. **S** → fixed within
60
65
  scope, otherwise `blocked` with the justification.
66
+ - **§5.C on `why = conflict`** — gate 4 is recorded (`unattended:integrate-base`), never asked:
67
+ the orchestrator launched the run for exactly this. No fetch: the base is `origin/<base>` as the
68
+ orchestrator left it, never `git.default_base` read from the config. Always `git merge
69
+ origin/<base>`, never a rebase. Each conflict is resolved on its merits as step 3 says; step 6's
70
+ "intent of either side unclear" → `git merge --abort`, then question `decision` when the two
71
+ sides want things that contradict each other (a product decision: which intent wins), `blocked`
72
+ otherwise. The base it merges goes into this round of `09-ci.md` before the merge starts; the
73
+ answer re-enters at §5.C, reads the base there, and merges again from `origin/<base>` — an
74
+ unfinished merge does not survive the run. Step 4's design change that is more than a tweak →
75
+ `blocked`. The merge commit, after the full local gate, is the round's commit.
61
76
  - **§5** — fixes and the local verification as written; a local gate it cannot get green →
62
77
  `blocked`. A fix adding behaviour gets its brief recorded, not asked; a fix that needs a schema
63
78
  change, a design invalidation that is more than a tweak, or a high-severity finding from the
@@ -149,6 +149,9 @@ Per query, exactly one of:
149
149
 
150
150
  - **ok** — access supported and bounded; state the index used and rows read. For a modified query,
151
151
  say how it compares to the base row: *improved*, *no measurable change*, or *regressed*.
152
+ **No plan was read → it is `ok (plan not measured)`**, with the reason, never a bare `ok`: a
153
+ modified query reaches the MR/PR through `/flow-feat-ship` §4.3 and the reviewer must be able to
154
+ tell it was never measured.
152
155
  - **regressed** — measurably worse than the base version, and the design does not justify it. A
153
156
  blocker, the same as `change`. Justified in `03-design.md` (a correctness fix that costs time, a
154
157
  deliberate trade) → not a blocker, but it is stated in the verdict and it reaches the MR/PR.
@@ -36,7 +36,7 @@ of truth, and an argument that disagrees with it is ignored and named in the log
36
36
  other mode a person is the router (`/flow-next`).
37
37
  - `autonomy.orchestrator_cmd` empty → flow-core §2.1: no phase runs.
38
38
  - Run `<cmd> why`. It fails, or prints anything but `start`, `study`, `answer`, `review`,
39
- `pipeline` or `merged` → print what it returned and its stderr in the §3 header and end. No closing order: an
39
+ `pipeline`, `merged` or `conflict` → print what it returned and its stderr in the §3 header and end. No closing order: an
40
40
  orchestrator that cannot say why it launched the run cannot take one either.
41
41
  - Run `<cmd> ticket --json` and keep `number`, `title`, `base` and `kind` (`kind` absent or `null` → `feat`: the tracker could not tell, so flow decides). `kind` is re-read on every run; only a run that starts the work uses it — once the work exists, its folder says feat or bug.
42
42
  It fails or prints no `number` → `blocked` (flow-core §2.1).
@@ -64,7 +64,8 @@ of truth, and an argument that disagrees with it is ignored and named in the log
64
64
  | `merged` | none, or nothing published | `blocked` — nothing of this ticket was published from here |
65
65
  | `review` | exists | `/flow-work-respond` — the threads come from `<cmd> events --json` |
66
66
  | `pipeline` | exists | `/flow-work-green` — the failed jobs come from `<cmd> events --json` |
67
- | `review` · `pipeline` | none | `blocked` — there is no work of this ticket to answer for |
67
+ | `conflict` | exists | `/flow-work-green` — the MR/PR conflicts with its base; which base, and where it stands, come from `<cmd> events --json` |
68
+ | `review` · `pipeline` · `conflict` | none | `blocked` — there is no work of this ticket to answer for |
68
69
 
69
70
  **The work's branch, on `start` and `study`.** The current branch is `meta.json.branch` → nothing to do. It is
70
71
  not — a study made on another machine arrives on the ticket's `base`, a run that died before its
@@ -16,7 +16,7 @@ Every `/flow-*` command assumes these rules. They are stated once, here, so a co
16
16
  carries what is specific to its phase. Read this once per session; a command that says "load
17
17
  `flow-core`" means this file.
18
18
 
19
- **This file belongs to flow `0.89.0`.** Compare it once, at the start of the session, against
19
+ **This file belongs to flow `0.91.0`.** Compare it once, at the start of the session, against
20
20
  `version` in `~/.claude/flow/.claude-plugin/plugin.json`. The two differing means the session
21
21
  is running a **mixture** — the commands from one copy of the plugin, these shared rules from another
22
22
  — which is exactly what happens when a branch or a release candidate is loaded over an installed
@@ -243,11 +243,12 @@ configuration; these orders are the whole interface, and nothing else about the
243
243
  assumed:
244
244
 
245
245
  ```
246
- <cmd> why → start | study | answer | review | pipeline | merged
246
+ <cmd> why → start | study | answer | review | pipeline | merged | conflict
247
247
  <cmd> ticket --json → {number, title, body, comments[{author, at, text}], base, kind}
248
248
  <cmd> answer → one option's exact text, or free text; exit 2 = no reply
249
249
  <cmd> events --json → {"comments":[{thread,kind,id,author,at,body,url,path?,line?}]}
250
250
  | {"sha":…, "failed":[{name,conclusion,url,log_tail}]}
251
+ | {"conflict":{"base":…, "base_sha":…}}
251
252
  <cmd> ask --question-file F --options-file F [--recommended N] [--free-text] [--gate NAME]
252
253
  <cmd> publish --title-file F --body-file F [--part N --of M]
253
254
  <cmd> blocked --reason-file F
@@ -307,7 +308,7 @@ run has.
307
308
  runs the study phases as any run does, and three rules make it stop where it should:
308
309
 
309
310
  - **The door to code.** The first step of `/flow-feat-build` and `/flow-bug-fix` pre-flight, in
310
- `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review` or `pipeline` → go on;
311
+ `unattended`, re-reads `<cmd> why`: `start`, `answer`, `merged`, `review`, `pipeline` or `conflict` → go on;
311
312
  `study` → the study ends here; anything else, or the order failing → `blocked`. Every path from a
312
313
  study to code crosses that door, an answered question that resumes straight into `build`
313
314
  included, so the phases before it carry no clause. The run kind is read there, never stored: the
@@ -350,6 +351,7 @@ person asking for changes, a ticket that already has code — is `/flow-work-run
350
351
  | The next MR/PR of a train (`ship` close) | record — not started: one MR/PR per unattended run |
351
352
  | `build` with no startable MR/PR (its `depends_on` not `merged` in `meta.json`) | blocked — the reason names what must merge; an unattended `build` never stacks a branch on another, and never creates a remote branch |
352
353
  | The parent of a stacked MR/PR merged — rebase and force-push | blocked — never force-push unattended |
354
+ | Integrating the base into the MR/PR's branch (`green`) | on `why = conflict`, record — `git merge origin/<base>` as the orchestrator left it, never rebase, each conflict resolved on its merits (`/flow-work-green` §5.C); on any other `why`, blocked — the orchestrator brings the base in |
353
355
  | Any offer about the work's own bookkeeping — a tracker issue, the first `KNOWLEDGE.md` or a knowledge save, a FLOW convention, an agent lesson or a saved specialist, the follow-up survey, carrying files over from the main checkout, a contract handoff, archiving the work | record — `Later`; nothing written outside the work folder |
354
356
  | A step the command cannot complete (a red suite it cannot fix, a tool missing, auth failing, the same check failing a second time) | blocked |
355
357
  | Anything `auto` already decides on its own (size, flow mechanics, WIP commits, the next command) | as `auto` |
@@ -183,9 +183,9 @@ With an "all threads resolved before merge" policy, the MR/PR cannot be merged o
183
183
 
184
184
  ### 3.3 Performance comment
185
185
 
186
- `/flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` and benchmark rows from
187
- `05-validation.md`, posted as a comment on the MR/PR, confirmed in every mode, nothing posted when
188
- nothing was measured. A slowness bug is the case where this comment *is* the proof the fix worked.
186
+ `/flow-feat-ship` §4.3, unchanged: query rows from `06-review.md` — a modified query that was not
187
+ measured included, as `not measured (<reason>)` — and benchmark rows from `05-validation.md`,
188
+ posted as comments anchored on the changed lines of the MR/PR, confirmed in every mode. A slowness bug is the case where this comment *is* the proof the fix worked.
189
189
 
190
190
  ## 4. Close
191
191
 
@@ -210,6 +210,8 @@ Judging rules: **no number, no win** (unresolved is recorded unresolved, never s
210
210
 
211
211
  **A modified query is judged against its own past, not only against its rivals.** For every query the diff *changes* (as opposed to adds), the duel carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, measured in the same sandbox with the same seed (`/flow-work-query` §4). Without it the table cannot say whether this diff made things better or worse, which is the one thing a reviewer wants from it.
212
212
 
213
+ **A modified query with no plan behind its verdict is recorded `ok (plan not measured)` — and says what would measure it (`data.explain_cmd`, a data set) — never a bare `ok`.** Ship publishes that row on the MR/PR (`/flow-feat-ship` §4.3); a reviewer who finds a plain "ok" and no number has no way to know nobody looked.
214
+
213
215
  Verdicts as findings: **change** = blocker, through §6; **regressed** = blocker unless `03-design.md` justifies the trade, in which case it is stated and published; **schema / follow-up** → proposed ticket in the output **and a `followups[]` entry** (flow-core §7); **unresolved** → recorded literally with the open question (as §3.5). A `change` verdict resting on a measured plan is already confirmed — no §6 skeptic.
214
216
 
215
217
  ## 4. Over-engineering audit (fit + YAGNI)
@@ -238,22 +238,40 @@ Thread body: the SQL block under "Pre-deploy: run this SQL on the server BEFORE
238
238
 
239
239
  With the repo policy "all threads resolved before merge", the MR/PR cannot merge until the SQL is executed and the thread **resolved**. **One single thread even with multiple statements.** Tell the user it is left open on purpose.
240
240
 
241
- ### 4.3 Performance comment (only when there are numbers)
242
-
243
- `06-review.md` §"Data-access duel" has rows for modified queries, or `07-validation.md` §3.7 has
244
- benchmark rows → post them as a **comment** on the MR/PR just created (`gh pr comment` /
245
- `glab mr note`). Nothing measured → post nothing; an empty performance comment is noise.
246
-
247
- A comment, not a description section, because it is **re-posted on every push that changes a
248
- measured path** (`/flow-work-green`, `/flow-work-respond`), and a description rewritten each round
241
+ ### 4.3 Performance comment (every modified query, measured or not)
242
+
243
+ Post one when `06-review.md` §"Data-access duel" has a row for a **modified** query — **whatever its
244
+ verdict, measured or not** — or `07-validation.md` §3.7 has benchmark rows. New queries only, or
245
+ renames and reformats only, and nothing benchmarked → post nothing.
246
+
247
+ **A modified query that was never measured is the case this comment exists for.** Silence reads as
248
+ "nothing to see"; a reviewer cannot tell a query that was measured and is fine from one that nobody
249
+ looked at. The row says `not measured (<reason>)` and what would settle it (`data.explain_cmd`, a
250
+ data set, the entry point to run), so the reviewer knows what was *not* checked.
251
+
252
+ **Where it goes: on the query, not under the title.** One **inline thread anchored to the changed
253
+ lines of that query in the MR/PR diff** (a resolvable GitLab discussion with a `position`; a GitHub
254
+ review comment on `path` + `line`), one per modified query. The entry-point rows anchor to the entry
255
+ point's changed line the same way. Anchoring fails (the line is not in the diff, the forge refuses
256
+ the position) → a general MR/PR comment that names the file and line, and say so. A general comment
257
+ is the fallback, never the default: a number a reviewer meets on the line they are reading is read;
258
+ one at the bottom of the page is not.
259
+
260
+ Still a comment and not a description section, because it is **re-posted on every push that changes
261
+ a measured path** (`/flow-work-green`, `/flow-work-respond`), and a description rewritten each round
249
262
  loses what the numbers were two rounds ago. The history is the point.
250
263
 
264
+ For each modified query the comment carries the **plan before and after** — the `data.explain_cmd`
265
+ output on the merge base and on the branch, same data, trimmed to access type, index, rows read — next
266
+ to the numbers:
267
+
251
268
  ```markdown
252
- ## Performance — base `<sha>` → `<sha>`
269
+ ## Performance — `FooRepository::findBar()`, base `<sha>` → `<sha>`
253
270
 
254
- | Query | Rows read | Time (min–max, 3 runs) | Verdict |
255
- |---|---|---|---|
256
- | `FooRepository::findBar()` | 41k → 750 | 120–131 ms → 8–9 ms | improved |
271
+ | | Plan (access · index · rows read) | Rows returned | Time (min–max, 3 runs) | Verdict |
272
+ |---|---|---|---|---|
273
+ | base | `ref` · `idx_a` · 41k | 15 | 120–131 ms | |
274
+ | now | `range` · `idx_b` · 750 | 15 | 8–9 ms | improved |
257
275
 
258
276
  | Entry point | Time | Peak memory | Verdict |
259
277
  |---|---|---|---|
@@ -263,6 +281,14 @@ Measured on <data set>, <n> runs each. A difference inside the run-to-run spread
263
281
  no measurable change, not a percentage.
264
282
  ```
265
283
 
284
+ - **The query, ready to run where the data is real.** Every performance comment ends with the exact
285
+ query (as the engine receives it, parameters filled with representative values) and the read-only
286
+ plan command for it — `EXPLAIN`, never `EXPLAIN ANALYZE` on a write — in a code block, so the
287
+ author can run it on production or a replica and answer in the thread. This is a complement to the
288
+ sandbox measurement, never a substitute: the flow does not touch production.
289
+ - **Memory is the entry point's, not the query's.** A modified query also gets the entry-point row
290
+ that exercises it, through `quality.bench_cmd` on both sides (`/flow-*-validate` §3.7). No
291
+ `bench_cmd` → the entry-point row says `not measured (no quality.bench_cmd)`; never an estimate.
266
292
  - **Verdicts**: `improved` · `no measurable change` · `regressed` · `not measured (<reason>)`.
267
293
  - **A `regressed` row is a review blocker** unless `03-design.md` justifies the trade — in which
268
294
  case the justification goes in the comment, in one line, next to the row.
@@ -48,7 +48,7 @@ This system **orchestrates** the project's existing sub-agents and skills (it do
48
48
  - **Understand before starting**: design-affecting doubts left after ticket + knowledge search + code → one `AskUserQuestion` with all of them before closing `/flow-feat-start` or `/flow-feat-design` §1.5.
49
49
  - **Reuse before creating**: in `/flow-feat-design`, check the affected and neighbouring modules before any new entity, column, repository, service or event; justify intentional duplication in `03-design.md`; never force a poor fit.
50
50
  - **Solve the real project problem, not the generic one (fit + YAGNI)**: before any defensive mechanism, **two questions with evidence** — **(a) Does it fit? Can this scenario actually happen given how this project works?** (knowledge search + code, **not** textbook patterns; already prevented → **unnecessary**); **(b) Do we need it now, for what the ticket asks?** (hypothetical → **don't add it**, "idea for a separate ticket"). Bias **remove, not add** — `/flow-feat-design` challenger ("Fit and need") and the defensive mechanisms table of `03-design.md`.
51
- - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers are posted as an MR/PR **comment** (`ship` §4.3), re-posted whenever a later push changes a measured path.
51
+ - **A change is measured against what it replaces, not only against its rivals**: a **modified** query carries the **base row** — the same query on `git merge-base HEAD <git.default_base>`, same sandbox, same seed, same three runs (`/flow-work-query` §4) — and `quality.bench_cmd` benchmarks a touched route, command, consumer or job on both sides (`/flow-*-validate` §3.7). Times are reported `min–max`, and **a difference inside the spread is "no measurable change", never a percentage**. `regressed` blocks like `change` unless the design justifies the trade. The numbers — or, for a modified query nobody could measure, `not measured` and why — are posted as **inline comments on the changed query** (`ship` §4.3), re-posted whenever a later push changes a measured path.
52
52
  - **A query is judged on its plan, not on its prose**: cost lives in the **execution plan**, not the diff. `/flow-work-query`: facts, a challenger blinded to the design's rationale, the main agent judges — **no number, no win**, **no dogma in either direction**, **the objector's variant measured next to yours**. **Any size, XS included.** A performance objection is answered with a plan; an unmeasured plan is never reported as measured ("schema only" / "not measured" are legitimate verdicts; a fixture-sized test database is not evidence).
53
53
  - **Size is revisable**: a later phase (`design`, `plan`, `investigate`) seeing a clear mismatch **proposes reclassifying to the user** and updates `meta.json.size`.
54
54
  - **If the implementation invalidates the design, return to design**: in `/flow-feat-build`, 2+ significant deviations in `05-implementation.md`, or one changing an ADR-light decision → **pause and return to `/flow-feat-design`** (what `/flow-feat-review` and `/flow-feat-validate` read). Same for `/flow-bug-fix` vs `/flow-bug-investigate`.
@@ -43,19 +43,34 @@ Read `~/.claude/flow/CORE.opencode.md` first (shared rules: `FLOW.md` step 0, mo
43
43
  4. **Integrating the base branch** — any `git merge`/`git rebase` of `git.default_base` into the branch, and any history rewrite or force-push it implies (§5.C).
44
44
  5. **Green-washing — NEVER, in any mode:** no rerun of a failed check hoping it passes; never disable, skip, `@skip`/`xit`, loosen a threshold, or delete a test/lint rule to force green; never resolve a conflict by blindly taking one side (`--ours`/`--theirs`, `checkout --theirs`, discarding the base's changes) — resolve by understanding both sides.
45
45
 
46
- **In `unattended`** (flow-core §2.1) — launched by `/flow-work-run` on `why = pipeline` — this
47
- command asks nothing and never touches the forge. Gate 5 holds exactly as written; the others
48
- resolve this way:
46
+ **In `unattended`** (flow-core §2.1) — launched by `/flow-work-run` on `why = pipeline` or
47
+ `why = conflict` — this command never touches the forge. Gate 5 holds exactly as written; the
48
+ others resolve this way:
49
49
 
50
50
  - **§1–§2** — no MR/PR is resolved and no forge is read: the failures are `<cmd> events --json` →
51
51
  `{"sha", "failed": [{name, conclusion, url, log_tail}]}`, and `log_tail` is the evidence. The order
52
52
  failing → `blocked`; `failed` empty → `done`, the summary saying so. `sha` not `HEAD` → `blocked`:
53
53
  the result is about another commit, and fixing it here would be fixing a guess.
54
+ On `why = conflict` the events are `{"conflict": {"base", "base_sha"}}` instead: the MR/PR
55
+ conflicts with `base`, which the orchestrator left in the checkout as `origin/<base>` at
56
+ `base_sha`. That is the only blocker of the round — category C, nothing to triage. `origin/<base>`
57
+ missing or not at `base_sha` → `blocked`: the run would merge a base nobody named.
54
58
  - **§3** — triage as written, recorded in `09-ci.md`. **K** (flaky / infra) → `blocked` with the
55
59
  evidence: the run has no rerun order, and the orchestrator owns reruns and the red-round limit.
56
- **C** (conflict, behind base) and anything needing a rebase or force-push → `blocked`: the
57
- orchestrator brings the base in. A **human** blocker → `blocked`, naming who. **S** → fixed within
60
+ **C** (conflict, behind base) on `why = pipeline`, and anything needing a rebase or force-push →
61
+ `blocked`: the orchestrator brings the base in, and relaunches with `why = conflict` when it
62
+ conflicts. A **human** blocker → `blocked`, naming who. **S** → fixed within
58
63
  scope, otherwise `blocked` with the justification.
64
+ - **§5.C on `why = conflict`** — gate 4 is recorded (`unattended:integrate-base`), never asked:
65
+ the orchestrator launched the run for exactly this. No fetch: the base is `origin/<base>` as the
66
+ orchestrator left it, never `git.default_base` read from the config. Always `git merge
67
+ origin/<base>`, never a rebase. Each conflict is resolved on its merits as step 3 says; step 6's
68
+ "intent of either side unclear" → `git merge --abort`, then question `decision` when the two
69
+ sides want things that contradict each other (a product decision: which intent wins), `blocked`
70
+ otherwise. The base it merges goes into this round of `09-ci.md` before the merge starts; the
71
+ answer re-enters at §5.C, reads the base there, and merges again from `origin/<base>` — an
72
+ unfinished merge does not survive the run. Step 4's design change that is more than a tweak →
73
+ `blocked`. The merge commit, after the full local gate, is the round's commit.
59
74
  - **§5** — fixes and the local verification as written; a local gate it cannot get green →
60
75
  `blocked`. A fix adding behaviour gets its brief recorded, not asked; a fix that needs a schema
61
76
  change, a design invalidation that is more than a tweak, or a high-severity finding from the
@@ -147,6 +147,9 @@ Per query, exactly one of:
147
147
 
148
148
  - **ok** — access supported and bounded; state the index used and rows read. For a modified query,
149
149
  say how it compares to the base row: *improved*, *no measurable change*, or *regressed*.
150
+ **No plan was read → it is `ok (plan not measured)`**, with the reason, never a bare `ok`: a
151
+ modified query reaches the MR/PR through `/flow-feat-ship` §4.3 and the reviewer must be able to
152
+ tell it was never measured.
150
153
  - **regressed** — measurably worse than the base version, and the design does not justify it. A
151
154
  blocker, the same as `change`. Justified in `03-design.md` (a correctness fix that costs time, a
152
155
  deliberate trade) → not a blocker, but it is stated in the verdict and it reaches the MR/PR.
@@ -34,7 +34,7 @@ of truth, and an argument that disagrees with it is ignored and named in the log
34
34
  other mode a person is the router (`/flow-next`).
35
35
  - `autonomy.orchestrator_cmd` empty → flow-core §2.1: no phase runs.
36
36
  - Run `<cmd> why`. It fails, or prints anything but `start`, `study`, `answer`, `review`,
37
- `pipeline` or `merged` → print what it returned and its stderr in the §3 header and end. No closing order: an
37
+ `pipeline`, `merged` or `conflict` → print what it returned and its stderr in the §3 header and end. No closing order: an
38
38
  orchestrator that cannot say why it launched the run cannot take one either.
39
39
  - Run `<cmd> ticket --json` and keep `number`, `title`, `base` and `kind` (`kind` absent or `null` → `feat`: the tracker could not tell, so flow decides). `kind` is re-read on every run; only a run that starts the work uses it — once the work exists, its folder says feat or bug.
40
40
  It fails or prints no `number` → `blocked` (flow-core §2.1).
@@ -62,7 +62,8 @@ of truth, and an argument that disagrees with it is ignored and named in the log
62
62
  | `merged` | none, or nothing published | `blocked` — nothing of this ticket was published from here |
63
63
  | `review` | exists | `/flow-work-respond` — the threads come from `<cmd> events --json` |
64
64
  | `pipeline` | exists | `/flow-work-green` — the failed jobs come from `<cmd> events --json` |
65
- | `review` · `pipeline` | none | `blocked` — there is no work of this ticket to answer for |
65
+ | `conflict` | exists | `/flow-work-green` — the MR/PR conflicts with its base; which base, and where it stands, come from `<cmd> events --json` |
66
+ | `review` · `pipeline` · `conflict` | none | `blocked` — there is no work of this ticket to answer for |
66
67
 
67
68
  **The work's branch, on `start` and `study`.** The current branch is `meta.json.branch` → nothing to do. It is
68
69
  not — a study made on another machine arrives on the ticket's `base`, a run that died before its
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flow-workflows",
3
- "version": "0.89.0",
3
+ "version": "0.91.0",
4
4
  "description": "Guided feat/bug workflows (start→ship), post-deploy monitoring and multi-agent code review, stack-agnostic. Each repo uses a shared FLOW.md plus optional harness overlay.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,7 @@
3
3
  "name": "flow",
4
4
  "displayName": "Flow — guided dev workflows",
5
5
  "description": "Guided feat/bug workflows (start→ship), post-deploy monitoring and multi-agent code review, stack-agnostic. Each repo uses a shared FLOW.md plus optional harness overlay.",
6
- "version": "0.89.0",
6
+ "version": "0.91.0",
7
7
  "author": {
8
8
  "name": "Alberto Vioque",
9
9
  "email": "mashware@gmail.com"
@@ -5,6 +5,43 @@ plugin and is what `/flow:news` reads to show you what changed since your previo
5
5
 
6
6
  The canonical, richest notes live in the [GitHub Releases](https://github.com/mashware/flow-workflows/releases).
7
7
 
8
+ ## v0.91.0 — An unattended run resolves the conflicts with its base when the orchestrator launches it for them · 2026-10-11
9
+
10
+ **In short**
11
+ - **A new `why`: `conflict`.** An orchestrator that sees the run's open MR/PR conflict with its base
12
+ launches it with `why = conflict`; `/flow:work:run` sends it to `/flow:work:green`. Before, an
13
+ unattended `green` turned any conflict into `blocked` and the draft waited for a person.
14
+ - **`<cmd> events --json`** on such a run is `{"conflict": {"base", "base_sha"}}`, and the base is in
15
+ the checkout as `origin/<base>` at that commit — the orchestrator brings it in, the run never
16
+ fetches.
17
+ - **`green` merges, never rebases**, and resolves each conflict on its merits, exactly as §5.C
18
+ already says (no `--ours`/`--theirs` wholesale, generated files regenerated, the full local gate
19
+ after), then publishes. Integrating the base is recorded, not asked: the run was launched for it.
20
+ - **Two sides that want contradicting things are a product decision**: the merge is aborted and the
21
+ run asks (`decision`). The answer re-enters `green`, which merges again from `origin/<base>`.
22
+ - On any other `why`, a conflict is still `blocked`.
23
+
24
+ ## v0.90.0 — A query that changed reaches the MR with its plan, or with the admission that nobody measured it · 2026-10-07
25
+
26
+ **In short**
27
+ - **A modified query always leaves a performance comment**, measured or not. Before, a repo without
28
+ `data.explain_cmd` closed the review with the query marked `ok` on the schema alone, the ship step
29
+ saw "nothing measured" and posted nothing — so the reviewer could not tell a query that had been
30
+ checked from one nobody looked at.
31
+ - **The comment sits on the query**: an inline thread on the changed lines of the diff, one per
32
+ modified query, instead of a general note under the title. A general comment is only the fallback
33
+ when the forge refuses the anchor.
34
+ - **Before and after, side by side**: plan (access, index, rows read), rows returned and time
35
+ `min–max` on the merge base and on the branch; peak memory comes from the entry point that runs the
36
+ query, through `quality.bench_cmd`. Missing pieces say `not measured (<reason>)` and which key
37
+ would measure them.
38
+ - **The comment ends with the query and its read-only plan command**, ready to run on production or a
39
+ replica; the answer goes in the thread.
40
+ - **A bare `ok` is gone for a modified query with no plan**: the review writes
41
+ `ok (plan not measured)`.
42
+ - `docs/CONFIGURATION.md` now lists what the comment can say for each combination of `data.*` and
43
+ `quality.bench_cmd`.
44
+
8
45
  ## v0.89.0 — An unattended run can stop at the end of the study, for a person to approve it · 2026-10-02
9
46
 
10
47
  **In short**