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
@@ -59,7 +59,32 @@ AI must locate the target feature and load its current state:
59
59
  completed = frozen-history semantic and produces confusing dir-rename
60
60
  history. The follow-up path is the only correct route.
61
61
 
62
- 3. **Load context for the new phase**
62
+ 3. **Refuse if the target is a minimal (zero-phase) host**
63
+
64
+ Decide this from the **persisted shape** — the same selector closeout
65
+ uses: an **empty Phase Specs table and no `phase-spec-*` file** in the host
66
+ directory. Such a host records a small standalone or follow-up change and
67
+ is *defined* by carrying no phase. Adding one does not extend it; it turns
68
+ the host into a phase-bearing feature, and closeout will then check it as
69
+ one. Refuse with:
70
+
71
+ ```
72
+ "`{SPEC-ID}-{slug}` is a minimal (zero-phase) host — it records a
73
+ lightweight change and carries no phase-specs, so a phase cannot be
74
+ added to it.
75
+
76
+ If this is genuinely new T1 work, run /dflow:new-feature. If it is
77
+ another small change, run /dflow:modify-existing — its Step 1 decides
78
+ the tier and finds or opens the right host for it."
79
+ ```
80
+
81
+ Do NOT create a first phase-spec here to "make the host usable", and do
82
+ not jump into `modify-existing-flow.md` Step 1.6, 1.7 or 1.8 from here:
83
+ each of those assumes Step 1's tier decision and host-linkage scan have
84
+ already run, and entering one directly skips both. Send the developer to
85
+ Step 1, exactly as `new-feature-flow.md` Step 4's guard does.
86
+
87
+ 4. **Load context for the new phase**
63
88
  - Read the feature's `_index.md` — Metadata, Goals & Scope, Phase Specs,
64
89
  Current BR Snapshot, Resume Pointer
65
90
  - Read the most recent phase-spec to understand where the prior phase
@@ -71,7 +96,7 @@ AI must locate the target feature and load its current state:
71
96
  not visible on this branch — if the incoming phase scope overlaps one
72
97
  of them, surface it before writing the phase-spec.
73
98
 
74
- 4. **Branch gate — ensure you are on this feature's branch (before any commit)**
99
+ 5. **Branch gate — ensure you are on this feature's branch (before any commit)**
75
100
 
76
101
  This phase's commits must land on the active feature's
77
102
  `feature/{SPEC-ID}-{slug}` branch. If you are not already on it (you
@@ -111,8 +136,19 @@ Walk the developer through what the new phase covers:
111
136
  re-read from `references/ddd-modeling-guide.md` § "Revising an Established
112
137
  Model" (match recorded re-evaluation conditions; record proceed / split /
113
138
  rename in the phase-spec).
114
- 4. **Data structure impact?** New tables, columns, indices?
115
- 5. **Why now?** Priority — informs sequencing relative to other phases.
139
+ 4. **System-level knowledge?** Does this phase add, change, or uncover an
140
+ ordered cross-context flow (always the domain-root copy), the lifecycle a
141
+ status field moves through, a figure computed rather than stored, a
142
+ mechanism no single rule explains, or which roles reach a function (always
143
+ the domain-root copy) — or work around a spot in one of them that only a
144
+ domain decision will settle (in the copy that holds the knowledge it is
145
+ stuck on)? If yes, record it in
146
+ `dflow/specs/domain/{context}/analysis.md` when one context owns what it
147
+ describes and in `dflow/specs/domain/analysis.md` when no one context does,
148
+ creating the file from `templates/analysis.md` the first time there is
149
+ something to record.
150
+ 5. **Data structure impact?** New tables, columns, indices?
151
+ 6. **Why now?** Priority — informs sequencing relative to other phases.
116
152
 
117
153
  This is also the moment to ask: "Should this be its own follow-up feature
118
154
  instead of a phase here?" — useful when the scope drift suggests a
@@ -123,7 +159,7 @@ separate concern.
123
159
  ## Step 3: Phase Slug Confirmation
124
160
 
125
161
  AI proposes the new phase-spec filename and asks the developer to confirm
126
- before any file is written.
162
+ before the phase-spec is written.
127
163
 
