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