dflow-sdd-ddd 0.15.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 (41) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/CONTRIBUTING.md +1 -0
  3. package/README.en.md +7 -4
  4. package/README.md +7 -4
  5. package/bin/dflow.js +75 -0
  6. package/docs/closeout-check.en.md +204 -0
  7. package/docs/closeout-check.md +204 -0
  8. package/docs/commands.en.md +4 -2
  9. package/docs/commands.md +4 -2
  10. package/docs/release-versioning-policy.md +1 -0
  11. package/lib/closeout-check.js +690 -0
  12. package/lib/doc-shapes.json +198 -0
  13. package/lib/init.js +17 -2
  14. package/package.json +2 -2
  15. package/templates/brownfield/references/finish-feature-flow.md +29 -26
  16. package/templates/brownfield/references/finish-feature-minimal-host.md +38 -17
  17. package/templates/brownfield/references/finish-feature-record-correction.md +45 -0
  18. package/templates/brownfield/references/git-integration.md +62 -28
  19. package/templates/brownfield/references/modify-existing-flow.md +55 -29
  20. package/templates/brownfield/references/modify-existing-follow-up.md +2 -1
  21. package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +121 -3
  22. package/templates/brownfield/references/new-feature-flow.md +49 -31
  23. package/templates/brownfield/references/new-phase-flow.md +2 -2
  24. package/templates/brownfield/references/pr-review-checklist.md +29 -4
  25. package/templates/brownfield/scaffolding/Git-principles-gitflow.md +13 -5
  26. package/templates/brownfield/scaffolding/Git-principles-trunk.md +8 -4
  27. package/templates/brownfield/templates/_index.md +17 -13
  28. package/templates/common/references/flow-rationale-registry.md +20 -4
  29. package/templates/greenfield/references/finish-feature-flow.md +29 -26
  30. package/templates/greenfield/references/finish-feature-minimal-host.md +36 -16
  31. package/templates/greenfield/references/finish-feature-record-correction.md +45 -0
  32. package/templates/greenfield/references/git-integration.md +63 -29
  33. package/templates/greenfield/references/modify-existing-flow.md +42 -27
  34. package/templates/greenfield/references/modify-existing-follow-up.md +2 -1
  35. package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +121 -3
  36. package/templates/greenfield/references/new-feature-flow.md +49 -31
  37. package/templates/greenfield/references/new-phase-flow.md +2 -2
  38. package/templates/greenfield/references/pr-review-checklist.md +28 -4
  39. package/templates/greenfield/scaffolding/Git-principles-gitflow.md +13 -5
  40. package/templates/greenfield/scaffolding/Git-principles-trunk.md +8 -4
  41. package/templates/greenfield/templates/_index.md +18 -13
@@ -177,7 +177,8 @@ project's decision (GitFlow → `develop`, Trunk / GitHub Flow → `main`).
177
177
  ### Commit checkpoints
178
178
 
179
179
  At lifecycle milestones the AI offers a commit checkpoint, folded into the
180
- existing Step Gate prompt (it does not add a separate question):
180
+ prompt the flow already shows there — a Step Gate, or the flow's closing
181
+ announcement (it does not add a separate question):
181
182
 