128
164
  > "Proposed phase-spec for `{SPEC-ID}-{slug}`:
129
165
  >
@@ -165,6 +201,8 @@ frontmatter `status` to `in-progress`. Phase-2-onward specs **must** fill
165
201
  in the **Delta from prior phases** section (the first phase typically has
166
202
  just "首 phase,無前置 Delta"; this is phase 2+, so the section is required).
167
203
 
204
+ > **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).
205
+
168
206
  Walk the developer through each section, in the same way `new-feature-flow`
169
207
  Step 4 does — Behavior / Business Rules / Delta / Edge Cases — but only
170
208
  list NEW or MODIFIED BRs in Business Rules; UNCHANGED BRs from prior phases stay
@@ -195,6 +233,8 @@ Wait for confirmation before entering Step 5.
195
233
 
196
234
  Update the feature's `_index.md`:
197
235
 
236
+ > **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).
237
+
198
238
  1. **Phase Specs table** — add a new row for this phase:
199
239
  ```
200
240
  | {N+1} | {YYYY-MM-DD} | {phase-slug} | in-progress | [phase-spec-{date}-{phase-slug}.md](./phase-spec-{date}-{phase-slug}.md) |
@@ -247,6 +287,14 @@ During implementation, continuously verify:
247
287
  - [ ] Domain code has no delivery-framework dependency
248
288
  - [ ] Data access changes do not introduce business rules into infrastructure
249
289
  - [ ] Test failures have been resolved or explicitly recorded as follow-up
290
+ - [ ] Every entry Step 2 recorded in `analysis.md`, at either of its two
291
+ paths, still holds. An entry for what this phase was to add or change
292
+ matches what it built — corrected where the build differs, removed
293
+ where nothing was built for it. An entry for something Step 2
294
+ uncovered in the existing system matches the system as it now stands —
295
+ corrected where this phase changed it, never removed because this
296
+ phase built nothing for it. Read the entries in the file, not in a
297
+ diff: a checkpoint may already have committed them.
250
298
 
251
299
  If implementation changes the agreed Delta, update the phase-spec before
252
300
  continuing. Do not let code and spec diverge silently.
@@ -283,10 +331,12 @@ Update the feature artifacts:
283
331
  feature is ready to wrap up"
284
332
 
285
333
  System-level domain docs, migration debt sync, and the feature directory move
286
- to `completed/` remain `/dflow:finish-feature` responsibilities. Do not sync
287
- system-level current state or archive the whole feature from `/dflow:new-phase`.
334
+ to `completed/` remain `/dflow:finish-feature` responsibilities; Step 2 records
335
+ into `analysis.md` directly, at whichever of its two paths the entry belongs
336
+ to. Do not sync any other system-level current state or archive the whole
337
+ feature from `/dflow:new-phase`.
288
338
 
289
339
  After completion, summarize for the developer:
290
340
  > "Phase {N+1} is implemented and marked completed. `_index.md` is refreshed.
291
- > If another slice is needed, run `/dflow:new-phase`; if the feature is done,
292
- > run `/dflow:finish-feature`."
341
+ > If another slice is needed, run `/dflow:new-phase`; if there is no further
342
+ > phase, run `/dflow:finish-feature` to close the feature out."
@@ -34,20 +34,45 @@ touches and read them all.
34
34
  {reason}." (If you can't, pause and ask the author.)
35
35
  - [ ] Only then proceed to the code-review sections below
36
36
 
37
- If the PR has no spec or no `_index.md`:
37
+ If the PR records its change in neither a spec nor an `_index.md` row (a T3
38
+ inline row or a `Tier = baseline` row is such a record, and a host that
39
+ carries one needs no spec), first check whether it only records existing
40
+ behaviour or a decision in Domain documents, without changing output.
41
+ That PR is observation-only, and some observation-only records legitimately
42
+ have no host: answer it with the reply under "If the spec is missing or
43
+ incomplete" at the end of Spec Compliance, not with the one below. For any
44
+ other PR with no such record:
38
45
  ```
39
46
  "I don't see a feature directory or _index.md for this PR. Before I
