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