dflow-sdd-ddd 0.13.0 → 0.15.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 (96) hide show
  1. package/CHANGELOG.md +824 -1
  2. package/CONTRIBUTING.md +16 -10
  3. package/README.en.md +156 -200
  4. package/README.md +89 -144
  5. package/TEMPLATE-COVERAGE.md +15 -8
  6. package/TEMPLATE-LANGUAGE-GLOSSARY.md +15 -1
  7. package/bin/dflow.js +36 -4
  8. package/docs/commands.en.md +110 -0
  9. package/docs/commands.md +101 -0
  10. package/docs/doctor-uncertainty.en.md +212 -0
  11. package/docs/doctor-uncertainty.md +212 -0
  12. package/docs/evaluating-dflow.en.md +29 -11
  13. package/docs/evaluating-dflow.md +8 -6
  14. package/docs/npm-publish-checklist.md +3 -1
  15. package/docs/release-versioning-policy.md +8 -2
  16. package/docs/upgrading.en.md +196 -0
  17. package/docs/upgrading.md +197 -0
  18. package/docs/using-with-claude-code.en.md +25 -10
  19. package/docs/using-with-claude-code.md +20 -7
  20. package/docs/using-with-codex.en.md +18 -6
  21. package/docs/using-with-codex.md +16 -5
  22. package/docs/using-with-github-copilot.en.md +25 -10
  23. package/docs/using-with-github-copilot.md +21 -8
  24. package/lib/doc-shapes.json +997 -0
  25. package/lib/doctor-checks.js +2654 -0
  26. package/lib/init.js +3583 -107
  27. package/lib/render-diagrams.js +1474 -0
  28. package/lib/render.js +865 -49
  29. package/package.json +2 -2
  30. package/templates/brownfield/references/drift-verification.md +4 -0
  31. package/templates/brownfield/references/finish-feature-flow.md +635 -88
  32. package/templates/brownfield/references/finish-feature-follow-up.md +60 -0
  33. package/templates/brownfield/references/finish-feature-minimal-host.md +406 -0
  34. package/templates/brownfield/references/finish-feature-post-hoc-hotfix.md +95 -0
  35. package/templates/brownfield/references/git-integration.md +160 -15
  36. package/templates/brownfield/references/init-project-flow.md +26 -4
  37. package/templates/brownfield/references/modify-existing-flow.md +412 -87
  38. package/templates/brownfield/references/modify-existing-follow-up.md +121 -0
  39. package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +82 -0
  40. package/templates/brownfield/references/new-feature-flow.md +61 -6
  41. package/templates/brownfield/references/new-phase-flow.md +57 -7
  42. package/templates/brownfield/references/pr-review-checklist.md +303 -10
  43. package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +158 -34
  44. package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +1 -1
  45. package/templates/brownfield/scaffolding/Git-principles-gitflow.md +75 -6
  46. package/templates/brownfield/scaffolding/Git-principles-trunk.md +82 -8
  47. package/templates/brownfield/scaffolding/_conventions.md +50 -28
  48. package/templates/brownfield/scaffolding/_overview.md +1 -0
  49. package/templates/brownfield/templates/_index.md +151 -7
  50. package/templates/brownfield/templates/analysis.md +79 -0
  51. package/templates/brownfield/templates/behavior.md +1 -0
  52. package/templates/brownfield/templates/context-definition.md +1 -0
  53. package/templates/brownfield/templates/context-map.md +2 -1
  54. package/templates/brownfield/templates/glossary.md +1 -0
  55. package/templates/brownfield/templates/lightweight-spec.md +154 -11
  56. package/templates/brownfield/templates/models.md +1 -0
  57. package/templates/brownfield/templates/phase-spec.md +9 -1
  58. package/templates/brownfield/templates/rules.md +1 -0
  59. package/templates/brownfield/templates/tech-debt.md +1 -0
  60. package/templates/common/references/ddd-modeling-guide.md +33 -16
  61. package/templates/{greenfield → common}/references/dflow-feedback-flow.md +2 -1
  62. package/templates/common/references/flow-rationale-registry.md +130 -0
  63. package/templates/common/skill/SKILL.md +13 -11
  64. package/templates/greenfield/references/drift-verification.md +4 -0
  65. package/templates/greenfield/references/finish-feature-flow.md +625 -89
  66. package/templates/greenfield/references/finish-feature-follow-up.md +60 -0
  67. package/templates/greenfield/references/finish-feature-minimal-host.md +363 -0
  68. package/templates/greenfield/references/finish-feature-post-hoc-hotfix.md +95 -0
  69. package/templates/greenfield/references/git-integration.md +148 -15
  70. package/templates/greenfield/references/init-project-flow.md +28 -8
  71. package/templates/greenfield/references/modify-existing-flow.md +378 -85
  72. package/templates/greenfield/references/modify-existing-follow-up.md +103 -0
  73. package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +82 -0
  74. package/templates/greenfield/references/new-feature-flow.md +67 -4
  75. package/templates/greenfield/references/new-phase-flow.md +56 -7
  76. package/templates/greenfield/references/pr-review-checklist.md +287 -8
  77. package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +153 -32
  78. package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +5 -2
  79. package/templates/greenfield/scaffolding/Git-principles-gitflow.md +74 -6
  80. package/templates/greenfield/scaffolding/Git-principles-trunk.md +88 -12
  81. package/templates/greenfield/scaffolding/_conventions.md +50 -28
  82. package/templates/greenfield/scaffolding/_overview.md +6 -2
  83. package/templates/greenfield/templates/_index.md +137 -7
  84. package/templates/greenfield/templates/aggregate-design.md +1 -0
  85. package/templates/greenfield/templates/analysis.md +79 -0
  86. package/templates/greenfield/templates/behavior.md +1 -0
  87. package/templates/greenfield/templates/context-definition.md +1 -0
  88. package/templates/greenfield/templates/context-map.md +2 -1
  89. package/templates/greenfield/templates/events.md +4 -1
  90. package/templates/greenfield/templates/glossary.md +1 -0
  91. package/templates/greenfield/templates/lightweight-spec.md +154 -11
  92. package/templates/greenfield/templates/models.md +1 -0
  93. package/templates/greenfield/templates/phase-spec.md +9 -1
  94. package/templates/greenfield/templates/rules.md +1 -0
  95. package/templates/greenfield/templates/tech-debt.md +1 -0
  96. package/templates/brownfield/references/dflow-feedback-flow.md +0 -251
@@ -15,26 +15,35 @@ state, archives the feature directory, and emits a Git-strategy-neutral
15
15
  own. Merge strategy follows the team's selected Git policy (`gitflow` /
16
16
  `trunk`, recorded in `dflow/specs/shared/_conventions.md` § Git Policy).
17
17
  - Closeout is split into two gates so it works offline: a **Local-closeout
18
- gate** (Steps 1–4: validation, status flip, BC sync, archive + an optional
19
- commit checkpoint — all doable with no network) and an **Integration / PR
18
+ gate** (Steps 1–4: validation, status flip, BC sync, archive + the closeout
19
+ commit checkpoint — all doable with no network; what is optional is **who**
20
+ runs that commit, not whether it happens, see Step 4) and an **Integration / PR
20
21
  gate** (Step 5: push / merge / PR — needs network; the AI only runs
21
22
  `git push` / `gh pr create` when you explicitly ask).
22
23
  - At the archive checkpoint the AI may offer to commit using your Git identity;
23
24
  you can always decline. The commit marker mode is read from `_conventions.md`
24
25
  § AI Commit Policy. This replaces Dflow's earlier "the AI never commits"
25
26
  stance — the AI helps at natural checkpoints, you keep the final say.