182
183
  ```
183
184
  ✓ {milestone} complete
@@ -188,25 +189,36 @@ existing Step Gate prompt (it does not add a separate question):
188
189
 
189
190
  Tier sets how many checkpoints a change has: T1 three (spec / implementation /
190
191
  closeout), T2 two (spec+implementation merged / closeout), T3 a single commit.
191
- "T3 = a single commit" means a single **implementation** commit — its hash is
192
- only knowable afterwards, so the `_index.md` inline row and checkpoint row are
193
- swept up by the host's **next** commit (more work, or closeout). If the host has
194
- no next commit scheduled, one ledger-only tracking commit that references the T3
195
- hash is allowed. It is bookkeeping, not a lifecycle milestone: it adds no
196
- Checkpoint Log row of its own (unlike closeout, which is a checkpoint and does get
197
- a row).
192
+ **What a checkpoint carries follows from where its flow offers it.**
193
+ `references/modify-existing-flow.md` offers its checkpoint at the end of its
194
+ artifact step (Step 5), so a T2's merged commit — and a T3's single commit —
195
+ carries the change together with the documentation that step writes.
196
+ `/dflow:new-feature` and `/dflow:new-phase` offer the implementation checkpoint
197
+ at the gate into their completion step, so what that step writes rides the
198
+ host's next commit, whichever that is — for example the next phase's first
199
+ checkpoint, a hosted lightweight change's commit, or closeout.
200
+ "T3 = a single commit" means a single **implementation** commit. On a hosted T3
201
+ the entries that carry its hash ride the host's **next** commit (more work, or
202
+ closeout), as every checkpoint's do (below). If the host has no next commit
203
+ scheduled, one ledger-only tracking commit that references the T3 hash is
204
+ allowed. It is bookkeeping, not a lifecycle milestone: it adds no Checkpoint Log
205
+ row of its own (unlike closeout, which is a checkpoint and does get a row).
198
206
  **Minimal host (zero-phase) exception — there, tier does not set the count.**
199
207
  On a minimal host (`references/modify-existing-flow.md` Step 1.7 standalone, or
200
208
  its Step 1.6 follow-up minimal variant) there is no later host commit for a row
201
209
  to ride, so the count is **two for every tier** — implementation, then closeout
202
- — and a T3's `_index.md` row is written into checkpoint 1 itself instead of
203
- sweeping into a later commit. Everything above about "T3 = a single commit"
204
- describes a **hosted** T3, the case where a later host commit exists.
210
+ — and every row, a T3's included, is written into checkpoint 1 itself; only the
211
+ entries that carry checkpoint 1's hash ride the closeout commit. Everything
212
+ above about "T3 = a single commit" describes a **hosted** T3, the case where a
213
+ later host commit exists.
205
214
  Whether you choose Y or N, the AI records one row in the feature `_index.md`
206
215
  Checkpoint Log — every checkpoint is accounted for (`committed` / `skipped` /
207
- `failed`), even when no commit happens. A commit hash is written only after the
208
- commit succeeds; a hook rejection or failed commit is recorded as `failed`
209
- (never a fake hash).
216
+ `failed`), even when no commit happens.
217
+ **No checkpoint commit can contain its own hash.** The entries that carry a
218
+ checkpoint's hash — its Checkpoint Log Result `committed ({hash})`, and any
219
+ Lightweight Changes `Commit` cell holding that hash — are written after the
220
+ commit succeeds and ride the host's **next** commit. A hook rejection or failed
221
+ commit is recorded as `failed` (never a fake hash).
210
222
  **One further Result value — `reconciled ({merged-hotfix-hash})`.** It records
211
223
  that this checkpoint documents a change that was **already merged** before any
212
224
  ceremony ran, and it is written only on a post-hoc hotfix host's implementation
@@ -215,8 +227,8 @@ belongs to the **merged hotfix**, not to this host's own documentation commit
215
227
  that one goes in the Lightweight Changes row's `Commit` cell, and the two have
216
228
  different provenance and must never be swapped. On every other host the three
217
229
  values above are the whole vocabulary.
218
- **Exception — the closeout row**: the closeout commit
219
- cannot contain its own hash, so the closeout row is written before the commit
230
+ **Exception — the closeout row**: no later commit of this host's lifecycle
231
+ carries it, so the closeout row is written before the commit
220
232
  as `closeout | committed` with **no hash** (see
221
233
  `references/finish-feature-flow.md` Step 4); trace that commit via
222
234
  `git log -1 -- dflow/specs/features/completed/{SPEC-ID}-{slug}` or the optional
@@ -339,12 +351,25 @@ heavily enough that git's similarity index dropped below the rename
339
351
  threshold; consider using `git mv` for the move, then making content
340
352
  edits in a follow-up commit).
341
353
 
342
- ### CI / hook automation (future)
354
+ ### CI / hook automation
343
355
 
344
- A pre-commit hook can refuse commits where `dflow/specs/features/active/` or
345
- `dflow/specs/features/completed/` show paired `D` + `A` instead of `R` for
346
- the same feature directory. Not part of Dflow today, but compatible
347
- with the rule.
356
+ Dflow ships `dflow check-closeout`, a gate for the closeout commit. For each
357
+ feature directory newly archived to `dflow/specs/features/completed/` it checks
358
+ that `_index.md` and every spec say `status: completed`, that nothing is left
359
+ under `active/`, that the Checkpoint Log has a real `closeout` row reading
360
+ `committed` or `skipped`, and — with `--staged` — that nothing under the
361
+ archived directory is left unstaged; it exits 1 when one fails. A project wires
362
+ it into a pre-commit hook (`--staged`) or a CI job (`--range <base>..<head>`)
363
+ itself — Dflow installs neither. `dflow check-closeout --help` names the page
364
+ with the hook and CI templates. When the hook refuses a closeout commit, handle
365
+ it as `references/finish-feature-flow.md` Step 4 instruction 3 says.
366
+
367
+ Passing it is not a clean closeout: the post-commit verification in
368
+ `references/finish-feature-flow.md` Step 4 still applies in full.
369
+
370
+ A hook that refuses paired `D` + `A` instead of `R` for the same feature
371
+ directory is compatible with the `git mv` rule, but it is not part of
372
+ `check-closeout`.
348
373
 
349
374
  ## Gate Checks by Branch Type
350
375
 
@@ -384,8 +409,12 @@ hotfix, or one added later):
384
409
  - [ ] `_index.md` Current BR Snapshot has been synced to BC layer
385
410
  (`rules.md` / `behavior.md` / `events.md` / `context-map.md`) —
386
411
  typically by `/dflow:finish-feature`
387
- - [ ] Whole feature directory ready to `git mv` to `dflow/specs/features/completed/`
388
- (or already moved if `/dflow:finish-feature` ran)
412
+ - [ ] **Closeout complete**: `/dflow:finish-feature` has announced closeout
413
+ complete for this host — its Local-closeout gate is satisfied (the
414
+ closeout commit landed and passed post-commit verification) and, if
415
+ the host carries `follow-up-of`, the Step 6 tracking commit has also
416
+ landed and been verified. `status: completed` or a directory already
417
+ under `completed/` does not show this on its own.
389
418
  - [ ] Domain layer: zero external dependencies
390
419
  - [ ] No business logic outside Domain layer
391
420
  - [ ] Domain Events documented in events.md
@@ -394,8 +423,8 @@ hotfix, or one added later):
394
423
  - [ ] No ORM/serialization attributes on Domain entities
395
424
 
396
425
  > **On a minimal (zero-phase) host**, split this list by what each item reads.
397
- > Items about **this host's own record** — `_index.md` status, archival
398
- > readiness, and the phase-spec check, which passes vacuously on an empty
426
+ > Items about **this host's own record** — `_index.md` status, **Closeout
427
+ > complete**, and the phase-spec check, which passes vacuously on an empty
399
428
  > table — apply **unchanged**: they are what stops a host merging with its
400
429
  > record still open. Every item that names a **Domain or bounded-context
401
430
  > artifact**, the BR-Snapshot sync included, applies only to what this change
@@ -440,10 +469,15 @@ hotfix, or one added later):
440
469
  - [ ] Fix approach is noted (which layer the fix belongs in)
441
470
 
442
471
  ### bugfix/ — Before Merging (Pre-PR / Pre-Integration)
443
- - [ ] `_index.md` status: `completed`, and the whole feature directory ready to
444
- `git mv` to `dflow/specs/features/completed/` (or already moved if
445
- `/dflow:finish-feature` ran). A `bugfix/` host closes out exactly like a
446
- `feature/` one; an un-closed-out host must not merge.
472
+ - [ ] `_index.md` status: `completed`
473
+ - [ ] **Closeout complete**: `/dflow:finish-feature` has announced closeout
474
+ complete for this host — its Local-closeout gate is satisfied (the
475
+ closeout commit landed and passed post-commit verification) and, if
476
+ the host carries `follow-up-of`, the Step 6 tracking commit has also
477
+ landed and been verified. `status: completed` or a directory already
478
+ under `completed/` does not show this on its own.
479
+ A `bugfix/` host closes out exactly like a `feature/` one; an
480
+ un-closed-out host must not merge.
447
481
  - [ ] **The tier's artifact** has the fix documented — a **T2**'s lightweight
448
482
  spec, or for a **T3** the host `_index.md` Lightweight Changes row, which
449
483
  is its whole record. Do not ask a T3 for a spec file here either: the
@@ -286,6 +286,10 @@ drop the T3 evidence.
286
286
  recorded, or a stale branch is still carrying it — and it is settled that
287
287
  way with the developer, as duplicate / existing work or branch-history
288
288
  cleanup. All identifiers are settled here, before the host exists.
289
+
290
+ For a **post-hoc hotfix** (Step 1.8), also run the branch-base check in
291
+ `references/modify-existing-post-hoc-hotfix.md` item 2 here, before step 3
292
+ creates anything: step 4 cuts from the base branch it settles.
289
293
  3. **Create the minimal `_index.md`** from `templates/_index.md` at
290
294
  `dflow/specs/features/active/{SPEC-ID}-{slug}/_index.md`, carrying all
291
295
  **seven required sections** that template names — the Metadata front
@@ -329,9 +333,10 @@ skips the Domain / DDD-impact work and records an inline row in
329
333
  `_index.md` Lightweight Changes — **one row per atomic T3 change**, so a
330
334
  compound request records each of them here (see "minimal means zero-phase,
331
335
  not one-artifact" above). The **implementation commit is checkpoint
332
- 1** — the Step 4 → Step 5 commit checkpoint records it in the Checkpoint
333
- Log. (For a **post-hoc** host, Step 4 is skipped and checkpoint 1 is the
334
- **documentation** commit instead; see Step 1.8 and Step 4's guard.)
336
+ 1** — offered at the end of Step 5 (§ 5.5), after Step 5 has written its
337
+ record, and recorded in the Checkpoint Log. (For a **post-hoc** host, Step 4 is
338
+ skipped and checkpoint 1 is the **documentation** commit instead; see Step 1.8
339
+ and Step 4's guard.)
335
340
 
336
341
  **Every artifact gets a Lightweight Changes row, and every row is written
337
342
  *before* checkpoint 1.** A **T3**'s row *is* its whole record; a **T2**'s row is
@@ -341,8 +346,8 @@ implementation paths. The **only** part that may appear afterwards is the
341
346
  `Commit` cell, which cannot exist until its commit does: closeout's allow-list
342
347
  admits that cell and no other change **to the row**, so a row added after
343
348
  checkpoint 1 **blocks**. (The allow-list separately admits the Checkpoint Log
344
- rows, the Resume Pointer and the Current BR Snapshot — the finalization edits
345
- this step makes next.)
349
+ rows, the Resume Pointer and the Current BR Snapshot; the first two are what
350
+ "Finalize + close" writes after checkpoint 1.)
346
351
 
347
352
  **Name the implementation paths in the artifact.** Closeout's evidence checks
348
353
  assert that a commit *touches the implementation paths this change describes* —
@@ -353,14 +358,17 @@ diff — enough to compare a commit against. An artifact that declares none
353
358
  leaves that check nothing to compare, and finish-feature treats a missing
354
359
  declaration as a **block**, not a pass.
355
360
 
356
- **Finalize + close.** Before `/dflow:finish-feature`, run the Step 5
357
- completion checklist's minimal-host finalization. For **every** T2: set the
358
- lightweight-spec `status: completed`, record its commit evidence, and advance
359
- the Resume Pointer to the closeout-ready state. **Only when that T2 is
360
- BC-bearing**, additionally refresh the host's Current BR Snapshot — a no-BC T2
361
- has no snapshot to refresh, but still owes the commit evidence and the Resume
362
- Pointer. A **T3** has no spec file — record its inline row's commit evidence
363
- and advance the Resume Pointer.
361
+ **Before checkpoint 1 — Step 5 finalizes the record.** Step 5's completion
362
+ checklist runs before checkpoint 1, so everything it writes rides that commit.
363
+ For **every** T2, 5.3 sets the lightweight-spec `status: completed`. **Only when
364
+ that T2 is BC-bearing**, 5.3 also refreshes the host's Current BR Snapshot — a
365
+ no-BC T2 has no snapshot to refresh. A **T3** has no spec file and no snapshot
366
+ to refresh; Step 5 only re-verifies its row.
367
+
368
+ **Finalize + close — after checkpoint 1.** Before `/dflow:finish-feature`,
369
+ write what cannot exist until checkpoint 1 does, for every tier: each row's
370
+ commit evidence (the two surfaces below) and the Resume Pointer advanced to the
371
+ closeout-ready state.
364
372
 
365
373
  **Commit evidence goes to two surfaces, always — every row, every tier.** They
366
374
  answer different questions, so filling one is never filling the other, and
@@ -377,16 +385,18 @@ The **mode** changes only *which hash* each surface carries:
377
385
 
378
386
  - **Normal** (T2 or T3) — Checkpoint Log Result `committed ({hash})` and the
379
387
  row's `Commit` cell both name **checkpoint 1**, the single commit that
380
- carried the artifact and the implementation together. Two cells, one hash;
381
- fill both.
388
+ carried the artifact, the implementation and Step 5's record together. Two
389
+ cells, one hash; fill both.
382
390
  - **Post-hoc hotfix** (Step 1.8; T2 or T3) — the Checkpoint Log Result stays
383
- `reconciled ({merged-hotfix-hash})`, **never overwrite it**, while the row's
384
- `Commit` cell carries the **documentation** commit's hash. This is the one
385
- mode where the two values genuinely differ (Step 1.8 item 4).
391
+ `reconciled ({merged-hotfix-hash})`, **never overwrite it** — only closeout
392
+ corrects it, through `references/modify-existing-post-hoc-hotfix.md` item 6 —
393
+ while the row's `Commit` cell carries the **documentation** commit's hash.
394
+ This is the one mode where the two values genuinely differ (Step 1.8 item 4).
386
395
 
387
396
  **A declined checkpoint still owes its hash.** If the developer answered **N**
388
- at the checkpoint-1 offer and made that commit themselves, the Checkpoint Log
389
- row was written `skipped` honestly — the AI did not commit — but it is now
397
+ at the checkpoint-1 offer and made that commit themselves — the change together
398
+ with Step 5's record — the Checkpoint Log row was written `skipped` honestly —
399
+ the AI did not commit — but it is now
390
400
  **incomplete, not final**: complete it here to `committed ({hash})` with the
391
401
  developer's actual hash. **That completion is for a normal checkpoint 1.** On a
392
402
  **post-hoc** host the Result stays
@@ -396,7 +406,8 @@ developer's commit lands, never by overwriting the reconciliation hash, which
396
406
  records a different commit entirely. A row left reading `skipped` blocks closeout, and
397
407
  finish-feature cannot tell "declined then committed" from "never committed" on
398
408
  your behalf. The **closeout** row is the deliberate exception — it carries no
399
- hash at all, because a commit cannot contain its own. Its **Result is still
409
+ hash at all, because no later commit of this host carries it
410
+ (`references/git-integration.md` § Commit checkpoints). Its **Result is still
400
411
  read** afterwards — finish-feature's post-commit gate rejects `failed` there. A
401
412
  declined closeout stays `skipped` because that value records the declined offer
402
413
  honestly, not because the row goes unread.
@@ -569,9 +580,10 @@ Wait for confirmation before entering Step 4.
569
580
  > **Post-hoc hotfix (Step 1.8) — skip this step.** The implementation already
570
581
  > landed on the mainline before any ceremony ran; that is the whole premise of
571
582
  > post-hoc mode, and Step 1.8 item 2 forbids redoing it. There is nothing to
572
- > implement here. Go straight to the Step 4 → Step 5 gate and commit
573
- > **documentation only** — the T2 spec or T3 row plus this host's `_index.md`,
574
- > and no source. Re-implementing the fix on this branch would satisfy the letter
583
+ > implement here. Go straight to the Step 4 → Step 5 gate and on through Step 5;
584
+ > checkpoint 1 (§ 5.5) then commits **documentation only** — the T2 spec or T3
585
+ > row, this host's `_index.md` and what Step 5 wrote, and no source.
586
+ > Re-implementing the fix on this branch would satisfy the letter
575
587
  > of the checkpoint while breaking §8's reconciliation contract, which is why
576
588
  > closeout inspects that commit's changed paths.
577
589
 
@@ -589,9 +601,7 @@ Even for bug fixes, verify:
589
601
  Announce to developer:
590
602
  > "Implementation appears complete. Ready to update documentation (spec, models.md, rules.md, events.md, glossary, tech-debt)? `/dflow:next` to proceed."
591
603
 
592
- > Commit checkpoint (per `references/git-integration.md` § Commit Checkpoints, Branch Gate & AI Commits): offer to commit, then record the result in the `_index.md` Checkpoint Log. Tier sets the count — T2 commits the merged spec+implementation here (closeout is the second checkpoint); T3 is a single implementation commit, and its `_index.md` rows ride along on the host's next commit (see `references/git-integration.md` § Commit checkpoints). **Minimal host exception (Step 1.7 standalone *and* the Step 1.6 follow-up variant)**: a minimal host has no later host commit for a T3 row to ride along on, so **the row rides checkpoint 1 itself** — write it into `_index.md` *before* this commit, alongside the implementation, because finish-feature Step 1 reads **checkpoint 1's committed `_index.md`** for it. Only what cannot exist until the commit does — the row's `Commit` cell and the Checkpoint Log Result — is backfilled later and rides the closeout commit. And a minimal host takes a second **closeout** checkpoint too: **every** minimal host records **two** checkpoints (implementation, then closeout), for T3 as well as T2. **Post-hoc hotfix (Step 1.8) — the same two checkpoints, but the first carries no implementation**: the fix is already on the mainline, so checkpoint 1 is the **documentation** commit (spec / row + `_index.md`, no source), and its Checkpoint Log Result is `reconciled ({merged-hotfix-hash})` rather than `committed`. "Commits the merged spec+implementation" above describes a normal host and does **not** apply here.
593
-
594
- Wait for confirmation before entering Step 5. This step gate is where the completion checklist is triggered — do not skip.
604
+ Wait for confirmation before entering Step 5. This step gate is where the completion checklist is triggered — do not skip. It offers **no** commit: checkpoint 1 is offered at the end of Step 5 (§ 5.5).
595
605
 
596
606
  ## Step 5: Update Documentation
597
607
 
@@ -632,6 +642,7 @@ Ask these one-by-one.
632
642
  > **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).
633
643
 
634
644
  - [ ] Update or create the feature / bug spec; set `status: completed` — **T3: N/A** (no spec file exists; the `_index.md` inline row is the record, and the host's own status is not touched)
645
+ - [ ] **BC-bearing T2, on every host** — refresh the host `_index.md` Current BR Snapshot from this spec's final delta (`templates/_index.md` regenerates it when a T2 lightweight-spec is finalized; on a minimal host this is the refresh Step 1.7 places before checkpoint 1). A spec with no BR delta leaves the Snapshot as it is. N/A for a no-BC T2 and a T3
635
646
  - [ ] The items below are the Domain sweep — **N/A for a T3**. For a **no-BC change** (one whose host Goals & Scope says it touches no bounded context) the **BC-scoped** items are N/A — everything under `dflow/specs/domain/{context}/` plus `context-map.md`: there is no `{context}` to sweep, and inventing one plants the fiction Step 2's no-BC guard refuses. The **global** documents are *not* covered by that: `glossary.md`, `domain/analysis.md` and `architecture/tech-debt.md` belong to no bounded context, and a no-BC operational T2 can genuinely rename a term, change which roles reach a function, or discover architecture debt — judge those three from the actual change, as always. For a no-BR family T2 only the *BR-derived* items are N/A; run each remaining item where this change actually touches that document (an added event field still lands in `events.md`)
636
647
  - [ ] `dflow/specs/domain/{context}/models.md` — Aggregate structure updates
637
648
  - [ ] `dflow/specs/domain/{context}/analysis.md` — lifecycles, derived figures or mechanisms no single rule explains this change found or altered, in the owning context's copy, which may not be this host's; any spot this change worked around pending a domain decision, in those or in a rule or unrecorded knowledge that belongs there; and any open row there this change settled (created from `templates/analysis.md` the first time there is something to record; N/A when there is none)
@@ -678,3 +689,7 @@ directory — the closeout `git mv` happens at `/dflow:finish-feature` time
678
689
 
679
690
  Only announce "change complete" after the appropriate archival step
680
691
  above (or the Step 5.3 docs sweep) is done.
692
+
693
+ ### 5.5 Commit checkpoint (checkpoint 1)
694
+
695
+ > Commit checkpoint (per `references/git-integration.md` § Commit Checkpoints, Branch Gate & AI Commits): fold the offer into the "change complete" announcement, then record the result in the `_index.md` Checkpoint Log. This commit is **checkpoint 1**: it carries the change — the spec or row plus the implementation — **and** everything Steps 2–5 wrote. Tier sets the count — T2 commits the merged spec+implementation here (closeout is the second checkpoint); T3 is a single implementation commit. The entries that carry this commit's hash — the Checkpoint Log Result and each Lightweight Changes `Commit` cell naming it — are written after the commit and ride the host's next commit; on a phase-bearing host, `/dflow:finish-feature` Step 4 backfills any `Commit` cell still unfilled at closeout. **Minimal host exception (Step 1.7 standalone *and* the Step 1.6 follow-up variant)**: a minimal host takes no host commit between checkpoint 1 and closeout, so **every row rides checkpoint 1 itself** — a T3 row included, written into `_index.md` *before* this commit, because finish-feature Step 1 reads **checkpoint 1's committed `_index.md`** for it. Only the entries that carry the hash come later: Step 1.7's "Finalize + close" writes them, and they ride the closeout commit. And a minimal host takes a second **closeout** checkpoint too: **every** minimal host records **two** checkpoints (implementation, then closeout), for T3 as well as T2. **Post-hoc hotfix (Step 1.8) — the same two checkpoints, but the first carries no implementation**: the fix is already on the mainline, so checkpoint 1 is the **documentation** commit (spec / row, `_index.md` and what Step 5 wrote, no source), and its Checkpoint Log Result is `reconciled ({merged-hotfix-hash})` rather than `committed`. "Commits the merged spec+implementation" above describes a normal host and does **not** apply here.
@@ -87,7 +87,8 @@ hardcoded `feature/` branch). Step 1.7's **step 1 does not apply**: it is the
87
87
  standalone-classification gate, and this caller has already been classified as
88
88
  a follow-up. Everything Step 1.7 states **after** step 4 *does* apply
89
89
  unchanged — every artifact's row written before checkpoint 1, the
90
- implementation-path declaration, tier-aware "Finalize + close", and the two
90
+ implementation-path declaration, the finalization before checkpoint 1 and
91
+ "Finalize + close" after it, and the two
91
92
  commit-evidence surfaces — because finish-feature reads for all of it on a
92
93
  follow-up host exactly as on a standalone one. Then record the change's
93
94
  artifacts — a T2 lightweight-spec, one
@@ -32,6 +32,10 @@ If you arrived here with a T1, go back to Part A's routing.
32
32
  includes *a related feature that is still in flight* — see above for why
33
33
  it cannot be the host.
34
34
 
35
+ Make the choice here, and **act on it only after item 2's branch-base
36
+ check**: on either route, nothing is opened, linked or cut before that
37
+ check.
38
+
35
39
  Do not skip this choice. If a feature that was in flight turns out to have
36
40
  touched the same code, that is a **merge** question, settled when the
37
41
  branches actually meet — see `finish-feature-flow.md` Step 5.
@@ -42,11 +46,69 @@ If you arrived here with a T1, go back to Part A's routing.
42
46
  1.6's delegation to it) like any other minimal host — that branch *is* the
43
47
  **post-hoc branch** the rest of this step names, and the value `_index.md`
44
48
  `branch:` records.
49
+
50
+ **Branch-base check — run it before Step 1.7 step 3 creates anything**
51
+ (on the follow-up route: before Step 1.6 opens the host and writes the
52
+ reverse link). It settles which base branch this host's branch is cut
53
+ from. Closeout requires the commit item 3 records to be an ancestor of
54
+ this branch, so a branch cut from a base that does not contain that commit
55
+ yet passes every step until closeout and is blocked there. Settle that
56
+ commit with the developer first (item 3), then:
57
+ 1. Settle the base branch and fetch it. The developer decides which
58
+ branch is the base — Dflow does not know it, though the project may
59
+ record it under `_conventions.md` § Git Policy — and it is the branch
60
+ Step 1.7 step 4 cuts from anyway. `{base-ref}` is that branch's latest
61
+ ref (for example `origin/develop`). Fetch the remote it belongs to
62
+ (`git fetch origin` for `origin/develop`; `git fetch --all` when
63
+ unsure): a bare `git fetch` updates only the default remote, so a base
64
+ on another remote stays stale and the steps below misjudge it. The fix
65
+ is merged and pushed by now, and it often reached the base on the
66
+ server — a PR merge or a squash — that this clone has not fetched yet.
67
+ 2. Run `git cat-file -t {hash}`. It must print `commit`. If it prints
68
+ anything else or fails, and step 1's fetch failed, the commit may only
69
+ be missing from this clone: stop, say the check needs the fetch, and
70
+ run it again once the remote is reachable. Otherwise the hash is
71
+ mistyped, names an object that is not a commit, or names a commit that
72
+ remote does not have — stop and ask the developer. Do not guess.
73
+ 3. Run `git show --stat {hash}`. At least one path it lists must be an
74
+ implementation path this record will declare. If none is, stop and
75
+ settle the commit with the developer — closeout's (c) would block it.
76
+ A last commit that touches only tests does this. Then run
77
+ `git rev-list --parents -n 1 {hash}`: more than two hashes means a
78
+ merge commit — for example the one a PR page shows as its merged
79
+ commit. Item 3 never records one, and a merge usually passes the path
80
+ check above, so stop here and settle the commit with the developer.
81
+ 4. Run `git merge-base --is-ancestor {hash} {base-ref}`:
82
+ - **Exit `0`** — cut this host's branch from `{base-ref}` at Step 1.7
83
+ step 4. Do **not** let the new branch track the base: run
84
+ `git checkout -b {branch} --no-track {base-ref}`, or update the local
85
+ base branch the way the project normally does and cut from that. A
86
+ branch that tracks `origin/develop` can, under some push settings,
87
+ push its documentation commits straight to `develop`.
88
+ - **Exit `1`** — the fix is not in that base yet. Tell the developer
89
+ both causes. **The fix has not been merged back yet**: merge it back
90
+ first (under GitFlow, into `develop`). **The recorded commit is not
91
+ the one item 3 names**: under a squash or rebase merge, or a
92
+ cherry-pick into the base, the fix's own commits never reach it —
93
+ record the commit the fix landed as (item 3).
94
+ ⚠ Do **not** cut from `main` instead to make the check pass: the
95
+ documentation branch would carry `main`'s merge commits into
96
+ `develop` when it merges, and its documents would start from
97
+ `main`'s older Domain documents and conflict there.
98
+ - **Any other exit code** — a mistyped ref or a failed command. Stop
99
+ and show the developer the output.
100
+ - **If step 1's fetch failed** (offline): exit `0` is still
101
+ trustworthy — the ref you cut from does contain the commit. Exit `1`
102
+ may only mean the local ref is behind; treat it as provisional and
103
+ say so.
45
104
  3. **Implementation checkpoint Result = `reconciled ({merged-hotfix-hash})`.**
46
105
  In the host's Checkpoint Log the implementation row's Result is
47
106
  `reconciled (...)` — the value meaning "this checkpoint documents an
48
- already-merged change" — carrying the merged hotfix commit's hash. It sits
49
- alongside the usual `committed` / `skipped` / `failed` Results (see
107
+ already-merged change" — carrying the hash of **one of the fix's own
108
+ commits**, or of **the commit the fix landed as** when it reached the base
109
+ branch by a squash, a rebase or a cherry-pick — a commit on the base
110
+ branch this host's branch is cut from (item 2).
111
+ It sits alongside the usual `committed` / `skipped` / `failed` Results (see
50
112
  `references/git-integration.md` § Commit Checkpoints, Branch Gate & AI
51
113
  Commits). That hash belongs to the **hotfix**, so it is **not** evidence
52
114
  that this host's own first commit landed — the documentation work still owes
@@ -54,12 +116,27 @@ If you arrived here with a T1, go back to Part A's routing.
54
116
  Committing the documentation together with closeout collapses the host to
55
117
  one commit and fails finish-feature Step 1.
56
118
 
119
+ **Which commit that is depends on how the fix reached the base branch:**
120
+
121
+ | The fix reached the base branch by | Record |
122
+ |---|---|
123
+ | a merge (under GitFlow, merging the hotfix branch back into `develop`) or a fast-forward | the last of **the fix's own commits** that touches a declared implementation path — never a merge commit, and under GitFlow never the tagged merge commit on `main`, which the back-merge does not bring into `develop` |
124
+ | a squash merge | the squash commit on the base branch |
125
+ | a rebase merge or a cherry-pick | the last of the fix's commits **as they landed on the base branch** that touches a declared implementation path |
126
+
127
+ When the fix's own commits are on the base branch, record one of them even if
128
+ a copy of the fix got there first. Closeout accepts a commit that touches only
129
+ some of the declared paths: it asks for at least one. The hotfix branch's own
130
+ name still goes in `hotfix-branch:` (item 4).
131
+
57
132
  **You are asserting this hash, not deriving it.** Nothing in the repository
58
133
  records which commit was the hotfix, so no check in this flow can confirm
59
134
  it — finish-feature's reconciliation gate is explicit that it tests
60
135
  plausibility only. So state the identity
61
136
  and **cite what it rests on**: the PR, incident, or tracker reference that
62
- identifies this fix. Record the citation alongside the per-tier trace
137
+ identifies this fix. **Cite the source, not the hash** — a corrected hash
138
+ (item 6) then never has to touch the citation. Record the citation
139
+ alongside the per-tier trace
63
140
  (item 4). An **uncited** hash blocks closeout —
64
141
  `references/pr-review-checklist.md` is what confirms the identity, and it
65
142
  needs something to confirm against.
@@ -80,3 +157,44 @@ If you arrived here with a T1, go back to Part A's routing.
80
157
  5. **Branch equality** for the host is asserted against the **post-hoc
81
158
  documentation branch** (the `_index.md` `branch:` value), never the deleted
82
159
  hotfix branch.
160
+ 6. **Closeout: (b) fails on the `reconciled (...)` hash.**
161
+ `references/finish-feature-minimal-host.md`'s `Minimal host, hotfix
162
+ post-hoc only` check sends you here. Report which cause it is — never only
163
+ "not an ancestor" — and give the developer the repair for that cause. First
164
+ fetch the remote `{base-ref}` belongs to (item 2 step 1) and find the
165
+ commit item 3 says to record, then test where it is. If the fetch fails, a
166
+ result that depends on `{base-ref}`, or on a commit this clone does not
167
+ have, is provisional — say so, as item 2 does:
168
+ - **It is already on this branch** (`git merge-base --is-ancestor
169
+ {its-hash} HEAD` exits `0`) — the record names the right fix but the
170
+ wrong commit — for example the tagged merge commit on `main`. Correct
171
+ only the Checkpoint Log hash. The citation (item 3 cites the source, not
172
+ the hash), `hotfix-branch:` and the declared paths stay as they are, so
173
+ the correction rides the closeout commit — closeout's closed list admits
174
+ Checkpoint Log rows. Checkpoint 1 is not touched.
175
+ - **It is on the base branch but not on this branch** (`git merge-base
176
+ --is-ancestor {its-hash} {base-ref}` exits `0`) — this branch was cut
177
+ before the fix reached the base, so correcting the hash is not enough.
178
+ Merge `{base-ref}` into this branch — or, if nothing is pushed yet,
179
+ rebase this branch onto it and refill every Lightweight Changes row's
180
+ `Commit` cell — then record that commit. Set closeout's uncommitted
181
+ finalization edits aside first with `git stash push --include-untracked`
182
+ (a plain `git stash` leaves newly created files behind), check that
183
+ `git status --porcelain` prints nothing, and restore them afterwards
184
+ with `git stash pop`: `git rebase` refuses to run over uncommitted
185
+ edits, and `git merge` refuses when the base changed the same files. If
186
+ `git stash pop` reports a conflict — it can when the base changed the
187
+ same part of a file those edits touch — resolve it before closeout
188
+ continues.
189
+ - **It is not on the base branch either** — the fix was never merged back.
190
+ Merge it back first (under GitFlow, into `develop`), then take the case
191
+ above.
192
+ - ⚠ **Never merge `main` or the tag into this branch to make (b) pass.** It
193
+ lets the wrongly recorded commit pass unchanged, and it carries `main`'s
194
+ commits into `develop` when this branch merges.
195
+ - **The record names a different fix** (the citation, the hotfix branch and
196
+ the declared paths are all wrong) — checkpoint 1's own record is wrong,
197
+ and closeout cannot repair that. Follow
198
+ `references/finish-feature-record-correction.md`: it says when
199
+ checkpoint 1 can be amended and when to stop for the developer, and it
200
+ covers `reconciled (...)`, which names the wrong fix too.
@@ -449,7 +449,7 @@ During implementation, continuously verify:
449
449
  **→ Step Gate: Step 7 → Step 8**
450
450
 
451
451
  Announce to developer:
452
- > "Implementation appears complete across all four layers. Ready to run the completion checklist (verify against spec, update domain docs + context-map, ensure test coverage, archive the spec)? `/dflow:next` to proceed."
452
+ > "Implementation appears complete across all four layers. Ready to run the completion checklist (verify against spec, update domain docs + context-map, ensure test coverage, complete the phase)? `/dflow:next` to proceed."
453
453
 
454
454
  > Commit checkpoint (T1 milestone 2 of 3): offer to commit the implementation, then record the result in the `_index.md` Checkpoint Log. Milestone 3 (closeout) is the `/dflow:finish-feature` checkpoint.
455
455
 
@@ -507,33 +507,51 @@ Ask these one-by-one; do not dump all six at once.
507
507
  - [ ] `dflow/specs/domain/context-map.md` — updated if cross-context interaction was added or changed
508
508
  - [ ] `dflow/specs/architecture/tech-debt.md` — tech debt discovered during implementation recorded
509
509
 
510
- ### 8.4 Archival
511
-
512
- For a single-phase feature, this is the closeout point. If the feature has
513
- later phases still ahead, this phase is complete but the feature is not —
514
- don't archive yet; run `/dflow:new-phase` to start the next phase, not
515
- `/dflow:finish-feature` yet. For a multi-phase feature, the developer
516
- typically reaches this point at the end of the final phase — at which time
517
- `/dflow:finish-feature` is the recommended trigger (it bundles steps 8.1 /
518
- 8.2 verification, BC sync, and archival into one explicit ceremony). Either
519
- path is acceptable; pick the one that matches the developer's habit.
520
-
521
- - [ ] `_index.md` `status` field changed to `completed`
522
- - [ ] All `phase-spec-*.md` files in the feature directory have `status:
523
- completed` in their frontmatter
524
- - [ ] **Whole feature directory** moved from `dflow/specs/features/active/`
525
- to `dflow/specs/features/completed/` using `git mv` (preserves rename
526
- tracking — see `references/git-integration.md` § "Directory Moves
527
- Must Use git mv"):
528
- ```
529
- git mv dflow/specs/features/active/{SPEC-ID}-{slug} \
530
- dflow/specs/features/completed/{SPEC-ID}-{slug}
531
- ```
532
-
533
- > **Recommended path for multi-phase features**: instead of doing
534
- > 8.1–8.4 manually at the end of every phase, run `/dflow:finish-feature`
535
- > once the feature's last phase is complete. It executes the same checks
536
- > + BC sync + `git mv` + emits an Integration Summary. See
537
- > `references/finish-feature-flow.md`.
538
-
539
- Only announce "feature complete" after 8.4 is done.
510
+ ### 8.4 Phase completion and handoff
511
+
512
+ This step completes the phase and hands the feature on. It does not close the
513
+ feature out: `/dflow:finish-feature` is the only closeout path — it flips the
514
+ `_index.md` `status`, moves the feature directory to `completed/`, and takes
515
+ the closeout commit checkpoint.
516
+
517
+ **Complete this phase** — all four items, whether or not another phase
518
+ follows:
519
+
520
+ - [ ] This phase-spec's frontmatter `status` changed from `in-progress` to
521
+ `completed`
522
+ - [ ] This phase's row in the `_index.md` Phase Specs table changed from
523
+ `in-progress` to `completed`
524
+ - [ ] `_index.md` Current BR Snapshot reconciled against this phase-spec's
525
+ **Business Rules** section — this is the feature's first phase, so it
526
+ has no Delta from prior phases. If implementation changed a rule, update
527
+ the phase-spec first, then reconcile. Touch only this phase's rows: a
528
+ row that another change in this host wrote — a lightweight change
529
+ hosted under this feature, for example — stays as it is. Where this
530
+ phase and such a change both changed the same rule, keep the later
531
+ change's text; if the host's record does not show which change came
532
+ later, ask the developer which text is current instead of choosing.
533
+ - [ ] `_index.md` Resume Pointer: rewrite **both** narrative lines — Current
534
+ Progress says this phase is completed, and Next Action names the
535
+ command the handoff below picks. Step 4 wrote "phase-1 in progress:
536
+ drafting phase-spec." and "finish phase-spec, then implement …" into
537
+ these two lines; either one left standing reads as open work.
538
+
539
+ `/dflow:finish-feature` relies on these four whenever the feature reaches it,
540
+ next or after later phases. Its Step 1 checks the phase-spec `status`, the
541
+ Phase Specs row and the Resume Pointer, and stops closeout on a stale one.
542
+ Its Step 3 syncs the Current BR Snapshot into the bounded context as it
543
+ stands, without re-deriving it from the phase-specs.
544
+
545
+ `Implementation Tasks` is not on this list: 8.1 verified it, and 8.2 may have
546
+ collapsed or removed it by team convention. BC document sync is not on it
547
+ either: 8.3 did it.
548
+
549
+ **Hand off:**
550
+
551
+ - Another phase is still ahead → run `/dflow:new-phase` next.
552
+ - No further phase — a single-phase feature, or this was the last phase →
553
+ run `/dflow:finish-feature` next. It is the only closeout path.
554
+
555
+ Announce that this phase is completed and name the next command. Do not
556
+ announce the feature as complete: `/dflow:finish-feature` announces that once
557
+ its closeout is complete.
@@ -347,5 +347,5 @@ state or archive the whole feature from `/dflow:new-phase`.
347
347
 
348
348
  After completion, summarize for the developer:
349
349
  > "Phase {N+1} is implemented and marked completed. `_index.md` is refreshed.
350
- > If another slice is needed, run `/dflow:new-phase`; if the feature is done,
351
- > run `/dflow:finish-feature`."
350
+ > If another slice is needed, run `/dflow:new-phase`; if there is no further
351
+ > phase, run `/dflow:finish-feature` to close the feature out."
@@ -160,7 +160,17 @@ skip one and the boundary is a hole rather than a division of labour.
160
160
  checkpoint 1 and closeout. A third, typically a host opened in its own
161
161
  commit before the work landed, breaks the two-commit lifecycle while
162
162
  every closeout check still passes, because those read only the hashes
163
- the record points at. **This count applies to a minimal host only**: a
163
+ the record points at. A third that comes **after** checkpoint 1 and
164
+ changes only the host's record text — no implementation path, no new
165
+ host file — may instead be a correction committed on its own once
166
+ checkpoint 1 was pushed (`references/finish-feature-record-correction.md`
167
+ sends the developer that way and asks for it to be named in the PR
168
+ description). Report which it is: the count still fails, and
169
+ accepting a correction commit is your call after reading its full
170
+ diff and the PR description. A correction that touches only a record
171
+ outside this directory — a follow-up's original `_index.md` — is not in
172
+ this walk; the PR description is where it is named.
173
+ **This count applies to a minimal host only**: a
164
174
  phase-bearing feature has no fixed number of host commits, so there is
165
175
  nothing to count there and this item is N/A.
166
176
  This is also where closeout's minimal-host **selector** boundary is
@@ -173,9 +183,23 @@ skip one and the boundary is a hole rather than a division of labour.
173
183
  so the developer asserts it and cites what the claim rests on —
174
184
  `hotfix-identity:` beside `hotfix-branch:` in a **T2**'s
175
185
  lightweight-spec frontmatter, or in the row Description for a **T3**.
176
- **Open that source** — PR, incident, or tracker — and confirm it names
177
- this fix and this commit. Closeout already checked that a citation is
178
- *present*; only review can check that it *matches*.
186
+ **Open that source** — PR, incident, or tracker — and confirm that it
187
+ names **this fix** and the recorded commit: one of the fix's own
188
+ commits, or the squash commit when the fix was squashed
189
+ (`references/modify-existing-post-hoc-hotfix.md` item 3). A fix merged
190
+ without a PR is identified by the source and the base branch's history
191
+ instead — the hotfix branch's name or the source's number in commit
192
+ messages.
193
+ **When the fix landed as a copy** — a rebase merge or a cherry-pick —
194
+ the recorded commit is that copy, and the source may not name it. Git
195
+ cannot prove that one commit is a copy of another: ancestry does not
196
+ follow copies, and patch comparison (`git cherry`, `git patch-id`)
197
+ cannot tell apart two changes that differ only in whitespace. A
198
+ `(cherry picked from commit …)` line or a matching subject is a clue,
199
+ not proof — settle it with the author from the source, and do not
200
+ report the identity as confirmed from command output alone.
201
+ Closeout already checked that a citation is *present*; only review can
202
+ check that it *matches*.
179
203
  - [ ] **Hosted `Commit` cell identity — confirm each hash is *that row's own*
180
204
  implementation commit** (phase-bearing host only). `finish-feature-flow.md`
181
205
  Step 4 instruction 1 orders the value ("each row's own implementation