40
- review the code, can you point me to it, or run /dflow:new-feature
41
- (or /dflow:bug-fix for a small fix) to create the feature directory
42
- and at least a lightweight spec? SDD relies on the spec being the
43
- review anchor."
47
+ review the code, can you point me to the host feature? SDD relies on
48
+ the recorded change being the review anchor.
49
+
50
+ If this change belongs to a feature that already exists, run
51
+ /dflow:modify-existing or /dflow:bug-fix against it — the cascade
52
+ decides what gets recorded (a T2 lands a lightweight spec; a T3 lands
53
+ one inline row in that feature's _index.md and no spec file). If it is
54
+ genuinely new work, run /dflow:new-feature.
55
+
56
+ If no feature exists to host it at all, that is the standalone case and
57
+ it has a defined route: /dflow:modify-existing opens a minimal
58
+ (zero-phase) host for it — Step 1.7, or Step 1.6's minimal variant when
59
+ the change is a follow-up on a completed feature. Send it there rather
60
+ than inventing a home by hand."
44
61
  ```
45
62
 
46
63
  ## Spec Compliance
47
64
 
48
65
  Per-feature checks:
49
66
  - [ ] **Feature directory exists** with `_index.md` + at least one
50
- phase-spec (or one lightweight-spec for a T2-only feature)
67
+ phase-spec (or one lightweight-spec for a T2-only feature).
68
+ **Exception — a host whose whole record is inline.** A **T3-only**
69
+ minimal host has no spec file by design: a T3 never produces one.
70
+ A **baseline-only** host has none either — a baseline capture writes
71
+ Domain documents directly and records a `Tier = baseline` row. For both,
72
+ the requirement is at least one `_index.md` Lightweight Changes row,
73
+ not a spec file. Do **not** ask the author to add a spec to satisfy
74
+ this line — that manufactures the artifact the tier forbids and the
75
+ flow refuses to write.
51
76
  - [ ] **`_index.md` Current BR Snapshot is up to date** — reflects
52
77
  the cumulative effect of all phase-specs / lightweight-specs in
53
78
  the directory
@@ -55,7 +80,9 @@ Per-feature checks:
55
80
  phase-spec file exists and its `status` matches the row's claim
56
81
  - [ ] **For follow-up features**: `_index.md` Metadata has `follow-up-of:
57
82
  {原 SPEC-ID}` AND the original feature's `_index.md`
58
- Follow-up Tracking row references this feature
83
+ Follow-up Tracking row references this feature. **`follow-up-of` may
84
+ be a YAML array** — when it names several originals, *each* of them
85
+ must carry the row; checking the first one is not checking the field.
59
86
 
60
87
  Per-phase-spec / lightweight-spec checks (run for **each** spec file the
61
88
  PR touches, not just one):
@@ -63,6 +90,23 @@ PR touches, not just one):
63
90
  - [ ] **Business rules covered** — all BR-* rules in this spec
64
91
  implemented
65
92
  - [ ] **Edge cases handled** — EC-* in this spec addressed
93
+ - [ ] **No-BR family evidence** — for a lightweight-spec whose Behavior
94
+ Delta is a `BR:` / `BR Delta:` none line, check that family's
95
+ evidence instead: (a) `Output Footprint`; (b) `Contract Delta` +
96
+ `**Downstream consumers**`; (c) `Operational Rationale` +
97
+ `**Trace**`; (d) `Performance Delta` + `**SLA / resource
98
+ context**`; (e) `Governing BR-IDs` + root cause + a regression
99
+ check; (f) `Change Rationale` with `**Before**` / `**After**` +
100
+ `**Regression**`. The BR / scenario / Domain items above are
101
+ **N/A** for these specs — record them as N/A rather than passing
102
+ them on an empty rule set, and never fabricate a BR or a
103
+ `behavior.md` anchor to make one pass. Domain documents are not
104
+ automatically N/A: a contract change that touches documented
105
+ behavior still belongs in `behavior.md`. A spec in the
106
+ classic BR-delta form, or a bug spec with the older single `BR:`
107
+ line, is accepted as-is. A change matching more than one family
108
+ must carry **every** matching family's evidence, not just the one
109
+ its BR line names
66
110
  - [ ] **Delta integrity** (phase 2+ only) — the Delta-from-prior-phases
67
111
  section's ADDED / MODIFIED / REMOVED / RENAMED entries actually
68
112
  match the diff against the prior phase-spec's BR set
@@ -70,19 +114,292 @@ PR touches, not just one):
70
114
  If the closeout commit is in this PR (`/dflow:finish-feature` was run):
71
115
  - [ ] **BC layer sync landed** — `dflow/specs/domain/{context}/rules.md` and
72
116
  `behavior.md` reflect the feature's net effect (compare against
73
- `_index.md` Current BR Snapshot)
117
+ `_index.md` Current BR Snapshot). **N/A for a no-BC host** — one whose
118
+ Goals & Scope declares no bounded context has nothing to sync, and a
119
+ `{context}` document invented to satisfy this line is a fiction, never a
120
+ pass.
121
+ ⚠ **Do not read that as "closeout already blocks it" without qualifying
122
+ where.** Closeout blocks it in the commits it actually reads — checkpoint
123
+ 1, and only on a **minimal** host, plus the closeout commit on any host.
124
+ A fiction committed by a **phase-bearing** no-BC host anywhere in between
125
+ passes every closeout check. That range is the *"A no-BC host committed no
126
+ BC-scoped Domain material"* item in the delegated block below, and it is
127
+ yours to run. **N/A for a baseline capture** — a baseline
128
+ writes Domain documents directly, so there is no separate sync to land.
129
+ **N/A for a T3**, which does no Domain work at all. Record the N/A rather
130
+ than ticking it against an empty comparison — but note `glossary.md` and
131
+ `migration/tech-debt.md` belong to no bounded context, stay in a no-BC
132
+ host's sweep, and are a baseline capture's own capture destinations, so
133
+ this N/A does not reach them.
74
134
  - [ ] **Whole feature directory `git mv`'d** to `completed/` — git
75
135
  shows `renamed:` (not `deleted:` + `new file:`)
76
136
  - [ ] **Integration Summary** was emitted to the conversation (not
77
137
  written to a file — it's ephemeral)
138
+ - [ ] **Every Lightweight Changes row now carries a `Commit` hash.**
139
+ Before closeout an empty cell can be legitimate: on a **hosted** row
140
+ the cell is filled by the host's *next* commit, and closeout is
141
+ allowed to be that commit. (A **minimal** host is stricter — its rows
142
+ are written before checkpoint 1, and closeout already refuses an empty
143
+ cell there.) After closeout there is no next commit, so an empty cell
144
+ will never be filled. That is why closeout does not assert this for a
145
+ phase-bearing host, and why it is checkable here.
146
+ ⚠ **A placeholder is not a hash.** `{hash}`, `{pending}`, `(待 commit)` —
147
+ anything that is not a resolvable commit — counts as **unfilled**, not as
148
+ carrying one. It is the harder case to catch precisely because the cell is
149
+ **non-empty**: every rule downstream is written against empty / non-empty,
150
+ so a placeholder reads as filled to all of them. Resolve each value
151
+ (`git cat-file -t {value}`) rather than checking the cell is populated.
152
+
153
+ **Delegated to review by `finish-feature-flow.md`.** Closeout states at each
154
+ of these sites that it does *not* prove them. They are not optional extras:
155
+ skip one and the boundary is a hole rather than a division of labour.
156
+
157
+ - [ ] **Whole history — no stray host-open commit.** Closeout validates the
158
+ hashes the record names and cannot see the rest of the branch. List
159
+ this branch's commits that touch the host directory:
160
+ `git log --oneline {base}..HEAD -- 'dflow/specs/features/active/{SPEC-ID}-{slug}/' 'dflow/specs/features/completed/{SPEC-ID}-{slug}/'`
161
+ For a **minimal (zero-phase) host** there must be exactly two —
162
+ checkpoint 1 and closeout. A third, typically a host opened in its own
163
+ commit before the work landed, breaks the two-commit lifecycle while
164
+ every closeout check still passes, because those read only the hashes
165
+ the record points at. A third that comes **after** checkpoint 1 and
166
+ changes only the host's record text — no implementation path, no new
167
+ host file — may instead be a correction committed on its own once
168
+ checkpoint 1 was pushed (`references/finish-feature-record-correction.md`
169
+ sends the developer that way and asks for it to be named in the PR
170
+ description). Report which it is: the count still fails, and
171
+ accepting a correction commit is your call after reading its full
172
+ diff and the PR description. A correction that touches only a record
173
+ outside this directory — a baseline capture's document, a follow-up's
174
+ original `_index.md` — is not in this walk; the PR description is where
175
+ it is named.
176
+ **This count applies to a minimal host only**: a
177
+ phase-bearing feature has no fixed number of host commits, so there is
178
+ nothing to count there and this item is N/A.
179
+ This is also where closeout's minimal-host **selector** boundary is
180
+ settled: that selector reads the host's persisted shape today and
181
+ cannot know it was opened minimal and *stayed* minimal. The commit walk
182
+ is what shows that.
183
+ - [ ] **Hotfix identity — confirm it against the cited source** (post-hoc
184
+ host only). Closeout tests **plausibility only**, and says so: which
185
+ commit was *the* merged hotfix is not derivable from inside the flow,
186
+ so the developer asserts it and cites what the claim rests on —
187
+ `hotfix-identity:` beside `hotfix-branch:` in a **T2**'s
188
+ lightweight-spec frontmatter, or in the row Description for a **T3**.
189
+ **Open that source** — PR, incident, or tracker — and confirm that it
190
+ names **this fix** and the recorded commit: one of the fix's own
191
+ commits, or the squash commit when the fix was squashed
192
+ (`references/modify-existing-post-hoc-hotfix.md` item 3). A fix merged
193
+ without a PR is identified by the source and the base branch's history
194
+ instead — the hotfix branch's name or the source's number in commit
195
+ messages.
196
+ **When the fix landed as a copy** — a rebase merge or a cherry-pick —
197
+ the recorded commit is that copy, and the source may not name it. Git
198
+ cannot prove that one commit is a copy of another: ancestry does not
199
+ follow copies, and patch comparison (`git cherry`, `git patch-id`)
200
+ cannot tell apart two changes that differ only in whitespace. A
201
+ `(cherry picked from commit …)` line or a matching subject is a clue,
202
+ not proof — settle it with the author from the source, and do not
203
+ report the identity as confirmed from command output alone.
204
+ Closeout already checked that a citation is *present*; only review can
205
+ check that it *matches*.
206
+ - [ ] **Hosted `Commit` cell identity — confirm each hash is *that row's own*
207
+ implementation commit** (phase-bearing host only). `finish-feature-flow.md`
208
+ Step 4 instruction 1 orders the value ("each row's own implementation
209
+ hash, never the closeout hash") and its post-commit verification **states
210
+ that it cannot decide it**: the hash-evidence test is minimal-host-only
211
+ and lives in `finish-feature-minimal-host.md`, so on a phase-bearing host
212
+ no closeout check reads the value at all. The presence item above asserts the cell is **non-empty**;
213
+ this one asserts it is **right**, and the two are not interchangeable.
214
+ For each Lightweight Changes row, take its `Commit` hash and run
215
+ `git show --stat {hash}`. **Take the row's declaration if it has one and
216
+ require the commit to touch it** — a **T2**'s own spec file
217
+ (`lightweight-*.md` / `BUG-*.md`), a **T3**'s Description implementation
218
+ paths, a **`Tier = baseline`** row's capture documents — a capture
219
+ is **observation-only**, so what it declares is documents, never
220
+ implementation paths. (A phase-bearing host may legitimately hold a
221
+ baseline row alongside phase work; `finish-feature-minimal-host.md` says so
222
+ where it explains why such a host never reaches the zero-phase checks.)
223
+ **If the row declares nothing, that is by design on a hosted row — with
224
+ one exception.** A **T2** links to its own spec file on *every* host shape:
225
+ `finish-feature-flow.md` Step 1 carries two `Tier = T2` checks that no
226
+ host shape escapes — one of them says so in as many words — and
227
+ `finish-feature-minimal-host.md`'s phase-bearing exemption note confirms
228
+ those hosts keep them. A T2 row with
229
+ no such link is **malformed, not exempt** — find the spec file or flag the
230
+ row. For a **T3**, and for a **baseline** row, the absence is the
231
+ sanctioned state — and for the baseline it is the same hosted exemption
232
+ that covers a hosted T3, which `templates/_index.md` extends to baseline
233
+ rows in the same breath. It is not a property of captures.
234
+ ⚠ The record then gives you nothing to compare against, and that is
235
+ deliberate: `finish-feature-minimal-host.md` exempts phase-bearing hosts
236
+ from its `Commit` cell path check for exactly this reason
237
+ (`references/flow-rationale-registry.md`, `R-FF-COMMITCELL-02`:
238
+ "demanding it would reject every hosted T3"), and `templates/_index.md`
239
+ calls declaring them good
240
+ practice with no gate behind it. **Do not read "touches nothing declared"
241
+ as a pass.** What remains checkable is weaker and is worth stating as
242
+ such: the hash is **not** the closeout commit, is **not** any other row's
243
+ hash, and is a commit on this host's branch — or on a branch this host
244
+ recorded a `branch-override` for. Past that, ask the developer which
245
+ commit carried the row. If identity matters for this change, say so and
246
+ have the row declare its paths — that is what makes the comparison above
247
+ available.
248
+ ⚠ **Do not let tier decide that a declaration must exist.** Tier says what
249
+ the declaration would have been; the one hosted row required to carry one
250
+ is a T2, and that requirement lives in Step 1, not here. Keying this check
251
+ on tier produced three successive versions that each named an input some
252
+ row was not required to carry.
253
+ A cell holding the **closeout** hash, or another row's hash, is what this
254
+ exists to catch: both are non-empty, both pass every closeout check, and
255
+ neither is what instruction 1 ordered.
256
+ **N/A for a minimal host** — there `finish-feature-minimal-host.md`'s
257
+ hash-evidence test already runs (a)–(c) against every row before closeout
258
+ may proceed.
259
+ - [ ] **A recorded branch override still matches where the closeout landed**
260
+ (only when the host carries a `branch-override` row). Closeout passes its
261
+ branch check on any such row naming the branch it ran on, and **states
262
+ that it cannot decide** whether that override was still the current
263
+ intent — nothing expires a row once the developer returns to the host's
264
+ own branch. You can see what closeout cannot: which branch the closeout
265
+ commit sits on, and whether the work had already been integrated by then.
266
+ Two cases to reject, both instances of one rule — **the branch this
267
+ closeout landed on is not where this host's work was finished**: a closeout
268
+ committed onto the **base** branch *after* the feature branch was merged
269
+ (that puts the closeout commit outside the review this checklist is part
270
+ of), and a closeout committed onto a branch an override named earlier but
271
+ which the work has since **left** — a superseded spike, say. Closeout
272
+ distinguishes neither, because it accepts any branch this host ever
273
+ recorded an override for.
274
+ - [ ] **The closeout commit carries only this host's delta.** Closeout's spill
275
+ check derives its permitted set from Step 4 instruction 2, which defines
276
+ one for **every** host shape — so there is a permitted-file list to read
277
+ against on a phase-bearing feature too. What that does not change is what
278
+ the check proves: it is a **path-level spill check**, so it proves no
279
+ unpermitted *file* entered the commit, never that the permitted ones carry
280
+ only this change. An unrelated BR edit inside `rules.md`, or another
281
+ feature's term in `glossary.md`, passes it untouched. Read each
282
+ permitted file's diff against the intended delta in the spec / row.
283
+ - [ ] **A follow-up's reverse link was opened, not only closed.** The
284
+ original's Follow-up Tracking row must have existed as `in-progress`
285
+ before the flip to `completed` — `absent → in-progress → completed`,
286
+ never created straight as `completed`. Closeout verifies this for a
287
+ **minimal** host only, from checkpoint 1's committed blob; a
288
+ **phase-bearing** follow-up has no single commit required to carry the
289
+ row, so closeout has nothing equivalent to read and the opening half
290
+ arrives here unchecked. Confirm it from the branch history
291
+ (`git log -p {base}..HEAD -- <original feature>/_index.md`), for
292
+ **each** SPEC-ID in `follow-up-of`.
293
+ - [ ] **A no-BC host committed no BC-scoped Domain material** — **every no-BC
294
+ host, not only phase-bearing ones.** Closeout inspects only **checkpoint
295
+ 1's** diff on a minimal host, and a phase-bearing host has no single
296
+ checkpoint to inspect at all. **Neither is a branch-range proof**, and
297
+ closeout cannot take a range — it needs a base branch and Dflow
298
+ deliberately does not know yours. **You have it.** Run
299
+ `git diff --name-status {base}..HEAD -- 'dflow/specs/domain/'` and confirm
300
+ no BC-scoped Domain document — **the set this track's own no-BC closeout
301
+ check names**, `context-map.md` included — was **added, modified, deleted
302
+ or renamed**. A no-BC host is one whose Goals & Scope declares no bounded
303
+ context, the same identification the BC-layer-sync item above uses.
304
+ ⚠ **The path filter is deliberately wider than the prohibited set, so read
305
+ the output before flagging.** `context-map.md` sits at `domain/` top level
306
+ rather than under `{context}/`, and the filter has to reach up there to
307
+ catch it — which means it also lists `domain/glossary.md` and
308
+ `domain/analysis.md`. Both belong to no bounded context and stay
309
+ legitimate for a no-BC host: **seeing either here is not a finding.**
310
+ `migration/tech-debt.md` is outside the filter entirely and never appears.
311
+ ⚠ **A baseline capture is not a no-BC host** and this item does not apply
312
+ to it: writing Domain documents is exactly what a baseline is for. Closeout's
313
+ own no-BC check states the same exemption.
78
314
 
79
315
  If the spec is missing or incomplete:
80
316
  ```
81
317
  "I notice this PR doesn't have a matching feature directory / _index.md.
82
- Let's create one retroactively — it'll help with documentation and
83
- the target architecture. Can you describe what this change does?"
318
+ Can you describe what this change does? If it only records existing
319
+ behaviour or a decision without changing output, it is observation-only
320
+ and tier-exempt — it is captured in whichever Domain documents it
321
+ actually wrote (there is no fixed list) and stops there. It needs no host
322
+ when it is an analysis.md record made with no workflow running and
323
+ committed on its own, or a capture about no bounded context at all; any
324
+ other capture with no active feature to attach it to gets a baseline
325
+ minimal host, whose Tier = baseline row declares the documents it wrote —
326
+ not a spec. Otherwise: if it belongs to a feature
327
+ we already have, we can record it there retroactively — the tier decides
328
+ what gets written (T2: a lightweight spec; T3: one inline _index.md
329
+ row). If it's genuinely new work, it wants /dflow:new-feature. If
330
+ there's no feature it could belong to, that's the standalone case and
331
+ it has a defined route: /dflow:modify-existing opens a minimal
332
+ (zero-phase) host for it — Step 1.7, or Step 1.6's minimal variant when
333
+ it's a follow-up on a completed feature. Let's send it there rather
334
+ than inventing a home by hand."
84
335
  ```
85
336
 
337
+ ## Risk-Triggered Modeling Review
338
+
339
+ Use the spec and diff to identify these risks; do not use a keyword-only scan.
340
+ Only when a signal matches, read the named section in
341
+ `dflow/specs/shared/dflow-workflows/references/ddd-modeling-guide.md`. If no
342
+ signal matches, skip this section's guide lookup, create no artifact, and
343
+ continue with the existing checklist below.
344
+
345
+ | Risk signal | Read in the modeling guide |
346
+ |---|---|
347
+ | One use case mutates / saves multiple Aggregate roots, or adds a multi-root Unit of Work | Aggregate Design Rules; Domain Services |
348
+ | The diff crosses Bounded Contexts or changes an event payload, handler, or delivery expectation | Domain Events; Event Handling Guidelines |
349
+ | Deadline, retry, out-of-order callback, compensation, business-visible progress, or a claim that local persistence and an external provider / service fact form one atomic outcome | Aggregate Design Rules; Long-Running Processes; failure-path guidance under Event Handling Guidelines |
350
+ | `unique`, `only one active`, or another set-based invariant | Set-Based / Uniqueness Invariants |
351
+ | A Domain Service is added or changed | Domain Services; Long-Running Processes when the flow has state |
352
+
353
+ For every matched risk, record one disposition in the review:
354
+
355
+ - **Finding** — identify the violated guarantee at the boundary named by the
356
+ matched risk, its severity, and merge disposition. For a match involving an
357
+ external provider / service fact, state that a local transaction cannot make
358
+ that fact atomic with local state; implementing or repairing a local
359
+ transaction / Unit of Work alone is not sufficient closure.
360
+ - **Accepted with rationale** — state why the boundary / transaction / service
361
+ role is sound and when to revisit it. For such an exception, the rationale
362
+ must already exist or be added to the appropriate Aggregate / Domain Service
363
+ row and Notes in `models.md`; when it protects an invariant, update the
364
+ related `rules.md` entry too. The PR conversation points to that decision and
365
+ is not its only home. This disposition evaluates the rationale's adequacy,
366
+ not only its existence: it must explain how it answers every matched risk.
367
+ For a match involving an external provider / service fact, acknowledge that
368
+ a local transaction cannot make that fact atomic with local state and explain
369
+ how the outcome progresses across the boundary, including idempotency and
370
+ retry / compensation (or a justified equivalent). For a Domain Service match,
371
+ name the actual domain rule or decision the accepted operation carries; an
372
+ operation that only orders root calls is Application-layer orchestration and
373
+ remains a Finding, even when another operation of the same service holds a
374
+ real rule. If the rationale does not cover the matched risk, use Finding
375
+ rather than this disposition.
376
+ - **Not applicable** — explain from the spec and diff why the match is only
377
+ superficial.
378
+
379
+ Multiple signals for the same design decision may share one disposition, but
380
+ every matched risk must have an auditable disposition. This is not a per-signal
381
+ form.
382
+
383
+ ### Closing a blocking modeling finding
384
+
385
+ A DDD / domain-correctness blocker must state:
386
+
387
+ - the code boundary or enforcement needed to restore the guarantee;
388
+ - the existing durable surfaces that must change, as applicable — the spec,
389
+ domain `models.md` / `rules.md` / `context.md` / `context-map.md` /
390
+ `behavior.md`, or `migration/tech-debt.md`;
391
+ - the automated tests or observable evidence that re-verify the guarantee; and
392
+ - that the unimplemented guarantee must not remain marked `completed` before
393
+ re-review.
394
+
395
+ If the review offers defer as an option, it must list the owner, known risk,
396
+ exit criteria, and `migration/tech-debt.md` landing surface as closure
397
+ conditions. If the team defers the finding, record those items on that surface;
398
+ rationale, a re-evaluation condition, or "future refactor" alone is not
399
+ closure. The reviewer defines the evidence needed to unblock, not the author's
400
+ implementation. Use the existing PR comment / conversation surface; do not
401
+ create a separate review report.
402
+
86
403
  ## Domain Layer Quality
87
404
 
88
405
  - [ ] **No delivery-framework references in Domain** — `src/Domain/` must have zero dependencies on HTTP request/response objects, session/cookie context, job-runner context, CLI flag parsers, or ViewState equivalents
@@ -158,6 +475,7 @@ uses '報銷 (Expense Claim)'. Should we align the naming?"
158
475
 
159
476
  - [ ] **models.md updated** — New entities/VOs/services documented?
160
477
  - [ ] **rules.md updated** — New or changed business rules recorded?
478
+ - [ ] **analysis.md updated** — did this change touch a status transition, an ordered cross-context flow, which roles reach a function, or the way a figure is computed? If so, is the matching entry in `analysis.md` updated? Open one Evidence source it cites and confirm it still says what the entry claims. An entry whose Evidence is `inferred` names what its conclusion was drawn from: confirm instead that the conclusion still follows from it. An entry whose Evidence is `assumed` has no source to open: confirm instead that it says what it rests on and that the question of confirming it is recorded where the Evidence convention at the top of `analysis.md` says, and report it as unverified rather than blocking on it.
161
479
  - [ ] **context.md boundaries respected** — Does this change stay within its context?
162
480
 
163
481
  ## Tech Debt Awareness