26
- - The BC-layer sync in Step 3 **reuses the existing Step 5.3 mechanism**
27
- from `new-feature-flow` (Step 8.3) and `modify-existing-flow` (Step
28
- 5.3) — it does not introduce a new sync flow. Treat it as "lift Step
29
- 5.3 / 8.3 out of the per-phase checklist and run it once at feature
27
+ - The BC-layer sync in Step 3 **reuses the existing documentation-sync
28
+ mechanism** from `new-feature-flow` (§ 8.3) and `modify-existing-flow`
29
+ (Step 5.3) — it does not introduce a new sync flow. Treat it as "lift
30
+ that step out of the per-phase checklist and run it once at feature
30
31
  closeout, with the `_index.md` Current BR Snapshot as input."
31
32
 
32
33
  **Step Gates** in this flow (stop-and-confirm before proceeding):
33
34
  - Step 1 → Step 2 (validation passed → flip status)
34
35
  - Step 3 → Step 4 (BC sync done → archive)
35
- - Step 5 → Step 6 (Integration Summary emitted → optional follow-up reverse-link)
36
36
 
37
- All other step transitions are **step-internal**: announce "Step N complete,
37
+ **Step 5 → Step 6 is deliberately not a step gate, and when the host carries a
38
+ `follow-up-of` field it also stops and waits.** It is this flow's
39
+ **post-Local-closeout confirmation**: by the time it is reached the archive has
40
+ landed and this feature's cursor reads `none`, so the step-gate protocol —
41
+ `/dflow:next` and `/dflow:cancel`, and the cursor update a step gate carries —
42
+ does not apply to it. **Step 5 states what it accepts.** Do not treat it as a
43
+ step gate because it waits, and do not treat it as step-internal because it is
44
+ not a gate.
45
+
46
+ **Every other step transition is step-internal**: announce "Step N complete,
38
47
  entering Step N+1" and proceed without waiting. See AI-AGENT-GUIDE.md § Workflow
39
48
  Transparency for the full transparency protocol and confirmation signals.
40
49
 
@@ -49,12 +58,59 @@ AI runs mechanical checks first. Report `✓` / `✗` for every item; if any
49
58
  `✗` appears, **stop here** and ask the developer to address them before
50
59
  proceeding (do not flip status, do not archive, do not emit summary).
51
60
 
61
+ **What these checks do and do not prove.** They catch the mistakes that actually
62
+ happen — work left uncommitted, a placeholder hash never replaced, a row copied
63
+ from another feature — not a record deliberately built to look right.
64
+ Whole-history assertions ("no stray host-open commit exists anywhere on this
65
+ branch") are **review's** job, not closeout's: they belong to
66
+ `references/pr-review-checklist.md`.
67
+
68
+ > **Reconcile a mainline hotfix BEFORE you run these checks.** If a T2 / T3
69
+ > post-hoc hotfix (`modify-existing-flow.md` Step 1.8) landed on the mainline
70
+ > while this feature was in flight and the two touched the same code, settle the
71
+ > overlap **now, before the checklist below** — anything *recorded into this
72
+ > host* after the checks have passed has bypassed them.
73
+ >
74
+ > **Open `references/finish-feature-post-hoc-hotfix.md` and follow it there.**
75
+ > What you classify, where the merge-resolution delta may be recorded, and what
76
+ > must not be routed mid-closeout are decided there.
77
+
78
+ **What "Minimal host (zero-phase) only" selects, and what that does not prove.**
79
+ A minimal host takes extra checks and extra field rules. Decide it from the
80
+ **persisted shape**: an **empty Phase Specs table and no `phase-spec-*` file**
81
+ in the host directory. There is no other selector — closeout runs in a fresh
82
+ session and cannot know which flow step created the host. So a host that
83
+ *carries* a phase is certified as **phase-bearing**, whatever it was intended to
84
+ be, and takes the ordinary checks rather than these. This gate does **not**
85
+ prove "this host was opened as minimal and stayed that way"; that is a claim
86
+ about history, and it belongs with the whole-history assertion already assigned
87
+ to `references/pr-review-checklist.md`.
88
+
89
+ **Open `references/finish-feature-minimal-host.md` and follow it there.** Do
90
+ this only when this host is minimal; a **phase-bearing** host does not open it
91
+ at all. Its rules live in that file and are not repeated here, and it stays open
92
+ for the whole closeout — it adds to this checklist, to Step 3's sync input, to
93
+ Step 4's post-commit verification, and to Step 5's Integration Summary fields.
94
+
52
95
  - [ ] Locate the feature directory at `dflow/specs/features/active/{SPEC-ID}-{slug}/`
53
- - [ ] `_index.md` exists and parses (YAML front matter intact, seven required
54
- sections present, including the Checkpoint Log)
96
+ - [ ] `_index.md` exists and parses (YAML front matter intact, and all seven
97
+ required sections present — the Metadata front matter, Goals & Scope,
98
+ Phase Specs, Current BR Snapshot, Lightweight Changes, Checkpoint Log,
99
+ Resume Pointer; `Follow-up Tracking` is `templates/_index.md`'s optional
100
+ eighth section and appears only when this feature has follow-ups)
55
101
  - [ ] Every row in `_index.md` Phase Specs table has Status = `completed`
56
102
  - [ ] Every phase-spec file referenced in the Phase Specs table exists at
57
103
  the path the table claims
104
+ - [ ] **And the other direction, for every host shape: every spec file in the
105
+ host directory is named by a row** — each `phase-spec-*` by a Phase Specs
106
+ row, each `lightweight-*.md` / `BUG-*.md` by a `Tier = T2` Lightweight
107
+ Changes row. An orphan file **blocks** — either it belongs to a phase or
108
+ change whose row is missing, or it belongs to nothing and does not belong
109
+ in the host.
110
+ **One file is legitimately unrowed**: an `aggregate-design.md` worksheet,
111
+ which `AI-AGENT-GUIDE.md` § Ceremony Scaling orders into the feature
112
+ directory for a T1 introducing a new Aggregate / BC. No table names it by
113
+ design — it is a worksheet, not a spec — so it is not an orphan.
58
114
  - [ ] Every phase-spec file's frontmatter has `status: completed`
59
115
  - [ ] Every Tier = T2 row in `_index.md` Lightweight Changes references an
60
116
  existing `lightweight-*.md` / `BUG-*.md` file in the feature directory
@@ -62,8 +118,53 @@ proceeding (do not flip status, do not archive, do not emit summary).
62
118
  `status: completed`
63
119
  - [ ] `_index.md` has no obvious open items in Resume Pointer (e.g. "phase-N
64
120
  drafting" / "implementation pending" / "TODO" markers)
65
- - [ ] Current BR Snapshot table is non-empty (or feature is intentionally
66
- a no-BR feature — confirm with developer if uncertain)
121
+ - [ ] **You are on this host's branch — or on a branch this host recorded a
122
+ sanctioned override for.** `git rev-parse --abbrev-ref HEAD`
123
+ equals `_index.md`'s `branch:` value — that field is authoritative for the
124
+ whole host — and for a **T2** the lightweight-spec's frontmatter `branch:`
125
+ equals it too. Steps 1–4 run *before* the merge / PR gate, so a mismatch
126
+ means you are closing out somewhere other than where the work was
127
+ finished, and it **blocks**. A hash check does not cover this: any sibling
128
+ branch descended from checkpoint 1 carries the same commits, so every hash
129
+ this host's record names still resolves and is still an ancestor. For a
130
+ **post-hoc** host this compares against the **documentation** branch,
131
+ never `hotfix-branch:`, which names the already-merged hotfix.
132
+ **Before blocking on a mismatch, read the Checkpoint Log for a
133
+ `branch-override` row.** `references/git-integration.md` § Branch gate
134
+ offers "override and stay" as a sanctioned third option and defines that
135
+ row's shape. If **any** such row's Result names the branch `HEAD` is on,
136
+ the mismatch is one this host recorded deliberately and this item
137
+ **passes**. `branch:` itself is still never rewritten — the override is a
138
+ record beside it, not a correction of it.
139
+ ⚠ **The row must name *this* branch** — that is the whole test.
140
+ ⚠ **What this cannot decide, stated rather than patched:** whether a
141
+ recorded override is still the current intent. **Nothing expires a row at
142
+ all** — not when the developer moves back to the host's own branch, not
143
+ when a later override supersedes it, and not with distance from closeout.
144
+ So *any* branch this host ever recorded an override for stays acceptable
145
+ here: closing out on the base branch after the feature branch was merged
146
+ passes, and so does closing out on an abandoned spike branch that an
147
+ override named two phases ago. Closeout cannot separate either from a
148
+ legitimate override. **Which branch the closeout commit actually landed on
149
+ is visible in the PR**, and it is judged by
150
+ `references/pr-review-checklist.md`'s **"A recorded branch override still
151
+ matches where the closeout landed"** item.
152
+ **When it passes this way, say so in the conversation** — name the row and
153
+ the branch, e.g. *"HEAD differs from `branch:`; found a recorded
154
+ `branch-override` for `{branch}` in the Checkpoint Log — verified and
155
+ passing."*
156
+ - [ ] Current BR Snapshot table is non-empty — **or this host's own record
157
+ carries no BR delta**, which is what makes an empty one legitimate.
158
+ That condition is the check; the shapes below are illustrations of it,
159
+ not the list of them: a **T3** host; a **no-BR family T2** whose Behavior
160
+ Delta is a `BR:` / `BR Delta:` none line; a **phase-bearing** host —
161
+ **including a T1** — whose phase-specs establish no BR delta, which the
162
+ cascade explicitly allows; or a shape added later that likewise carries
163
+ none. Decide from the artifact, not from a declaration:
164
+ "the feature is intentionally no-BR" is a claim, and the record is what
165
+ settles it. A classic BR-delta spec carrying ADDED / MODIFIED / RENAMED
166
+ entries **and** an empty Snapshot means finalization never refreshed it
167
+ (`modify-existing-flow.md` Step 1.7) and **blocks**.
67
168
 
68
169
  If any check fails:
69
170
  > "Cannot finish feature `{SPEC-ID}-{slug}` yet — {N} validation issues
@@ -75,6 +176,50 @@ If any check fails:
75
176
  > Address these (run `/dflow:new-phase` to add missing work, or fix the
76
177
  > stale status manually), then re-run `/dflow:finish-feature`."
77
178
 
179
+ **Once every item above — and every item this host's branch file added — reads
180
+ `✓`, record the baseline the post-commit check compares against.** Capture
181
+ **exactly the set the closeout commit will carry**:
182
+
183
+ ```bash
184
+ git ls-files --cached --others --exclude-standard -- dflow/specs/features/active/{SPEC-ID}-{slug}
185
+ ```
186
+
187
+ ⚠ **Both halves of that command are load-bearing, because it has to agree with
188
+ `git add`.** It **excludes** ignored files, which `git add` also skips and the
189
+ span can never carry. It **includes** untracked files that are not ignored,
190
+ which `git add` **does** stage — a host file created during this feature and
191
+ never committed still rides into the closeout commit, so a baseline without it
192
+ reports that file as added after Step 1 and blocks a correct closeout.
193
+
194
+ Run `git hash-object -w {path}` for each and build the `path → blob` list, one
195
+ entry per line. **Strip the host-directory prefix**: that command prints paths
196
+ from the repository root, and Step 4 matches on the path *relative to the host
197
+ directory*.
198
+ ⚠ **The `-w` is mandatory.** It writes each blob into the object database, so
199
+ Step 4 can read the baseline's **content** back; without it the list is
200
+ fingerprints only, and Step 4's comparison cannot run.
201
+
202
+ **Then anchor the list itself where Step 4 can recompute its name** — pipe the
203
+ list into `git hash-object -w --stdin`, then:
204
+
205
+ ```bash
206
+ git update-ref refs/dflow/closeout-baseline/{SPEC-ID}-{slug} {list blob}
207
+ ```
208
+
209
+ **That ref is the baseline.** Step 4 resolves it by recomputing the same name
210
+ from this host's own `{SPEC-ID}-{slug}`; if `git check-ref-format` rejects that
211
+ name, say so and stop.
212
+ ⚠ **Trim both fields of every entry, on write and on read.** A shell that
213
+ writes CRLF leaves a trailing `\r` on each line, and `git cat-file -p` on a blob
214
+ id carrying one fails as an invalid object name — which would report a healthy
215
+ baseline as unreadable.
216
+ ⚠ **`refs/dflow/**` is local by construction.** A default `git push`, `git fetch`
217
+ and a plain `git clone` all leave it behind; an explicit refspec or
218
+ `clone --mirror` does carry it. Do not add it to any refspec.
219
+ ⚠ **`HEAD^` is not a substitute** — at this point the working tree legitimately
220
+ carries uncommitted finalization edits, so comparing against the parent commit
221
+ reports every one of them as a difference no step ordered.
222
+
78
223
  **→ Step Gate: Step 1 → Step 2**
79
224
 
80
225
  If all checks pass:
@@ -94,45 +239,117 @@ spec-id: SPEC-{YYYYMMDD}-{NNN}
94
239
  slug: {slug}
95
240
  status: completed # ← flipped from in-progress
96
241
  created: {YYYY-MM-DD}
97
- branch: feature/{SPEC-ID}-{slug}
242
+ branch: {unchanged — keep the host's existing value} # feature/{SPEC-ID}-{slug} or bugfix/BUG-{NUMBER}-{slug}; never rewrite
98
243
  ---
99
244
  ```
100
245
 
101
- Also update the **Resume Pointer** to reflect closeout — this writes the
102
- cursor's terminal state (after closeout no workflow is active on this
103
- feature; do not edit the cursor again after the Step 4 closeout commit):
246
+ **Flip `status` only — never rewrite `branch:`.** The `branch:` field is
247
+ authoritative and stays exactly as opened: a bugfix host keeps its
248
+ `bugfix/BUG-{NUMBER}-{slug}` value, a **non-bug** standalone / follow-up host
249
+ keeps its `feature/{SPEC-ID}-{slug}` value. The branch follows the **change
250
+ class**, not how the host was opened — a functional-bug standalone or
251
+ follow-up host **is** a bugfix host and keeps `bugfix/BUG-*` (the branch-by-class
252
+ rule at `modify-existing-flow.md` Step 1.7 step 4). Overwriting it to
253
+ `feature/...` at closeout would break branch equality for a bugfix host.
254
+
255
+ Also update the **Resume Pointer** — to the state closeout is actually in, which
256
+ is **not** the terminal state. **Step 4 writes the terminal value**, immediately
257
+ after the `git mv`:
104
258
 
105
259
  ```
106
- **Current Progress**: feature completed ({date}); all phase-specs status = completed.
107
- **Next Action**: integration — push / merge / PR per the selected Git policy.
108
- **Active Workflow**: none
109
- **Current Step**: n/a
110
- **Gates Passed**: n/a
111
- **Awaiting**: none
260
+ **Current Progress**: status flipped to `completed` ({date}); closeout in progress.
261
+ **Next Action**: continue closeout — sync the BR Snapshot to the BC layer (Step 3).
262
+ **Active Workflow**: finish-feature
263
+ **Current Step**: Step 3 — sync BR Snapshot to BC layer
264
+ **Gates Passed**: 1→2
265
+ **Awaiting**: none (mid-step)
112
266
  ```
113
267
 
268
+ ⚠ **`Awaiting` is `none (mid-step)`, never `gate 3→4`.** Step 3 has not run at
269
+ this point, and a cursor that says otherwise sends the next session to
270
+ `/dflow:next`, **skipping the BC sync**. `none (mid-step)` is the existing
271
+ convention for this position.
272
+
114
273
  **→ Transition (step-internal)**: Step 2 complete. Announce "Step 2 complete (status flipped). Entering Step 3: Sync BR Snapshot to BC layer." and continue.
115
274
 
116
275
  ## Step 3: Sync `_index.md` Current BR Snapshot to BC Layer
117
276
 
118
- This step **reuses the existing sync mechanism** from `new-feature-flow`
277
+ **First, branch on what this host actually carries** — "zero-phase" (no
278
+ phase-spec) is independent of whether a bounded context exists, so a minimal
279
+ host may still touch a real BC, or none at all:
280
+
281
+ - **(i) BC-bearing** — the host touched a real bounded context, **whether or
282
+ not it carries a BR delta** (BC presence and BR presence are independent). Run
283
+ the sync below **for the documents this host actually changed**: a BR delta
284
+ updates `rules.md` / `behavior.md`; a no-BR family that touched, say, an event
285
+ field updates `events.md`. Fill the Integration Summary's BC field with the
286
+ context, and its BR-IDs with whatever applies — a real set, the per-family
287
+ no-BR marker, or empty. Do **not** skip, and do **not** manufacture a BR delta
288
+ a no-BR host does not have.
289
+ **`Aggregates affected:` and `Domain Events Changes:` take what this host
290
+ actually changed — and `none` for those it did not.** ⚠ **Every BC-bearing
291
+ host, not only a zero-phase one.** An unchanged field is reported `none`, never
292
+ left blank: a blank cannot be told apart from a field nobody filled in, and the
293
+ reader of this summary has no other source for the difference. The same shape
294
+ for a zero-phase host, and the authority on that host's *whole* field set, is
295
+ `references/finish-feature-minimal-host.md` § Step 5. This sentence is the
296
+ half that was only ever written there.
297
+ - **(ii) no-BC** — the host touched **no** bounded context at all (a display
298
+ T3, an appearance sweep). **Skip this sync entirely** — do **not** create
299
+ `rules.md` / `behavior.md` / `events.md`, and do not invent a BC to sync into.
300
+ Its Integration Summary sets the fields that **report a sync** to `none` —
301
+ `BC`, `Aggregates affected`, `Domain Events Changes`. **`Related BR-IDs` is
302
+ not one of those**: it reports what this change's own record carries, so it
303
+ stays empty or keeps the per-family no-BR marker. Step 5's "exact fields"
304
+ block is the authority on the shape; do not flatten it to "all BR fields are
305
+ none" here.
306
+
307
+ For a **BC-bearing** host, continue with the sync. This step **reuses the
308
+ existing sync mechanism** from `new-feature-flow`
119
309
  Step 8.3 / `modify-existing-flow` Step 5.3 (`dflow/specs/domain/{context}/rules.md`
120
310
  + `behavior.md` + `events.md` + `context-map.md` updates). The input is
121
311
  the feature's `_index.md` Current BR Snapshot table; the output is the
122
312
  BC's `rules.md` / `behavior.md` updated to reflect the feature's net
123
313
  effect.
124
314
 
125
- Before syncing, ensure required BC files exist. If missing, create from templates:
315
+ **Minimal host — sync input.** A minimal host reads "phase-spec" differently
316
+ here, and a phase-bearing host must not apply that reading. The rule is
317
+ `references/finish-feature-minimal-host.md` § Step 3.
318
+
319
+ **Every host — lightweight-spec deltas are a sync input too.** A host of **any**
320
+ shape may carry hosted Lightweight Changes rows: Step 1's two `Tier = T2` checks
321
+ carry no host-shape restriction, so they reach a phase-bearing host as well. So
322
+ a phase-bearing host can hold a hosted T2 whose delta belongs in
323
+ this sync, and **the steps below name phase-specs only**. Read them as *this
324
+ feature's phase-specs **and** its hosted lightweight-specs' recorded deltas*.
325
+ ⚠ **Where this bites hardest is a no-BR family**, because the BR-driven sections
326
+ below cannot reach it at all: with no BR-ID there is no Current BR Snapshot row
327
+ to iterate, so a hosted T2 that changed only an event field or a documented
328
+ behaviour is visible **solely** in its own recorded delta. Miss it and
329
+ `events.md` / `behavior.md` silently lose a change that a phase-bearing closeout
330
+ had no other instruction to look for.
331
+
332
+ Before syncing, ensure the BC files **this sync actually writes** exist; create a
333
+ missing one from its template **only when this host's delta writes to it**. Case
334
+ (i) above syncs "the documents this host actually changed", so a document this
335
+ host does not touch is **not** created — a BC-bearing **T3**, or a no-BR family
336
+ that touched only one of them, leaves the others absent, exactly as case (ii)
337
+ does for a no-BC host. Creating one anyway plants the same fiction the no-BC
338
+ guard refuses:
126
339
  - `dflow/specs/domain/{context}/rules.md` → `templates/rules.md`
127
340
  - `dflow/specs/domain/{context}/behavior.md` → `templates/behavior.md`
128
341
  - `dflow/specs/domain/{context}/events.md` → `templates/events.md`
129
342
 
130
- For each row in Current BR Snapshot where Status = `active`:
343
+ > **Table-cell formatting**: keep table cells concise — separate multiple short items with `<br>` (never chain them into one line with ;/; separators), and move long narrative detail out of the cell into a document section (full convention: the formatting comment at each spec doc's head).
131
344
 
132
- - If the BR-ID is **not yet in `rules.md`** → add it (new ADDED rule
133
- introduced by this feature)
134
- - If the BR-ID is **already in `rules.md`** but the rule text differs →
135
- update it (MODIFIED rule, reflect the new text)
345
+ For each row in Current BR Snapshot — **every** row, not only the `active` ones.
346
+ The branch below is selected by the row's own Status, and a `removed` row is
347
+ exactly what the REMOVED branch needs:
348
+
349
+ - Status `active`, and the BR-ID is **not yet in `rules.md`** → add it (new
350
+ ADDED rule introduced by this feature)
351
+ - Status `active`, and the BR-ID is **already in `rules.md`** but the rule text
352
+ differs → update it (MODIFIED rule, reflect the new text)
136
353
  - If the BR-ID was previously in `rules.md` and is now in Current BR
137
354
  Snapshot with Status = `removed` → remove the corresponding section in
138
355
  `rules.md` (REMOVED rule)
@@ -151,7 +368,10 @@ For `behavior.md`:
151
368
  `behavior.md`
152
369
 
153
370
  For `events.md`:
154
- - Add any new Domain Events introduced by phase-specs in this feature
371
+ - Add any new Domain Events introduced by this feature — **by its phase-specs
372
+ and by its hosted lightweight-specs alike** (see "Every host — lightweight-spec
373
+ deltas are a sync input too" above; a hosted no-BR T2 appears in neither the
374
+ phase-specs nor the BR Snapshot)
155
375
  - Remove events that were REMOVED across the feature's net delta
156
376
  - Update producers / consumers if Aggregate ownership shifted
157
377
 
@@ -164,7 +384,8 @@ rules.md ↔ behavior.md drift check (see `references/drift-verification.md`).
164
384
 
165
385
  Cross-reference each phase-spec's Delta-from-prior-phases section to
166
386
  double-check the net result; the Snapshot is the SSOT but the per-phase
167
- Deltas are the audit trail.
387
+ Deltas are the audit trail. **Cross-reference each hosted lightweight-spec's
388
+ recorded delta the same way** — on any host shape, for the reason above.
168
389
 
169
390
  > Note: this step does NOT read individual phase-specs to re-derive the BR
170
391
  > set — that work was already reconciled by `/dflow:new-phase` Step 7 each
@@ -173,10 +394,12 @@ Deltas are the audit trail.
173
394
  > and the phase-specs, fix `_index.md` first, then re-run
174
395
  > `/dflow:finish-feature`.
175
396
 
176
- Also update `architecture/tech-debt.md` / `models.md` / `glossary.md` as
177
- discovered during the feature (the same items listed in
178
- `new-feature-flow.md` Step 8.3) — these may have been touched per phase
179
- already; this is the closeout sweep.
397
+ Also update `architecture/tech-debt.md` / `models.md` / `glossary.md` /
398
+ `analysis.md` as discovered during the feature (the same items listed in
399
+ `new-feature-flow.md` Step 8.3; `analysis.md` is two files — the owning
400
+ context's and the domain-root one — each created from `templates/analysis.md`
401
+ the first time there is something to record) — these may have been touched per
402
+ phase already; this is the closeout sweep.
180
403
 
181
404
  **→ Step Gate: Step 3 → Step 4**
182
405
 
@@ -186,25 +409,78 @@ already; this is the closeout sweep.
186
409
  > `context-map.md` {updated / unchanged}, `last-updated` set to {date}.
187
410
  > Ready to archive the feature directory? `/dflow:next` to proceed."
188
411
 
412
+ For a **no-BC host** (case ii) the sync was skipped — do **not** announce a
413
+ sync that did not happen. Say instead: "No BC-scoped sync was performed (no-BC
414
+ host). Ready to archive the feature directory? `/dflow:next` to proceed." Do
415
+ **not** say "nothing was written to the Domain layer" — a no-BC host may still
416
+ have updated a **global** document (`glossary.md`, `domain/analysis.md`,
417
+ `architecture/tech-debt.md`); those belong to no bounded context and Step 4
418
+ must still stage them.
419
+
189
420
  Wait for confirmation before entering Step 4.
190
421
 
191
422
  ## Step 4: Archive — `git mv` the Feature Directory
192
423
 
193
- AI runs:
424
+ **First, confirm the baseline is still readable — before anything irreversible:**
425
+
426
+ ```bash
427
+ git cat-file -p refs/dflow/closeout-baseline/{SPEC-ID}-{slug}
428
+ ```
429
+
430
+ If it does not resolve, or the list it prints names a blob that does not read
431
+ back, **stop here** and say the baseline is unavailable: closeout restarts from
432
+ Step 1, which re-captures it. **Nothing is committed yet and the host is still in
433
+ `active/`** — that is what this position buys.
434
+ ⚠ **This does not prove the baseline is the *right* one** — only that it is
435
+ readable. Reading back and *judging* is the post-commit item's job.
436
+
437
+ Then AI runs:
194
438
 
195
439
  ```bash
196
440
  git mv dflow/specs/features/active/{SPEC-ID}-{slug} \
197
441
  dflow/specs/features/completed/{SPEC-ID}-{slug}
198
- git status # confirm rename detection AND check for `RM` — an `M` next to
199
- # a rename means unstaged edits you must re-add before committing
200
442
  ```
201
443
 
202
- `git mv` is mandatory — never use plain `mv` + `git add`. This preserves
203
- git's directory rename detection so `git log --follow` / `git blame` /
204
- PR diff quality stays intact across the move. See
444
+ `git mv` is mandatory — never use plain `mv` + `git add`. See
205
445
  `references/git-integration.md` § "Directory Moves Must Use git mv" for
206
446
  the full rule set.
207
447
 
448
+ **Immediately after the `git mv`, write the Resume Pointer's terminal value into
449
+ the moved `_index.md`.** Write both prose lines and all four cursor fields —
450
+ all six lines of the Resume Pointer — because this write sets each line's final
451
+ value:
452
+
453
+ ```
454
+ **Current Progress**: feature completed ({date}); all phase-specs status = completed.
455
+ **Next Action**: integration — push / merge / PR per the selected Git policy.
456
+ **Active Workflow**: none
457
+ **Current Step**: n/a
458
+ **Gates Passed**: n/a
459
+ **Awaiting**: none
460
+ ```
461
+
462
+ ⚠⚠ **The `git mv` and this write are one uninterruptible pair.** Nothing goes
463
+ between them — not the Y / N prompt below, not a question to the developer, not
464
+ a tool call that can wait on input, and not the `git status` check below.
465
+
466
+ ⚠ **From here the cursor is terminal and closeout does not edit it again** — not
467
+ when the developer declines the commit (N), not when the commit fails, not when
468
+ the post-commit verification below reports `✗`. **Do not restore it to an
469
+ in-progress value on any of those paths.** What surfaces a closeout that failed
470
+ after this point is `git status` — the staged rename plus the uncommitted
471
+ `_index.md` edits — not the cursor, which no global scan reads once the host has
472
+ left `active/`.
473
+
474
+ **Now check the rename landed**, with the terminal cursor already written:
475
+
476
+ ```bash
477
+ git status --short # confirm rename detection AND check for `RM` — an `M`
478
+ # next to a rename means unstaged edits you must re-add
479
+ # before committing. --short is required: the default long
480
+ # format lists the rename and the modification separately
481
+ # and never prints a two-column `RM`.
482
+ ```
483
+
208
484
  **Closeout commit checkpoint** (completes the offline Local-closeout gate):
209
485
 
210
486
  ```
@@ -225,6 +501,14 @@ Then, in this order:
225
501
  still applies to spec / implementation rows — closeout is the documented
226
502
  exception (see `references/git-integration.md` § Commit Checkpoints,
227
503
  Branch Gate & AI Commits).
504
+ **Backfill any unfilled hosted `Commit` cell in the same edit** — a hosted row
505
+ waits for the host's *next* commit and this is it. **Unfilled means empty *or*
506
+ holding a placeholder** — `{hash}`, `{pending}`, `(待 commit)`, anything that
507
+ `git cat-file -t` cannot resolve; both are backfilled the same way, and the
508
+ placeholder case matters more because the cell is **non-empty** and therefore
509
+ invisible to every rule written against empty / non-empty. Write each row's own
510
+ implementation hash, never the closeout hash. After closeout there is no next
511
+ commit, which is why `references/pr-review-checklist.md` asserts it there.
228
512
  2. **Stage the whole archived feature directory:**
229
513
 
230
514
  ```bash
@@ -232,14 +516,68 @@ Then, in this order:
232
516
  ```
233
517
 
234
518
  This is required, not optional: `git mv` stages the rename with the
235
- **last-committed** content, so working-tree edits made earlier in this
236
- flow to the moved files — the Step 2 status flip and Resume Pointer
237
- update, plus the checkpoint row you just wrote — stay **unstaged** until
238
- this `git add`. In `git status`, the moved `_index.md` showing `RM`
239
- instead of plain `R` is exactly this signal. Then also `git add` the
240
- files updated in Step 3 (the updated `rules.md`, `behavior.md`,
241
- `events.md`, `context-map.md`, `glossary.md`,
242
- `architecture/tech-debt.md`, etc.) into the same stage.
519
+ **last-committed** content, so **every** working-tree edit to the moved files
520
+ stays **unstaged** until this `git add` — Step 2's status flip and its
521
+ in-progress cursor, whatever gate 3 → 4 wrote to the cursor, the terminal
522
+ cursor value written just after the `git mv`, and **everything instruction 1
523
+ ordered**: the checkpoint row *and* every hosted `Commit` cell it backfilled.
524
+ Take that last part from instruction 1 itself, not from this sentence — it is
525
+ the step that orders those edits, and a copy kept here would go stale the
526
+ next time it changes. In `git status --short`, the moved `_index.md` showing
527
+ `RM` instead of plain `R` is exactly this signal — the same `--short`
528
+ requirement as the rename check above. Then also `git add`
529
+ **every external document this closeout carries.** That set is defined
530
+ here, once, and it covers **every** host shape — take it from this
531
+ instruction, not from a list kept somewhere else:
532
+ **(a)** whatever Step 3 wrote (`rules.md`, `behavior.md`, `events.md`,
533
+ `models.md`, `analysis.md`, `context-map.md`, `glossary.md`,
534
+ `domain/analysis.md`, `architecture/tech-debt.md`) — Step 3 is **skipped
535
+ entirely for a no-BC host**, so this half is empty there; **and**
536
+ **(b)** the **documentation-sweep step of the flow that produced *this
537
+ change*** — take the paths from that step, not from a list kept here. It runs
538
+ *after* that flow's implementation checkpoint and *before* closeout, so **that
539
+ flow** leaves its deltas uncommitted; a later phase's own checkpoints may have
540
+ committed them since, and staging an already-committed path is a no-op.
541
+ Those sweeps reach
542
+ Domain-layer documents under `dflow/specs/domain/`, plus the **global**
543
+ documents `glossary.md`, `domain/analysis.md` and
544
+ `architecture/tech-debt.md`, which belong to no bounded context and stay
545
+ legitimate for a **no-BC** host. The two that exist
546
+ today —
547
+ `modify-existing-flow.md` **Step 5.3** and `new-feature-flow.md`
548
+ **§ 8.3 Documentation updates** — are **illustrations, not the definition**:
549
+ a later flow with a sweep of its own is covered without editing this line.
550
+ ⚠ **Key it on the flow that produced the change, never on the flow that
551
+ opened the host.**
552
+ ⚠ **How to tell which flow produced a change: it is determined by the
553
+ artifact, and it is recorded nowhere.** The ledger has no producer column, so
554
+ read it off the shape.
555
+ A **Lightweight Changes row** was produced by `modify-existing-flow.md` **by
556
+ construction** — that is the only flow that writes such a row
557
+ (`/dflow:bug-fix` routes into the same file, and `new-feature-flow.md` creates
558
+ that table empty and never adds to it), so its sweep is **Step 5.3**.
559
+ A **phase-spec** has three possible producers — `/dflow:new-feature`,
560
+ `/dflow:new-phase`, or `modify-existing-flow.md` Step 1.6 / 5.4 routing into
561
+ the new-feature machinery — and **you do not have to tell them apart**:
562
+ § 8.3 and Step 5.3 enumerate the same external paths, and `/dflow:new-phase`
563
+ has no sweep at all, so every branch yields that same set or a part of it.
564
+ ⚠ **`/dflow:new-phase` has no sweep and is deliberately absent here.**
565
+ `new-phase-flow.md` Step 7 updates the phase-spec and this host's own tables,
566
+ and says so in as many words — "The bounded context's `rules.md` /
567
+ `behavior.md` / `events.md` and the feature directory move to `completed/`
568
+ remain `/dflow:finish-feature` responsibilities; Step 2 records into
569
+ `analysis.md` directly, at whichever of its two paths the entry belongs to.
570
+ **Do not sync any other BC-level current state**". The external writes its
571
+ own steps call for are those two paths, and the set above already carries
572
+ them.
573
+ On a **minimal host** that set is exactly what allow-list member (iii) of
574
+ `references/finish-feature-minimal-host.md`'s uncommitted-source check
575
+ admitted. On a **phase-bearing** host there is **no allow-list at all** —
576
+ that check is minimal-host-only and lives in that branch file — so read the
577
+ sweep directly and **do not go looking for a list that host never produced.**
578
+ Read each delta (`git diff -- {path}`) and scope every one to *this change*;
579
+ staging only "what Step 3 wrote" leaves a no-BC host's global delta dirty and
580
+ the post-commit clean-tree check fails.
243
581
  3. **Commit (Y) or stop (N).** For Y the AI commits. If a pre-commit hook
244
582
  rejects it or the commit fails, flip the checkpoint row to `failed` (the
245
583
  row is not committed yet — edit it directly), surface the error, and
@@ -249,19 +587,159 @@ Then, in this order:
249
587
  declaring the Local-closeout gate satisfied, AI runs and reports `✓` / `✗` for
250
588
  every item:
251
589
 
252
- - [ ] `git show HEAD:dflow/specs/features/completed/{SPEC-ID}-{slug}/_index.md`
253
- — one blob read verifying **two** things: frontmatter `status: completed`
254
- **and** the Checkpoint Log contains the closeout row. This reads the
255
- **committed** content, not the working tree — the former catches "rename
256
- carried stale content", the latter catches "row never made it into the
257
- commit".
590
+ - [ ] `git ls-tree -r --name-only HEAD
591
+ dflow/specs/features/completed/{SPEC-ID}-{slug}/` — **every file this
592
+ commit carries from inside the archived host directory** — then
593
+ `git show HEAD:<path>` for each.
594
+ **The span, fixed once and used by both halves of this check:** exactly
595
+ that set — nothing wider, nothing narrower. What may differ, and what
596
+ blocks, are judged over exactly that span.
597
+ ⚠ **The span is derived from what was staged, not from what the tables
598
+ name.**
599
+ **The baseline is the `path → blob` list Step 1 anchored** — read it with
600
+ `git cat-file -p refs/dflow/closeout-baseline/{SPEC-ID}-{slug}`, recomputing
601
+ that name from this host's own `{SPEC-ID}-{slug}`. The tree Step 1 read is
602
+ captured rather than remembered, **and so is the way back into it** — this
603
+ resolves in a session that never saw Step 1. **Both sides are already
604
+ host-relative** — Step 1 stripped its prefix, this span is read under
605
+ `completed/{SPEC-ID}-{slug}/`, and the `git mv` preserved the relative
606
+ tree — so the two sets line up with no prefix left to reconcile.
607
+ **Compare the two path sets first, before any blob comparison.** A
608
+ **baseline path missing from the span** is a file removed after Step 1; a
609
+ **span path with no baseline entry** is a file added after Step 1. Each is
610
+ a difference and is judged like any other: it **blocks** unless a step of
611
+ this closeout ordered it.
612
+ Then, for each path the two sets share, take the committed blob with
613
+ `git rev-parse HEAD:{completed path}`. **Different from that path's
614
+ baseline blob → read the delta itself with `git diff {baseline blob}
615
+ HEAD:{completed path}`, and judge that delta against the derived
616
+ condition below. Equal → that path carries no difference at all, which
617
+ satisfies the condition only where no step of this closeout ordered an
618
+ edit to it; where one did, the ordered edit never landed and this
619
+ **blocks**.**
620
+ ⚠ **Blob ids alone answer only *same* / *different*, which cannot decide
621
+ the derived condition.**
622
+ ⚠ **If the ref does not resolve, or the content behind it does not read
623
+ back, report this check as degraded and say so — do not substitute
624
+ `HEAD^`.** The probe at the top of this step exists so that this is reached
625
+ only when the baseline became unreadable *during* closeout.
626
+ Step 1 blocks unless every
627
+ spec in the host already reads `status: completed`, so those flips are
628
+ already **in** the baseline whichever step made them: a **minimal** host's
629
+ come from `modify-existing-flow.md` Step 1.7's finalization, a
630
+ **phase-bearing** host's from its phases completing — no phase-bearing
631
+ host runs Step 1.7, whichever flow opened it. Either way the
632
+ flips are not differences this check admits; they are part of what it
633
+ compares against. (They are differences only from the *pre-flip* record,
634
+ which is not the baseline.)
635
+ **The condition is derived, not listed:** within that span, the committed
636
+ blob differs from that baseline in **exactly the edits Step 2, Step 4's
637
+ terminal Resume Pointer write and Step 4 instruction 1 ordered, carrying
638
+ the values those steps state, and in nothing else**. Read those three
639
+ steps and compute the set — do not keep a second copy of it here. Any
640
+ difference **no step of this closeout ordered** is edit fallout and
641
+ **blocks**.
642
+ ⚠ **This compares final net state, not the sequence of edits.** Step 2
643
+ writes an in-progress cursor and gate 3 → 4 updates it again; Step 4's
644
+ terminal write then overwrites every field either of them touched, so
645
+ neither appears in the committed blob. **Their absence is not a difference
646
+ and must not block**, and nothing here proves they happened. What this
647
+ comparison settles is that the *terminal* value is the one that landed.
648
+ **What this check cannot decide — stated, not asserted:** whether a
649
+ backfilled hosted `Commit` cell holds *that row's own* implementation hash
650
+ rather than some other commit's. Instruction 1 orders that value and is
651
+ the single place the constraint lives; closeout cannot verify it, because
652
+ the hash-evidence test is minimal-host-only and lives in
653
+ `references/finish-feature-minimal-host.md`, so on a phase-bearing host
654
+ **no check reads the value at all**.
655
+ **Identity is confirmed by `references/pr-review-checklist.md`'s
656
+ "Hosted `Commit` cell identity" item**, which sits in that file's
657
+ *Delegated to review by `finish-feature-flow.md`* block — the same
658
+ assignment this file already makes for the whole-history assertion.
659
+ ⚠ **Not** its "Every Lightweight Changes row now carries a `Commit` hash"
660
+ item: that one asserts **presence, not correctness**, and pointing a
661
+ boundary at a presence check would make it a hole rather than a division
662
+ of labour.
663
+ ⚠ **An empty hosted `Commit` cell does not fail this item — and this item
664
+ does not clear it either.** `references/pr-review-checklist.md` asserts that
665
+ cell and says so in its own words; **this check does not**. Judge the item on
666
+ the rest of its evidence and leave the cell to that checklist.
667
+ **Scope, stated so it is not read as more than it is:** this gate re-reads
668
+ the archived **host directory** only — what the archived record *says*.
669
+ What the closeout **commit** *contains* is the separate item below; neither
670
+ implies the other, and "closeout is clean" needs both.
671
+ What has actually gone missing this way: `branch:` rewritten away from the
672
+ value the host opened with (Step 2 flips `status` only, so a bugfix host
673
+ still reads `bugfix/BUG-{NUMBER}-{slug}`); `follow-up-of` dropped by the
674
+ Step 2 metadata edit — Step 5 skips Step 6 when it is absent, so losing it
675
+ strands the original feature's reverse link at `in-progress` while
676
+ closeout reports success; a Lightweight Changes row, or its `Commit` cell,
677
+ deleted — archiving an effectively empty host.
678
+ The closeout row's Result must also **not** be `failed`: you are reading a
679
+ commit that landed, so `failed` contradicts reality (`committed` on the Y
680
+ path, `skipped` when the developer declined and committed it themselves).
681
+ **Minimal host (zero-phase) additionally**: one further condition on the
682
+ first checkpoint row, stated in
683
+ `references/finish-feature-minimal-host.md` § Step 4.
684
+ - [ ] **The closeout commit contains only what closeout is allowed to write.**
685
+ Read its changed paths (`git show --stat HEAD`) and admit **only** the
686
+ archived host directory — the `git mv` rename plus the finalization
687
+ Step 1 validated — **plus exactly the external paths Step 4 instruction 2
688
+ ordered staged**. That is the same derivation the item above uses, applied
689
+ to the other half of "closeout is clean": the permitted set comes from the
690
+ instruction that ordered the staging, never from a copy kept here.
691
+ **Anything else blocks**: implementation source, or a
692
+ `domain/{context}/…` document under a host that declared itself no-BC.
693
+ ⚠ **That no-BC half applies to every host shape, and on a phase-bearing
694
+ host this is the only place closeout tests it.** The
695
+ `Minimal host (zero-phase), no-BC only` check in
696
+ `references/finish-feature-minimal-host.md` reads checkpoint 1, and a
697
+ phase-bearing host has no checkpoint 1 to read — so do not treat "the
698
+ minimal-host branch file already covers no-BC" as true here. It is true
699
+ only for a minimal host.
700
+ **Scope: this is a path-level spill check and nothing more.** It proves no
701
+ unpermitted *file* entered the commit — not that the permitted ones carry
702
+ only this host's delta. Judging a hunk inside `rules.md` as "this host's
703
+ change rather than an unrelated BR edit" needs the intended delta, which
704
+ lives in the spec and the row, not in a path list. The item that reads one
705
+ against the other is `references/pr-review-checklist.md`'s
706
+ **"The closeout commit carries only this host's delta"**.
707
+ Say that plainly rather than implying the stronger claim.
708
+ ⚠ **And its span is this one commit.** Between them, the minimal-host
709
+ branch file's check (a minimal host's checkpoint 1) and this one (any
710
+ host's closeout commit)
711
+ still leave every *other* commit on the branch unread — where a no-BC host
712
+ is concerned, that gap is closed by
713
+ `references/pr-review-checklist.md`'s **"A no-BC host committed no
714
+ BC-scoped Domain material"** item, which takes the branch range for
715
+ **every** no-BC host.
258
716
  - [ ] `dflow/specs/features/active/{SPEC-ID}-{slug}/` no longer exists (the
259
717
  directory was moved, not copied)
260
718
  - [ ] `git status --short` shows no leftovers related to this feature
261
719
  (working tree clean; identify any unrelated dirty files explicitly)
262
720
 
263
- If any item fails, do **not** declare closeout complete — fix it (re-add and
264
- amend, or a follow-up commit; the developer chooses) and re-verify.
721
+ **Once every item above reads `✓`, release the baseline anchor:**
722
+
723
+ ```bash
724
+ git update-ref -d refs/dflow/closeout-baseline/{SPEC-ID}-{slug}
725
+ ```
726
+
727
+ ⚠ **Only after they all pass** — a failed item is fixed and re-verified against
728
+ this same evidence, so the anchor has to survive it.
729
+ ⚠ **Deleting the ref is not a worktree edit**, so it is not a difference the
730
+ item above could have seen.
731
+
732
+ If any item fails, do **not** declare closeout complete — fix it and re-verify.
733
+ **How you may fix it depends on the host.** A **phase-bearing** feature has no
734
+ fixed commit count, so re-add and amend *or* a follow-up commit both work; the
735
+ developer chooses. A **minimal (zero-phase) host** does not: its lifecycle is
736
+ exactly checkpoint 1 plus closeout, and every check its branch file adds rests
737
+ on that, so the
738
+ repair must go **into the closeout commit itself** — re-add and amend. Nothing
739
+ has been pushed yet (integration is Step 5), so amending is safe here. A
740
+ follow-up commit would give the host a third host-mutating commit while its
741
+ ledger still claims two; the Step 6 flip is the *only* sanctioned
742
+ post-closeout commit, and it touches the original feature, not this host.
265
743
 
266
744
  The Local-closeout gate is satisfied **only when the closeout is committed and
267
745
  the verification above passes**. If you declined the commit (chose N) or it
@@ -277,7 +755,32 @@ offline; integration happens in Step 5 when you have network.
277
755
 
278
756
  ## Step 5: Emit Integration Summary (Git-strategy-neutral)
279
757
 
280
- Produce a plain-text summary of what this feature did. The summary is
758
+ **First, print the closeout verification's derivation.** Before the summary
759
+ itself, state in the conversation *how* Step 4's post-commit verification
760
+ reached its result — not that it passed, but how it was computed:
761
+
762
+ 1. **The baseline** — the tree Step 1 read, and which step put each spec's
763
+ `status: completed` flip into it (Step 1.7's finalization on a minimal host;
764
+ the phases completing on a phase-bearing one, which never runs Step 1.7).
765
+ 2. **The differences you derived, each with the step that ordered it** —
766
+ Step 2, Step 4's terminal Resume Pointer write, or Step 4 instruction 1. This
767
+ is the set the check *derives* instead of listing, so printing it is what
768
+ lets a reader check the derivation rather than trust it. Say as well that the
769
+ comparison reads final net state, so the transitional cursor values Step 2
770
+ and gate 3 → 4 wrote are outside what it can show.
771
+ 3. **The external paths admitted, and which half of Step 4 instruction 2 each
772
+ came from** — what Step 3 wrote, or this change's own sweep.
773
+ 4. **What the check could not decide, and who holds it** — for a backfilled
774
+ hosted `Commit` cell, that closeout cannot decide whether the value is that
775
+ row's own implementation hash, and that
776
+ `references/pr-review-checklist.md`'s **"Hosted `Commit` cell identity"**
777
+ item is where it gets confirmed (not its presence check). Say this **even
778
+ when there was nothing to backfill**, so its absence is visible rather than
779
+ silent.
780
+ 5. **Any check that passed on a recorded exception** — e.g. a branch mismatch
781
+ honoured by a `branch-override` row, naming the row and the branch.
782
+
783
+ Then produce a plain-text summary of what this feature did. The summary is
281
784
  **not** a commit message template — it is reference material the
282
785
  developer adapts to whichever merge strategy their project uses
283
786
  (merge commit, squash, rebase, fast-forward — Dflow stays neutral).
@@ -322,17 +825,70 @@ Next Steps (developer) — Integration / PR gate (needs network):
322
825
  you, but only when you explicitly ask; it never pushes on its own
323
826
  ```
324
827
 
828
+ **Zero-phase minimal host — exact fields.** A zero-phase host does not fill the
829
+ format above the way a phase-bearing one does. Its whole field set, and the
830
+ authority on that shape, is `references/finish-feature-minimal-host.md`
831
+ § Step 5.
832
+
325
833
  Print the summary to the conversation; do not write it to a file (it is
326
834
  ephemeral closeout output).
327
835
 
328
- **→ Step Gate: Step 5 → Step 6**
329
-
330
- If the feature has `follow-up-of: {原 SPEC-ID}` in its Metadata, prompt
331
- the developer:
332
- > "This feature is a follow-up of `{原 SPEC-ID}`. Ready to update the
333
- > original feature's `_index.md` Follow-up Tracking row to mark this
334
- > follow-up as `completed`? `/dflow:next` to proceed (or skip if you
335
- > prefer to do it manually)."
836
+ **A mainline hotfix that overlapped this feature.** Step 4 has already archived
837
+ and committed this host — **it is frozen**, so anything a merge with the
838
+ mainline leaves over is routed elsewhere and never recorded back into it. What
839
+ is left over, how to classify it, and where each class goes are decided in
840
+ `references/finish-feature-post-hoc-hotfix.md` § After closeout — the same
841
+ branch file Step 1's hotfix callout dispatches to.
842
+
843
+ **→ Post-Local-closeout confirmation: Step 5 → Step 6**
844
+
845
+ **This stops and waits, and it is not a step gate.** By now the host is in
846
+ `completed/` and its cursor reads `none`. **Confirming here updates no cursor**,
847
+ and nothing below re-opens the workflow.
848
+
849
+ If the feature has `follow-up-of` in its Metadata, prompt the developer —
850
+ naming **every** original it lists (the field may be a YAML array):
851
+ > "This feature is a follow-up of `{原 SPEC-ID}` (and `{原 SPEC-ID-2}`, …).
852
+ > Ready to update **each** original feature's `_index.md` Follow-up Tracking row
853
+ > to mark this follow-up as `completed`? (Or tell me you'll do it manually —
854
+ > either way this tracking commit is **required** before closeout is complete;
855
+ > see Step 6.)"
856
+
857
+ **What this confirmation accepts** — this governs the follow-up prompt above; a
858
+ host with no `follow-up-of` never reaches it and takes the branch below instead:
859
+
860
+ - **Any affirmative reply → enter Step 6.** The verbal signals the guide lists
861
+ (`AI-AGENT-GUIDE.md` § Confirmation Signals) are illustrations, **not the
862
+ list**: a reply that plainly means "go ahead" counts however it is worded.
863
+ **Implicit confirmation counts too** — a developer who supplies what Step 6
864
+ needs, by naming the originals or by saying they will make the commit
865
+ themselves, has confirmed, not declined.
866
+ - **`/dflow:next` and `/dflow:cancel` do not apply here.** No workflow is
867
+ active, and the guide requires both to be refused in that state. Refuse as the
868
+ guide says, **then ask this question again in plain language**. ⚠ **Do not
869
+ read either as a decline** — every earlier gate in this flow asked for
870
+ `/dflow:next`, so typing it here is a trained reflex, not an answer.
871
+ - **A request to change something first** — revise what was asked about, then
872
+ ask again. Nothing already committed is undone, and the Local-closeout gate
873
+ stays satisfied.
874
+ - **A plain decline, or "not now" — stop, and do not ask again.** There is
875
+ nothing to revise, so re-asking is the same question and reads as pressure.
876
+ Report that closeout is **not complete**, name the Step 6 flip as what is
877
+ still owed, and leave restarting to the developer.
878
+
879
+ ⚠ **Declining does not waive the tracking commit** — Step 6's flip is required
880
+ either way. `references/finish-feature-follow-up.md` holds that rule and states
881
+ what it takes to satisfy it.
882
+
883
+ **The reverse link must have been opened, not only closed — and that is not
884
+ minimal-host-only.** This confirmation point puts no host shape on
885
+ `follow-up-of`, so a phase-bearing host can carry it too, but it has no single
886
+ commit required to carry the row. The requirement still holds for that host; nothing in closeout
887
+ tests it. **The opening half is confirmed for a phase-bearing follow-up host by
888
+ `references/pr-review-checklist.md`'s "A follow-up's reverse link was opened,
889
+ not only closed" item**, in that file's *Delegated to review by
890
+ `finish-feature-flow.md`* block — it reads the branch history, which is exactly
891
+ what closeout cannot take.
336
892
 
337
893
  If no `follow-up-of` field, skip Step 6 and announce closeout complete:
338
894
  > "`/dflow:finish-feature` complete for `{SPEC-ID}-{slug}`. Feature
@@ -344,29 +900,9 @@ If no `follow-up-of` field, skip Step 6 and announce closeout complete:
344
900
  **In-flight reminder** — after the closeout announcement (with or without
345
901
  Step 6), run the in-flight overview scan (see `AI-AGENT-GUIDE.md` § Status /
346
902
  Control Commands) and list any other unfinished features in `active/` and any
347
- in-flight feature / bugfix branches. Surfacing them at closeout is deliberate:
348
- attention is about to move elsewhere, and this is exactly where half-done work
349
- sinks.
903
+ in-flight feature / bugfix branches.
350
904
 
351
905
  ## Step 6: Reverse-Update Follow-up Tracking (only if follow-up)
352
906
 
353
- For features that were created as follow-ups of an earlier completed
354
- feature, update the original feature's
355
- Follow-up Tracking table.
356
-
357
- 1. Locate `dflow/specs/features/completed/{原 SPEC-ID}-{原 slug}/_index.md`
358
- 2. Find the Follow-up Tracking section's row for this feature's SPEC-ID
359
- 3. Flip Status → `completed`
360
-
361
- ```bash
362
- # The AI makes the edit and may offer to commit it (Y / N), per the AI commit policy
363
- ```
364
-
365
- After the update:
366
- > "Follow-up Tracking row in `{原 SPEC-ID}-{原 slug}/_index.md` updated
367
- > to Status = `completed`. Closeout complete."
368
-
369
- The connection is bidirectional and weakly redundant: the new feature's
370
- `follow-up-of` field is the authoritative source; the old feature's
371
- Follow-up Tracking row is a derived index. If they ever disagree, trust
372
- `follow-up-of`.
907
+ **Open `references/finish-feature-follow-up.md` and follow it there.** This
908
+ step's rules live in that file and are not repeated here.