dflow-sdd-ddd 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +824 -1
  2. package/CONTRIBUTING.md +16 -10
  3. package/README.en.md +156 -200
  4. package/README.md +89 -144
  5. package/TEMPLATE-COVERAGE.md +15 -8
  6. package/TEMPLATE-LANGUAGE-GLOSSARY.md +15 -1
  7. package/bin/dflow.js +36 -4
  8. package/docs/commands.en.md +110 -0
  9. package/docs/commands.md +101 -0
  10. package/docs/doctor-uncertainty.en.md +212 -0
  11. package/docs/doctor-uncertainty.md +212 -0
  12. package/docs/evaluating-dflow.en.md +29 -11
  13. package/docs/evaluating-dflow.md +8 -6
  14. package/docs/npm-publish-checklist.md +3 -1
  15. package/docs/release-versioning-policy.md +8 -2
  16. package/docs/upgrading.en.md +196 -0
  17. package/docs/upgrading.md +197 -0
  18. package/docs/using-with-claude-code.en.md +25 -10
  19. package/docs/using-with-claude-code.md +20 -7
  20. package/docs/using-with-codex.en.md +18 -6
  21. package/docs/using-with-codex.md +16 -5
  22. package/docs/using-with-github-copilot.en.md +25 -10
  23. package/docs/using-with-github-copilot.md +21 -8
  24. package/lib/doc-shapes.json +997 -0
  25. package/lib/doctor-checks.js +2654 -0
  26. package/lib/init.js +3583 -107
  27. package/lib/render-diagrams.js +1474 -0
  28. package/lib/render.js +865 -49
  29. package/package.json +2 -2
  30. package/templates/brownfield/references/drift-verification.md +4 -0
  31. package/templates/brownfield/references/finish-feature-flow.md +635 -88
  32. package/templates/brownfield/references/finish-feature-follow-up.md +60 -0
  33. package/templates/brownfield/references/finish-feature-minimal-host.md +406 -0
  34. package/templates/brownfield/references/finish-feature-post-hoc-hotfix.md +95 -0
  35. package/templates/brownfield/references/git-integration.md +160 -15
  36. package/templates/brownfield/references/init-project-flow.md +26 -4
  37. package/templates/brownfield/references/modify-existing-flow.md +412 -87
  38. package/templates/brownfield/references/modify-existing-follow-up.md +121 -0
  39. package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +82 -0
  40. package/templates/brownfield/references/new-feature-flow.md +61 -6
  41. package/templates/brownfield/references/new-phase-flow.md +57 -7
  42. package/templates/brownfield/references/pr-review-checklist.md +303 -10
  43. package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +158 -34
  44. package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +1 -1
  45. package/templates/brownfield/scaffolding/Git-principles-gitflow.md +75 -6
  46. package/templates/brownfield/scaffolding/Git-principles-trunk.md +82 -8
  47. package/templates/brownfield/scaffolding/_conventions.md +50 -28
  48. package/templates/brownfield/scaffolding/_overview.md +1 -0
  49. package/templates/brownfield/templates/_index.md +151 -7
  50. package/templates/brownfield/templates/analysis.md +79 -0
  51. package/templates/brownfield/templates/behavior.md +1 -0
  52. package/templates/brownfield/templates/context-definition.md +1 -0
  53. package/templates/brownfield/templates/context-map.md +2 -1
  54. package/templates/brownfield/templates/glossary.md +1 -0
  55. package/templates/brownfield/templates/lightweight-spec.md +154 -11
  56. package/templates/brownfield/templates/models.md +1 -0
  57. package/templates/brownfield/templates/phase-spec.md +9 -1
  58. package/templates/brownfield/templates/rules.md +1 -0
  59. package/templates/brownfield/templates/tech-debt.md +1 -0
  60. package/templates/common/references/ddd-modeling-guide.md +33 -16
  61. package/templates/{greenfield → common}/references/dflow-feedback-flow.md +2 -1
  62. package/templates/common/references/flow-rationale-registry.md +130 -0
  63. package/templates/common/skill/SKILL.md +13 -11
  64. package/templates/greenfield/references/drift-verification.md +4 -0
  65. package/templates/greenfield/references/finish-feature-flow.md +625 -89
  66. package/templates/greenfield/references/finish-feature-follow-up.md +60 -0
  67. package/templates/greenfield/references/finish-feature-minimal-host.md +363 -0
  68. package/templates/greenfield/references/finish-feature-post-hoc-hotfix.md +95 -0
  69. package/templates/greenfield/references/git-integration.md +148 -15
  70. package/templates/greenfield/references/init-project-flow.md +28 -8
  71. package/templates/greenfield/references/modify-existing-flow.md +378 -85
  72. package/templates/greenfield/references/modify-existing-follow-up.md +103 -0
  73. package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +82 -0
  74. package/templates/greenfield/references/new-feature-flow.md +67 -4
  75. package/templates/greenfield/references/new-phase-flow.md +56 -7
  76. package/templates/greenfield/references/pr-review-checklist.md +287 -8
  77. package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +153 -32
  78. package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +5 -2
  79. package/templates/greenfield/scaffolding/Git-principles-gitflow.md +74 -6
  80. package/templates/greenfield/scaffolding/Git-principles-trunk.md +88 -12
  81. package/templates/greenfield/scaffolding/_conventions.md +50 -28
  82. package/templates/greenfield/scaffolding/_overview.md +6 -2
  83. package/templates/greenfield/templates/_index.md +137 -7
  84. package/templates/greenfield/templates/aggregate-design.md +1 -0
  85. package/templates/greenfield/templates/analysis.md +79 -0
  86. package/templates/greenfield/templates/behavior.md +1 -0
  87. package/templates/greenfield/templates/context-definition.md +1 -0
  88. package/templates/greenfield/templates/context-map.md +2 -1
  89. package/templates/greenfield/templates/events.md +4 -1
  90. package/templates/greenfield/templates/glossary.md +1 -0
  91. package/templates/greenfield/templates/lightweight-spec.md +154 -11
  92. package/templates/greenfield/templates/models.md +1 -0
  93. package/templates/greenfield/templates/phase-spec.md +9 -1
  94. package/templates/greenfield/templates/rules.md +1 -0
  95. package/templates/greenfield/templates/tech-debt.md +1 -0
  96. package/templates/brownfield/references/dflow-feedback-flow.md +0 -251
@@ -2,10 +2,13 @@
2
2
  id: BUG-{NUMBER} # bug-type T2 only; a non-bug T2 (lightweight-{date}-{slug}.md) carries no id — the filename identifies it
3
3
  title: {簡述問題}
4
4
  status: in-progress # in-progress | completed
5
- bounded-context: {ContextName}
5
+ bounded-context: {ContextName} # or `none` when the host has no meaningful bounded context
6
6
  created: {YYYY-MM-DD}
7
- branch: bugfix/BUG-{NUMBER}-{slug}
7
+ branch: bugfix/BUG-{NUMBER}-{slug} # must equal the host _index.md `branch:` value (that field is authoritative) — a non-bug T2 therefore carries the host's feature/{SPEC-ID}-{slug} branch
8
+ # hotfix-branch: hotfix/{name} # ADD (uncomment) only for a post-hoc T2 — references/modify-existing-flow.md Step 1.8. The already-merged emergency fix's own branch, kept even after that branch was deleted.
9
+ # hotfix-identity: {PR / incident / tracker reference} # ADD only for a post-hoc T2 — the source the asserted `reconciled ({merged-hotfix-hash})` identity rests on; an uncited hash blocks closeout. Keep it adjacent to hotfix-branch.
8
10
  ---
11
+ <!-- dflow-shape: brownfield/lightweight-spec.md 1 — keep this line: dflow doctor reads it -->
9
12
 
10
13
  <!--
11
14
  Template note (for AI):
@@ -16,7 +19,7 @@ Template note (for AI):
16
19
  - T1 Heavy → use templates/phase-spec.md instead
17
20
  - T2 Light → THIS template; produces an independent file
18
21
  - T3 Trivial → no independent file; just one inline row in _index.md
19
- Lightweight Changes with a tag like [cosmetic] / [text] / [format]
22
+ Lightweight Changes with a tag like [cosmetic] / [text] / [appearance]
20
23
 
21
24
  Instance file location and naming:
22
25
  Place the instantiated file inside the corresponding feature directory:
@@ -24,17 +27,136 @@ Template note (for AI):
24
27
  or, when the lightweight change is a tracked bug:
25
28
  dflow/specs/features/active/{SPEC-ID}-{slug}/BUG-{NUMBER}-{slug}.md
26
29
 
27
- If the change is a standalone bug not yet attached to any existing
28
- feature, /dflow:bug-fix must first create a feature directory (with a
29
- minimal _index.md) before placing the lightweight-spec instance inside.
30
- This keeps the structure invariant: every spec file lives under some
31
- feature directory.
32
-
33
- After finalizing this lightweight-spec, AI must:
30
+ A standalone change not yet attached to any existing feature needs a
31
+ minimal host feature directory (with a minimal _index.md) to hold this
32
+ instance — the structure invariant is that every spec file lives under
33
+ some feature directory. That minimal host has a defined create / record /
34
+ close-out lifecycle: references/modify-existing-flow.md Step 1.7 for a
35
+ standalone change, or its Step 1.6 minimal variant when the change is a
36
+ follow-up on a completed feature; a post-hoc hotfix reaches the same
37
+ lifecycle through Step 1.8, which resolves its linkage to one of those two.
38
+ Follow that lifecycle rather than
39
+ improvising one, and do not leave an empty host behind.
40
+
41
+ No-BR variants (a T2 that carries no BR delta at all):
42
+ The default shape below is the classic BR-delta form (Behavior Delta with
43
+ BR-NN entries + Root Cause). It stays valid and is the right shape whenever
44
+ the change does have a BR delta.
45
+ A change can reach T2 carrying no BR delta at all. These families describe
46
+ how to write a spec the cascade has ALREADY placed at T2; none of them
47
+ makes a change T2, and none of them overrides an earlier cascade step. Do
48
+ NOT invent a BR-NN and do NOT write a fake root cause. Pick the matching
49
+ family, put its BR line under `## Behavior Delta` in place of the
50
+ ADDED / MODIFIED / REMOVED / RENAMED subsections, and replace `## Root Cause`
51
+ with that family's evidence section:
52
+
53
+ (a) presentation — any copy / appearance change T3 would not take: a sweep
54
+ beyond the local unit, a whole-screen rewrite, a local edit to
55
+ high-consequence content (a security warning, a password hint,
56
+ consent text, a payment or legal notice), or one that changes what the output *means* (an
57
+ instruction reversed from "company email only" to "any email", a
58
+ danger / status colour flipped)
59
+ BR line: `BR: none — presentation`
60
+ Evidence: `## Output Footprint` — which screens / outputs the change
61
+ actually reaches, and **what it now says or signals** whenever the
62
+ meaning moved or the content is high-consequence
63
+ (b) contract change — non-breaking structured-log / export / API / event
64
+ field or semantics change
65
+ BR line: `BR: none — contract change`
66
+ Evidence: `## Contract Delta`, including a `**Downstream consumers**:`
67
+ line naming who reads it
68
+ (c) operational — CVE / security dependency bump, or a behavior-preserving
69
+ refactor on an auth / payment / resilience / compliance path
70
+ BR line: `BR: none — operational`
71
+ Evidence: `## Operational Rationale`, including a `**Trace**:` line
72
+ (advisory / ticket / audit reference; a self-initiated case with nothing
73
+ to cite records `**Trace**: none — self-initiated` — the line is never
74
+ omitted)
75
+ (d) performance — a runtime performance / resource / SLA change, including
76
+ one no audience perceives
77
+ BR line: `BR: none — performance`
78
+ Evidence: `## Performance Delta`, including an
79
+ `**SLA / resource context**:` line
80
+ (e) implementation defect — the rule is unchanged; the implementation was
81
+ wrong
82
+ BR lines: `BR Delta: none — implementation defect` AND
83
+ `Governing BR-IDs: {BR-NN, BR-NN | none}`
84
+ Evidence: keep `## Problem` / `## Root Cause` / `## Fix Approach`, plus a
85
+ regression task
86
+ (f) intentional change — a planned, non-normative functional or interaction
87
+ change with no rule in the catalogue (default tab, redirect target,
88
+ ordering, a presentation / interaction control)
89
+ BR line: `BR: none — intentional change`
90
+ Evidence: `## Change Rationale`, including `**Before**` / `**After**`
91
+ behavior lines and a `**Regression**:` line
92
+
93
+ More than one family can fit — a behaviour-preserving payment-retry refactor
94
+ that also shifts runtime SLA is both (c) and (d). Do not pick one and drop
95
+ the other's evidence: write the BR line of the family with the higher
96
+ consequence (order: (e) → (c) → (b) → (d) → (f) → (a)) and include **every**
97
+ matching family's evidence section. A reviewer needs the operational trace
98
+ and the SLA context, not whichever one the author chose first.
99
+
100
+ Family (e) keeps two fields on purpose: "no BR delta" is not "no governing
101
+ rule". `Governing BR-IDs:` lists the rules the defect sits under, so the rule
102
+ the fix answers to stays traceable; a genuinely uncatalogued defect records
103
+ `none`. Never collapse the pair into a single `BR: none`.
104
+ Family (f) is non-normative only. A new normative constraint (permission /
105
+ eligibility / threshold / approval / required outcome) is a new BR — take it
106
+ back to the cascade rather than filing it here, even when the catalogue has
107
+ no BR-ID for it yet.
108
+
109
+ Legacy shapes are accepted, never migrated. Readers and gates take both the
110
+ classic BR-delta form and the older single `BR:` line bug form (written
111
+ before the `BR Delta:` / `Governing BR-IDs:` split) as they are; leave
112
+ existing specs alone and use the shapes above for new ones.
113
+
114
+ After drafting this lightweight-spec, AI must:
34
115
  1. Add an outbound-link row to the feature's _index.md Lightweight Changes table
35
- (Tier = T2; description includes the link to this file)
116
+ (Tier = T2; description includes the link to this file).
117
+ TIMING — do this BEFORE the implementation commit, not after. On a
118
+ minimal (zero-phase) host the row must already be inside checkpoint 1,
119
+ and closeout's allow-list admits only the row's `Commit` cell
120
+ afterwards, so a row added later blocks. "Drafting", not the Step 1.7
121
+ "Finalize + close" sub-step, which runs after that commit and only fills
122
+ the cell in.
36
123
  2. Refresh the feature's _index.md Current BR Snapshot table to reflect
37
124
  any BR ADDED / MODIFIED / REMOVED / RENAMED in this lightweight-spec
125
+ (a no-BR family has nothing to refresh — leave the snapshot as it is)
126
+
127
+ Implementation Paths:
128
+ The `## Implementation Paths` section below names the source paths this
129
+ change touches. Paths, not a diff — enough to compare a commit against.
130
+ REQUIRED on a minimal (zero-phase) host — standalone or follow-up:
131
+ `/dflow:finish-feature` asserts that checkpoint 1's diff touches them, so a
132
+ spec declaring none leaves that check nothing to compare and a missing
133
+ declaration BLOCKS closeout rather than passing vacuously
134
+ (references/finish-feature-flow.md Step 1; written at
135
+ references/modify-existing-flow.md Step 1.7, before the first commit).
136
+ On a HOSTED T2 — one recorded under a phase-bearing feature — closeout runs
137
+ no such check and nothing blocks on it, so the section is good practice
138
+ there, not a gate. Do not read the "blocks" above as applying to a hosted
139
+ spec; it does not.
140
+ Keep the paths OUT of `## Implementation Tasks`: the completion checklist
141
+ may collapse or remove that section once the tasks are done, and the paths
142
+ must still be readable at closeout.
143
+
144
+ Post-hoc hotfix fields — T2 only, references/modify-existing-flow.md Step 1.8:
145
+ `hotfix-branch:` records the already-merged emergency fix's own branch
146
+ (keep the value even when that branch has been deleted); `hotfix-identity:`
147
+ cites what the identity claim rests on — the PR, incident, or tracker
148
+ reference. Both live in the frontmatter, adjacent, so the trace and the
149
+ thing it rests on stay together. They ship COMMENTED OUT: an ordinary
150
+ (non-post-hoc) T2 leaves them commented and they are inert. Uncomment them
151
+ only in post-hoc mode — never fill them with `none`, and never leave them
152
+ live on an ordinary T2, which would claim a hotfix branch that never
153
+ existed on a record nothing else re-checks.
154
+ In post-hoc mode `## Implementation Paths` names the paths the MERGED
155
+ HOTFIX touched: closeout compares them against the `reconciled ({hash})`
156
+ commit, and blocks if this host's own documentation commit touches any of
157
+ them (that would be re-implementation, not reconciliation). Closeout tests
158
+ plausibility only and blocks on an uncited hash; the identity itself is
159
+ confirmed by references/pr-review-checklist.md.
38
160
  -->
39
161
 
40
162
  # {問題簡述}
@@ -46,6 +168,8 @@ Template note (for AI):
46
168
  ## Behavior Delta
47
169
 
48
170
  > 精簡 delta 格式:bug fix 多數只需 MODIFIED;若確實是新增規則可改用 ADDED、移除用 REMOVED、改名用 RENAMED。多項變更時照類別列。
171
+ >
172
+ > 完全沒有 BR delta 時改用 no-BR 家族形(見檔首 Template note 的 No-BR variants):本段只留該家族的 BR 行(例如 `BR: none — presentation`;家族 (e) 是 `BR Delta:` + `Governing BR-IDs:` 兩行),不要為了填滿 delta 子段捏造 BR-NN。
49
173
 
50
174
  ### MODIFIED - behavior modified in this fix
51
175
  #### Rule: BR-NN {規則名稱}
@@ -58,12 +182,31 @@ Template note (for AI):
58
182
 
59
183
  ## Root Cause
60
184
 
185
+ > classic BR-delta 形與 no-BR 家族 (e) 保留本段;家族 (a)–(d)、(f) 以該家族的 evidence 段取代本段(見檔首 Template note 的 No-BR variants)。
186
+
61
187
  {為什麼會這樣?是邏輯錯誤?資料問題?還是需求理解有誤?}
62
188
 
63
189
  ## Fix Approach
64
190
 
65
191
  {怎麼修?有沒有抽到 Domain 層的機會?}
66
192
 
193
+ ## Implementation Paths
194
+
195
+ > The source paths this change touches — paths, not a diff, at a granularity a
196
+ > commit can be compared against. **Required on a minimal (zero-phase) host**:
197
+ > `/dflow:finish-feature` asserts checkpoint 1's diff touches them, and
198
+ > declaring none **blocks** closeout instead of passing. On a **hosted** T2 —
199
+ > recorded under a phase-bearing feature — closeout runs no such check, so this
200
+ > is good practice there rather than a gate. Do not move the list into
201
+ > `## Implementation Tasks`: that section may be collapsed once the tasks are
202
+ > done, and these paths must still be readable at closeout.
203
+ >
204
+ > **Post-hoc hotfix (Step 1.8)**: list what the **already-merged hotfix**
205
+ > touched. This host's own documentation commit must **not** touch any of them.
206
+
207
+ - `{src/path/touched}`
208
+ - `{src/another/path/touched}`
209
+
67
210
  <!-- dflow:section implementation-tasks -->
68
211
  ## Implementation Tasks
69
212
 
@@ -1,3 +1,4 @@
1
+ <!-- dflow-shape: brownfield/models.md 1 — keep this line: dflow doctor reads it -->
1
2
  <!-- Seeded by Dflow. -->
2
3
  <!-- Formatting convention: keep table cells concise. When one cell holds multiple short items (invariants, rules, steps), separate them with <br> so each renders on its own line - never chain them into one line with ;/; separators. Long narrative detail does not belong in a table cell: keep the cell to a concise summary and put extended detail in an existing section of this document when one fits, or give each item its own row. -->
3
4
 
@@ -7,6 +7,7 @@ created: {YYYY-MM-DD}
7
7
  author: {developer-name}
8
8
  branch: feature/{SPEC-ID}-{slug}
9
9
  ---
10
+ <!-- dflow-shape: brownfield/phase-spec.md 1 — keep this line: dflow doctor reads it -->
10
11
 
11
12
  <!-- Formatting convention: keep table cells concise. When one cell holds multiple short items (invariants, rules, steps), separate them with <br> so each renders on its own line - never chain them into one line with ;/; separators. Long narrative detail does not belong in a table cell: keep the cell to a concise summary and put extended detail in an existing section of this document when one fits, or give each item its own row. -->
12
13
 
@@ -17,7 +18,14 @@ Template note (for AI):
17
18
  This is the **phase-spec** template - one phase-spec captures one full
18
19
  "Kickoff -> Domain -> Design -> Build -> Verify" cycle inside a feature directory.
19
20
  A feature can have 1..N phase-specs; the feature-level dashboard lives in
20
- the sibling `_index.md` (see templates/_index.md). The instance file name is
21
+ the sibling `_index.md` (see templates/_index.md).
22
+ Exception — a **minimal (zero-phase) host** has **0**: it records a small
23
+ standalone or follow-up change and is defined by carrying no phase-spec at
24
+ all (references/modify-existing-flow.md Step 1.7, or its Step 1.6 minimal
25
+ variant). Such a host never uses this template. If you are about to create a
26
+ phase-spec for one, stop: adding a phase makes it a phase-bearing feature,
27
+ and closeout will then check it as one.
28
+ The instance file name is
21
29
  `phase-spec-YYYY-MM-DD-{slug}.md` placed at
22
30
  `dflow/specs/features/active/{SPEC-ID}-{slug}/`.
23
31
 
@@ -1,3 +1,4 @@
1
+ <!-- dflow-shape: brownfield/rules.md 1 — keep this line: dflow doctor reads it -->
1
2
  <!-- Seeded by Dflow. -->
2
3
  <!-- Formatting convention: keep table cells concise. When one cell holds multiple short items (invariants, rules, steps), separate them with <br> so each renders on its own line - never chain them into one line with ;/; separators. Long narrative detail does not belong in a table cell: keep the cell to a concise summary and put extended detail in an existing section of this document when one fits, or give each item its own row. -->
3
4
 
@@ -1,3 +1,4 @@
1
+ <!-- dflow-shape: brownfield/tech-debt.md 1 — keep this line: dflow doctor reads it -->
1
2
  <!-- Seeded by Dflow. -->
2
3
  <!-- Formatting convention: keep table cells concise. When one cell holds multiple short items (invariants, rules, steps), separate them with <br> so each renders on its own line - never chain them into one line with ;/; separators. Long narrative detail does not belong in a table cell: keep the cell to a concise summary and put extended detail in an existing section of this document when one fits, or give each item its own row. -->
3
4
 
@@ -76,10 +76,10 @@ never cross:
76
76
  BR-ID in `rules.md` and a scenario in `behavior.md`; `/dflow:verify` works
77
77
  the same. What shrinks is the *domain model's thickness*, not the spec's
78
78
  existence.
79
- 2. **The Tier system is unchanged.** A bug fix in a generic context is still a
80
- T2; a new feature still runs the flow. Subdomain governs *how deep to model
81
- a context*; Tier governs *how much ceremony a single change needs* — they
82
- are orthogonal.
79
+ 2. **The Tier system is unchanged.** A bug fix's tier is whatever the cascade
80
+ decides by the change itself (T3 / T2 / T1), not by the subdomain; a new
81
+ feature still runs the flow. Subdomain governs *how deep to model a context*;
82
+ Tier governs *how much ceremony a single change needs* — they are orthogonal.
83
83
  3. **Criticality is not downgraded.** Security, tests, SLA, audit,
84
84
  permissions, and data integrity do **not** drop with the subdomain type —
85
85
  auth or notifications can be generic *and* mission-critical. Generic lowers
@@ -119,9 +119,14 @@ or value objects directly.
119
119
  1. **Protect invariants** — The Aggregate exists to enforce business rules that span
120
120
  multiple objects within it.
121
121
 
122
- 2. **One Aggregate per transaction** — A single operation should modify only ONE
123
- Aggregate. If you need to modify two Aggregates, use Domain Events for eventual
124
- consistency.
122
+ 2. **One Aggregate per transaction — by default** — A single operation should
123
+ normally modify one Aggregate. If it appears to need multiple roots, first
124
+ challenge whether the boundary is wrong and whether Domain Events with eventual
125
+ consistency can separate the work. If a multi-root local transaction remains
126
+ justified, record the domain rationale, the shared atomic boundary / operational
127
+ coupling, and when to re-evaluate the choice. A local database transaction cannot
128
+ make an external provider or service fact atomic with local state; that workflow
129
+ still needs idempotency plus reliable delivery / retry or compensation.
125
130
 
126
131
  3. **Reference other Aggregates by ID only** — Never hold a direct object reference
127
132
  to another Aggregate. Store its ID instead.
@@ -479,9 +484,10 @@ the values that changed — not a snapshot of the whole Aggregate.
479
484
  event** decision (record the Delivery expectation in `events.md`; Brownfield —
480
485
  see the Edition note) and treat
481
486
  the payload as a published contract: add fields if you must, never change
482
- their meaning. Ceremony-wise, extending an event payload that crosses
483
- contexts is a T1 contract change (see the ceremony examples in
484
- `_conventions.md`).
487
+ their meaning. Ceremony-wise it follows the contract axis of the ordered
488
+ cascade — classify the change in AI-AGENT-GUIDE.md § Ceremony Scaling, which
489
+ is where breaking versus non-breaking and the resulting tier are decided; a
490
+ project's own `_conventions.md` may escalate from there.
485
491
 
486
492
  ### Event Flow
487
493
 
@@ -673,13 +679,19 @@ build. (Read model ≠ event sourcing.)
673
679
  ## Domain Services
674
680
 
675
681
  Use Domain Services for operations that:
676
- - Involve multiple Aggregates (read-only access to the second Aggregate)
682
+ - Require a domain decision spanning information from multiple Aggregates
683
+ (read-only access by default)
677
684
  - Require external information (through interfaces) to make domain decisions
678
685
  - Don't naturally belong to any single Entity
679
686
 
680
- A Domain Service can make a **stateless** cross-Aggregate decision. It is
681
- not a home for process progress, retries, deadlines, or compensation state.
682
- When the flow has state, use the "Long-Running Processes" ladder.
687
+ A Domain Service expresses a **stateless domain operation or decision** and may
688
+ read multiple Aggregate roots. A justified multi-root mutation is rare rather
689
+ than automatically invalid: it must trigger a boundary / transaction review and
690
+ the service itself must carry a real domain rule or decision. A service that only
691
+ orders calls to multiple roots is Application-layer orchestration in the wrong
692
+ place. A Domain Service is not a home for process progress, retries, deadlines,
693
+ or compensation state. Application code owns ordinary workflow sequencing; when
694
+ the flow has business-visible state, use the "Long-Running Processes" ladder.
683
695
 
684
696
  ```csharp
685
697
  // Domain Service — in Domain layer
@@ -791,8 +803,13 @@ The less-obvious three — when to reach for them:
791
803
  4. **Business logic in Infrastructure** — Rules in SQL queries or EF configurations
792
804
  → Domain defines WHAT, Infrastructure defines HOW
793
805
 
794
- 5. **Direct cross-Aggregate modification** — One command modifying two Aggregates
795
- → Use Domain Events for the second Aggregate
806
+ 5. **Unreviewed direct cross-Aggregate modification** — One command modifies
807
+ multiple Aggregate roots without challenging the boundaries or recording why
808
+ one local transaction is justified
809
+ → Prefer Domain Events and eventual consistency; if the local-transaction
810
+ exception is justified, record the rationale and re-evaluation condition from
811
+ Aggregate Design Rule 2. External provider / service facts are never atomic
812
+ with the local database transaction.
796
813
 
797
814
  6. **Set-based invariant guarded only in memory** — A "unique" / "only one active"
798
815
  rule enforced solely by an in-app check, with no DB unique constraint or
@@ -77,7 +77,8 @@ Allowed evidence:
77
77
  - Dflow command name
78
78
  - Dflow version if known
79
79
  - Template or reference file name
80
- - Generic project type, such as "existing legacy presentation-framework app"
80
+ - Generic project type, such as "existing legacy presentation-framework app" or
81
+ "new internal service"
81
82
  - Minimal paraphrased symptom
82
83
  - Short sanitized snippets from Dflow-owned files
83
84
 
@@ -0,0 +1,130 @@
1
+ # Flow Rationale Registry
2
+
3
+ Why the rules in the workflow flow files and templates are shaped the way they
4
+ are. One entry per physical line, so one lookup returns the whole answer:
5
+
6
+ ```bash
7
+ grep -E '^R-FF-BRANCH-02:' references/flow-rationale-registry.md
8
+ ```
9
+
10
+ **Do not read this file whole.** It is a lookup table, not a document. Nothing
11
+ here is needed to execute a flow. Look up an entry when a developer asks why a
12
+ rule exists, or says a rule looks wrong.
13
+
14
+ Every line reads `<id>: [<edition ·>? <file> · <step or section> · <the rule it
15
+ belongs to>]` followed by the reason, so a keyword search finds the line when
16
+ you do not have an id. **Search a short fragment — four to eight words — not a whole sentence.**
17
+ The flow files are hard-wrapped, so most sentences there span a line break and
18
+ carry an indent that this file's single long lines do not have; a long phrase
19
+ copied out of a flow file will therefore match nothing here.
20
+
21
+ **Do not add `-m 1`.** A bracket that opens with `gf ·` or `bf ·` applies to
22
+ that edition only — usually one line per edition of the same rule, sometimes a
23
+ rule only one edition has. A bracket with no edition marker applies to both. So
24
+ a lookup can legitimately return two lines: read the one matching this project's
25
+ edition, which the workflow bundle's own `.dflow-bundle-manifest.json` records
26
+ in its `edition` field. Taking only the first would hand a Brownfield project
27
+ the Greenfield answer — the `gf ·` line is always first.
28
+
29
+ **Reviewers and future maintainers: read this before concluding that a check is
30
+ under-specified.** Several of these rules look loose on their own and are
31
+ bounded deliberately, with the boundary recorded here.
32
+
33
+ ## finish-feature-flow.md
34
+
35
+ R-FF-MINSEL-01: [finish-feature-flow.md · Step 1 · What "Minimal host (zero-phase) only" selects, and what that does not prove] The routing rules are what keep the two apart in practice: a T1 never records into a minimal host, and a sealed minimal host cannot take anything further.
36
+
37
+ R-FF-ORPHAN-01: [finish-feature-flow.md · Step 1 · "And the other direction, for every host shape"] The checks around this one walk rows → files, so a file no row names is otherwise never examined. Deliberately **not** scoped to a host shape: an unreferenced `phase-spec-*` is precisely what makes a host read as *phase-bearing* to the minimal-host selector below, so a shape-scoped version of this check could never be the one that sees it.
38
+
39
+ R-FF-BRANCH-01: [finish-feature-flow.md · Step 1 · "The row must name *this* branch"] "An override happened once" would excuse closing out on any branch at all, which is not what the option sanctions; requiring the row to name where you actually are defeats that without over-blocking.
40
+
41
+ R-FF-BRANCH-02: [finish-feature-flow.md · Step 1 · "You are on this host's branch — or on a branch this host recorded a sanctioned override for"] ⚠ **Deliberately not "the most recent override".** A host can override onto `develop` for one phase and onto `spike/x` for the next, then close out on `develop` — and closeout fires no branch gate of its own, so no newer row is ever written. Keying on the most recent row would block a branch this host recorded a sanctioned override for, which is the same false block this clause exists to remove.
42
+
43
+ R-FF-BRANCH-03: [finish-feature-flow.md · Step 1 · "When it passes this way, say so in the conversation"] A gate that silently accepts an exception teaches the developer nothing about why it passed.
44
+
45
+ R-FF-BRSNAP-01: [finish-feature-flow.md · Step 1 · "Current BR Snapshot table is non-empty", on a phase-bearing T1 whose phase-specs establish no BR delta] (genuinely new work is `/dflow:new-feature` "even with no new BR / Domain / schema", `AI-AGENT-GUIDE.md` § Ceremony Scaling step 0, and `new-feature-flow.md` Step 4 initialises the Snapshot from planned BRs that may legitimately be none)
46
+
47
+ R-FF-BASELINE-01: [finish-feature-flow.md · Step 1 · "The `-w` is mandatory"] Without `-w`, `git hash-object` computes the id and never stores the blob, so the baseline's **content** is unrecoverable afterwards — `git cat-file -p` on that id fails with `Not a valid object name`, leaving Step 4 holding fingerprints for a comparison that needs the text. It is also what makes Step 1's "captured rather than remembered" true of the **content** and not only of the id. ⚠ **These blobs are unreferenced, and the ref does not change that.** A ref pointing at the list keeps *the list* reachable; the ids written inside it are text, not object pointers, so reachability does not follow them — measured: `git rev-list --objects --no-walk {list blob}` returns one object, and a `git gc --prune=now` after anchoring the ref removes the per-file blobs while the list survives. What bounds their lifetime is git's own prune grace (`gc.pruneExpire`, two weeks by default), which a closeout does not outlast even when it spans sessions. ⚠ The blobs most needed are the least covered: a path whose content is **unchanged** between Step 1 and the commit is reachable from the commit anyway, while `_index.md` — the one every ordered edit touches — is reachable from neither the commit nor the ref.
48
+
49
+ R-FF-BASELINE-04: [finish-feature-flow.md · Step 1 · "Both halves of that command are load-bearing"] The capture has to name the same set `git add` will stage, and it can miss in **both** directions. An **ignored** file captured here enters the baseline as a path the span can never carry — `git add` skips it too — so it reads as a file deleted after Step 1. An **untracked but not ignored** file *omitted* here is the mirror image: `git add {dir}` does stage it, so it arrives in the span with no baseline entry and reads as a file added after Step 1. Both block, and neither is clearable — by the time either surfaces the host is archived and the closeout committed. `--cached --others --exclude-standard` is the set `git add` itself works from, so the two are the same predicate evaluated at two times rather than two sets that happen to agree. ⚠ The second direction is not hypothetical: greenfield's `aggregate-design.md` worksheet is ordered into the host by `AI-AGENT-GUIDE.md` § Ceremony Scaling and may never have been committed.
50
+
51
+ R-FF-BASELINE-05: [finish-feature-flow.md · Step 1 · "That ref is the baseline"] The `-w` made the blobs durable but left the `path → blob` list — the only way to address them — stated in the conversation, two sentences after the same paragraph called that store unreliable. Closeout is expected to cross sessions: `templates/_index.md`'s Resume Pointer says to read it when continuing in a new conversation, and Step 2 writes a cursor for "the next session". A ref fixes the addressing rather than shrinking it: the name is **recomputed** from `{SPEC-ID}-{slug}`, which the next session already knows from the host it is closing, so nothing about the baseline has to survive the conversation. It also makes the list itself an object in the database rather than a conversational artefact. ⚠ **It does not extend reachability to the blobs it names** — see `R-FF-BASELINE-01` for what actually bounds those. `refs/dflow/**` is outside the default push, fetch and clone refspecs; an explicit refspec or `clone --mirror` does carry it, which is why the step says not to add one.
52
+
53
+ R-FF-BASELINE-06: [finish-feature-flow.md · Step 4 · "First, confirm the baseline is still readable"] The post-commit item below reads the same ref, so this looks redundant — the difference is entirely **where the failure leaves you**. Below, the `git mv` has happened and the closeout is committed: the host is in `completed/`, the tree Step 1 read cannot be rebuilt, and the section's own "fix it and re-verify" has nothing to act on. Detected here the host is still in `active/` with nothing committed, so the remedy is one step. A check whose failure has no repair procedure is a report, not a gate; this moves the detection to where it is a gate.
54
+
55
+
56
+ R-FF-BASELINE-08: [finish-feature-flow.md · Step 4 post-commit verification · "release the baseline anchor"] The ref is deleted rather than left behind because it is scaffolding, and an adopter repo should not accumulate one per closed feature. It is deleted **after** the items pass, not before: while it exists a failed item can be fixed and re-verified against the same evidence, and releasing it first would discard the only copy of what the comparison needs. Deleting a ref changes no file, so it is not a difference the comparison above could see and nothing there needs to admit it.
57
+
58
+ R-FF-COMMITCELL-03: [finish-feature-flow.md · Step 4 post-commit verification · "An empty hosted `Commit` cell does not fail this item"] The obvious repair — have closeout check the cell it is reading anyway — is the wrong one. `references/pr-review-checklist.md`'s "Every Lightweight Changes row now carries a `Commit` hash" item states its own reason for existing as *"That is why closeout does not assert this for a phase-bearing host, and why it is checkable here"*, so making closeout assert it falsifies the other half of a documented division of labour and leaves the same rule written in two places. What was wrong was never the division; it was that the clause read as a **ruling** that an empty cell is acceptable, stated at the moment closeout is *entered*, while the item reading it runs against the **committed** blob after instruction 1 has already ordered the backfill.
59
+
60
+ R-FF-CURSOR-01: [finish-feature-flow.md · Step 2 · "Also update the **Resume Pointer** — to the state closeout is actually in"] The terminal value used to be written here, four steps before closeout ends. While the host is still in `active/` the global scans do read this cursor, so a `none` written at Step 2 made the flow's own gate 3 → 4 ask the developer for `/dflow:next` — a command `AI-AGENT-GUIDE.md` requires to be refused when no workflow is active — and it disabled `/dflow:cancel` for a closeout that was genuinely still running. An honest in-progress value removes both and costs nothing: the field is a claim, and a claim that closeout has finished is false until it has.
61
+
62
+ R-FF-CURSOR-02: [finish-feature-flow.md · Step 4 · "The `git mv` and this write are one uninterruptible pair"] Two decisions, and the second is a deliberate narrowing. **Why it is bound to the `git mv` rather than to the commit:** the archive is what moves the host out of `active/`, so from that instant the cursor is read by one reader (the PR reviewer `references/pr-review-checklist.md` sends to it) instead of by the global scans; writing before the Y / N prompt closes the only window where the host sits in `completed/` while the cursor still declares the workflow active, which is the window `/dflow:cancel` would act on. **Why closeout does not restore it when the commit is declined, fails, or post-commit verification reports `✗`:** a restore cannot buy back findability, because `git mv` is Step 4's first action and `/dflow:status` reads only `active/` — the host is already gone from the only place a restored cursor would be seen. The rule was written and then removed: it bought nothing and cost an index / working-tree synchronisation contract on three failure branches. The accepted residual risk is that a closeout failing after this point is visible only in `git status`.
63
+
64
+ R-FF-GITMV-01: [finish-feature-flow.md · Step 4 · "`git mv` is mandatory — never use plain `mv` + `git add`"] This preserves git's directory rename detection so `git log --follow` / `git blame` / PR diff quality stays intact across the move.
65
+
66
+ R-FF-SWEEPKEY-01: [gf · finish-feature-flow.md · Step 4 instruction 2 · "Key it on the flow that produced the change, never on the flow that opened the host"] Those differ, and keying on the host is how the deadlock came back: `modify-existing-flow.md` Step 1.6 opens a **phase-bearing** host for a T1 follow-up, and Step 5.4 puts a T1 new-phase or a T2 lightweight *inside* a host opened by `/dflow:new-feature` — each of those runs Step 5.3 as its own sweep. Ask "which flow opened this host" and a hosted **T2** on a phase-bearing **no-BC** host has no arm at all: half (a) is empty because Step 3 is skipped, and the `glossary.md` rename its Step 5.3 made belongs to a sweep the question refused to look at. Unstaged it fails the clean-tree item; staged it is attributable to nothing. Deriving from *this change's* flow is the fix — not widening the set to "anything dirty".
67
+
68
+ R-FF-SWEEPKEY-01: [bf · finish-feature-flow.md · Step 4 instruction 2 · "Key it on the flow that produced the change, never on the flow that opened the host"] Those differ, and keying on the host is how the deadlock came back: `modify-existing-flow.md` Step 1.6 opens a **phase-bearing** host for a T1 follow-up, and Step 6.4 puts a T1 new-phase or a T2 lightweight *inside* a host opened by `/dflow:new-feature` — each of those runs Step 6.3 as its own sweep. Ask "which flow opened this host" and a hosted **T2** on a phase-bearing **no-BC** host has no arm at all: half (a) is empty because Step 3 is skipped, and the `glossary.md` rename its Step 6.3 made belongs to a sweep the question refused to look at. Unstaged it fails the clean-tree item; staged it is attributable to nothing. Deriving from *this change's* flow is the fix — not widening the set to "anything dirty".
69
+
70
+ R-FF-SWEEPKEY-02: [finish-feature-flow.md · Step 4 instruction 2 · "do not go looking for a list that host never produced"] ⚠ Keying this on the minimal-host allow-list (`finish-feature-minimal-host.md`) is what deadlocked the one shape where both halves are empty: a **phase-bearing, no-BC** host that swept a global document has nothing from Step 3 *and* no Step 1 allow-list, while this instruction still requires the delta staged and the post-commit path check would then reject it as unpermitted — two mandatory requirements that could not both be met.
71
+
72
+ R-FF-SPAN-01: [gf · finish-feature-flow.md · Step 4 post-commit verification · "The span is derived from what was staged, not from what the tables name"] Instruction 2 stages the archived directory **whole**, so a span built from the Phase Specs and Lightweight Changes rows is narrower than the commit — and Step 1 permits exactly one file no table names, the `aggregate-design.md` worksheet. Under the narrower span that file entered the commit and was compared against nothing. Keying on `git ls-tree` closes that for any such file, present or future, with no list to maintain.
73
+
74
+ R-FF-SPAN-01: [bf · finish-feature-flow.md · Step 4 post-commit verification · "The span is derived from what was staged, not from what the tables name"] Instruction 2 stages the archived directory **whole**, so a span built from the Phase Specs and Lightweight Changes rows is narrower than the commit. Greenfield's Step 1 permits one such file by name (an `aggregate-design.md` worksheet); brownfield has no counterpart today, and keying on `git ls-tree` means it needs no edit if one ever appears.
75
+
76
+ R-FF-BASELINE-02: [finish-feature-flow.md · Step 4 post-commit verification · "Blob ids alone answer only *same* / *different*"] The derived condition asks whether the committed blob differs from the baseline in **exactly** the ordered edits and nothing else, and id equality cannot express it: on the normal path the ids differ, because Step 2, Step 4's terminal Resume Pointer write and Step 4 instruction 1 all edit `_index.md` by design — so *different* is the expected reading and says nothing about **which** differences are present. Reading the delta is the only form that separates an ordered edit from edit fallout. Equality is still read, and it is not a pass on its own: identical ids mean that file was not touched at all — the ordinary case for the spec files in the host, and a failure for any path a step of this closeout ordered an edit to, because it says the ordered edit never landed.
77
+
78
+ R-FF-BASELINE-03: [finish-feature-flow.md · Step 4 post-commit verification · "Compare the two path sets first, before any blob comparison"] The `git mv` makes the two sets line up only while no file was added or removed between Step 1 and the commit, so the alignment is the thing to establish rather than the thing to assume — and neither kind of mismatch is reachable by the per-path comparison that follows: a removed file is never visited, because the span comes from `git ls-tree` on the commit, and an added one has no baseline blob to diff against. The other half of this check does not catch them either: the spill item admits the archived host directory **whole**, so a file added inside it is a permitted path. Left unstated, a host file deleted during closeout passes both halves in silence — the coarsest form of the edit fallout this check exists to block.
79
+
80
+ R-FF-POSTCOMMIT-01: [finish-feature-flow.md · Step 4 post-commit verification · "do not substitute `HEAD^`"] The parent commit is not the tree Step 1 read: the edits legitimately uncommitted at Step 1 would each surface as a difference no step ordered, and the check would block a correct closeout.
81
+
82
+ R-FF-POSTCOMMIT-02: [finish-feature-flow.md · Step 4 post-commit verification · "`git show HEAD:<path>` for each"] Reading the **committed** blob is what catches "the rename carried stale content" and "the row never made it into the commit".
83
+
84
+ R-FF-SPILL-01: [gf · finish-feature-flow.md · Step 4 post-commit verification · "The closeout commit contains only what closeout is allowed to write"] ⚠ **Keyed on instruction 2, and deliberately not on the minimal-host allow-list.** That allow-list exists only for a `Minimal host (zero-phase)` and lives in `finish-feature-minimal-host.md`; a phase-bearing host has none. Keying this item on it — with "plus whatever Step 3 wrote" as the other half — left a **phase-bearing, no-BC** host with **both** sources empty while instruction 2 still required its global sweep delta staged: stage it and this item rejected the path, skip it and the clean-tree item failed. Instruction 2 is the one statement that covers every host shape, which is why it is the source.
85
+
86
+ R-FF-SPILL-01: [bf · finish-feature-flow.md · Step 4 post-commit verification · "The closeout commit contains only what closeout is allowed to write"] ⚠ **Keyed on instruction 2, and deliberately not on the minimal-host allow-list.** That allow-list exists only for a `Minimal host (zero-phase)` and lives in `finish-feature-minimal-host.md`; a phase-bearing host has none. Keying this item on it — with "plus whatever Step 3 wrote" as the other half — left a **phase-bearing, no-BC** host with **both** sources empty (Step 3 is skipped for a no-BC host and for a baseline host) while instruction 2 still required its global sweep delta staged: stage it and this item rejected the path, skip it and the clean-tree item failed. Instruction 2 is the one statement that covers every host shape, which is why it is the source.
87
+
88
+ R-FF-SPILL-02: [finish-feature-flow.md · Step 4 post-commit verification · "Scope: this is a path-level spill check and nothing more"] A clean tree does not even cover the path level — it proves the changes were committed, not that only permitted ones were — and Step 4's staging instructions prove nothing on the developer-commit path.
89
+
90
+ R-FF-DERIV-01: [finish-feature-flow.md · Step 5 · "First, print the closeout verification's derivation"] **Why this is here.** The verification derives its admitted set instead of reading a list — that is what keeps the list and its sources from drifting apart — but it also means a reader cannot see the set by looking at the checklist. Printing the derivation keeps the flow inspectable instead of a box that answers `✓` without showing its working.
91
+
92
+ R-FF-INFLIGHT-01: [finish-feature-flow.md · Step 5 · "In-flight reminder"] Surfacing them at closeout is deliberate: attention is about to move elsewhere, and this is exactly where half-done work sinks.
93
+
94
+ ## finish-feature-minimal-host.md
95
+
96
+ R-FF-COMMITCELL-01: [finish-feature-minimal-host.md · Step 1 · "Do not collapse the middle case into the first"] it is the one that reads as *filled* to every rule written against empty / non-empty, which is exactly why it survives elsewhere.
97
+
98
+ R-FF-COMMITCELL-02: [finish-feature-minimal-host.md · Step 1 · "Deliberately not run on a phase-bearing host"] a hosted T3's row rides the host's *next* commit (`references/git-integration.md` § Commit checkpoints), and nothing requires a hosted row to declare implementation paths — so (c) has no defined input there, and demanding it would reject every hosted T3.
99
+
100
+ R-FF-BASELINECAP-01: [bf · finish-feature-minimal-host.md · Step 1 · "a `spec-baseline` checkpoint and a `Tier = baseline` row imply each other"] `modify-existing-flow.md` Step 1.7 states the contract: a baseline capture is observation-only, *there is no implementation work*, which is why its checkpoint is not named `implementation`. (1) and (2) alone are the ledger talking about itself; (3) is what makes the claim true of the commit.
101
+
102
+ R-FF-CKPTCOUNT-01: [finish-feature-minimal-host.md · Step 1 · "the Checkpoint Log carries exactly one row at this point"] ⚠ **Why this counts every row, with no exclusion for the `branch-override` record:** a minimal host cannot carry one. `references/modify-existing-flow.md` withholds the branch-gate override from exactly these hosts (Step 1.6's follow-up variant, Step 1.7, Step 1.8), because they assert branch equality against a `branch:` cut by change class.
103
+
104
+ R-FF-CKPTCOUNT-02: [finish-feature-minimal-host.md · Step 1 · "the Checkpoint Log carries exactly one row at this point"] "Exactly two after closeout" follows from `finish-feature-flow.md` Step 4's post-commit read, which derives its admitted differences from Step 2, Step 4's terminal Resume Pointer write and Step 4 instruction 1, and admits nothing else.
105
+
106
+ R-FF-FOLLOWUP-01: [finish-feature-minimal-host.md · Step 1 · "the reverse link was opened, not only closed"] Decidable from one commit and one blob per original, so it stays inside closeout's remit.
107
+
108
+ R-FF-SUMMARY-01: [finish-feature-minimal-host.md · Step 5 · "Zero-phase minimal host — exact fields", on a no-BC host's `Related BR-IDs`] Forcing `none` here would erase a marker the approved zero-phase shape requires.
109
+
110
+ ## init-project-flow.md
111
+
112
+ R-INIT-DEFERRED-01: [init-project-flow.md · Step 3.1 · "is NOT generated here", both bullets] Init creates neither a per-context `behavior.md` nor either `analysis.md`: an empty file created ahead of its content becomes a stale placeholder.
113
+
114
+ ## modify-existing-flow.md
115
+
116
+ R-ME-BASELINE-01: [bf · modify-existing-flow.md · Step 2 · "Systematic Baseline Capture"] Baseline capture looks for system-level knowledge and records it in `analysis.md`, even though it runs only when the feature being modified has no prior spec: it is how a project that adopts Dflow midway gets its existing system into `analysis.md` in bulk, rather than one entry at a time. Every other entry point only records what the change in hand happens to touch; the observation-only route in Part A records without changing anything, but only what that observation reached.
117
+
118
+ ## new-phase-flow.md
119
+
120
+ R-NP-ANALYSIS-01: [new-phase-flow.md · Step 2 · "System-level knowledge?"] Step 2 records into `analysis.md` on the spot rather than leaving it to closeout, because deferring needs two things this flow does not have: somewhere to hold the answer until closeout (the phase-spec template has no section for it), and a closeout step that always runs (a host with no bounded context skips `/dflow:finish-feature` Step 3 entirely). Each track's Step 7 carves these two paths out of its "remain `/dflow:finish-feature` responsibilities" sentence for the same reason. Step 6 re-checks what Step 2 recorded - what the phase was to add or change against what it built, what Step 2 uncovered in the existing system against the system as it now stands, since knowledge Step 2 uncovered was true before the phase began and does not stop being true because the phase built nothing for it - instead of leaving that to closeout as well: no closeout step is sure to reach the entry (`/dflow:new-feature` § 8.4 archives without `/dflow:finish-feature`), and by then a checkpoint has usually committed it, so no diff would show it.
121
+
122
+ ## templates/analysis.md
123
+
124
+ R-ANALYSIS-PLACEMENT-01: [templates/analysis.md · Placement · "who owns what the entry describes"] This template fills two paths rather than one because the knowledge it holds splits that way in practice: a lifecycle, a derived figure and a mechanism usually have an owning context, while ordered flows across contexts and the function/role map are kept in the domain-root copy by policy, whoever owns what a given flow or row describes - a flow is read end to end across the contexts its steps belong to, and the index answers who reaches what across the whole system, which no one context's copy could show. Keeping all of it in one domain-root file would both deny the first three their owning context and make that single file grow with the whole system; asking who owns the thing an entry describes lets each copy grow with what it describes, and leaves a home for the cases that turn out to have no owner. A hotspot row goes with the knowledge it is stuck on - in whichever copy holds that knowledge, the ownership question deciding only when it is stuck on a business rule or on knowledge not recorded yet - so that whoever reads that knowledge also sees what the project is still working around in it. The per-context path costs little to enforce, because it is an ordinary BC-layer document to every closeout, no-BC and path check in the workflow - though the checks that enumerate those documents by name still name it.
125
+
126
+ R-ANALYSIS-EVIDENCE-01: [templates/analysis.md · Lifecycles · "Evidence sits on the transitions"] The state list carries no Evidence column because it is the entry's vocabulary - what each value means and what it allows or blocks next - while each transition is a claim about how the system behaves now, which goes stale and must stay re-checkable. The cost is that the claim "these are all the values this field holds" cannot itself be re-checked from the table.
127
+
128
+ R-ANALYSIS-QUESTIONS-01: [templates/analysis.md · Open Questions and Hotspots · "comes up while writing an entry here"] Questions are not routed into this file by subject: where a question belongs depends on the form its answer will take, and the two can disagree — "which role may approve a refund?" is about role reach, yet its answer is a business rule for the owning context's `rules.md`. An unanswered question therefore stays in the feature spec or in the Open Questions section of the document its answer belongs to; when that document is this template, the question goes in the copy that will hold the answer, whichever copy raised it, so that whoever comes to record the answer finds the question beside it - even where that means creating that copy for the question.
129
+
130
+ R-ANALYSIS-IDCOL-01: [templates/analysis.md · the two tables with an ID column · "carries it there"] The ID column is placed after the name column, not first, because `dflow render` takes a table's first column as each card's title: with the id first, every card in these two tables would be titled `FN-01` or `HS-01` and the name or the hotspot itself would be demoted to a field. The flow table keeps `#` first because a step number is a reasonable card title.
@@ -1,19 +1,21 @@
1
1
  ---
2
2
  name: dflow
3
3
  description: >
4
- Dflow SDD/DDD workflow guardian for this project. PRIMARY: the canonical
5
- /dflow:* commands (/dflow:new-feature, /dflow:modify-existing, /dflow:bug-fix,
4
+ Dflow SDD/DDD workflow guardian. PRIMARY: the canonical /dflow:* commands
5
+ (/dflow:new-feature, /dflow:modify-existing, /dflow:bug-fix,
6
6
  /dflow:new-phase, /dflow:finish-feature, /dflow:pr-review, /dflow:verify,
7
7
  /dflow:report-dflow-feedback, /dflow:status, /dflow:next, /dflow:cancel).
8
- SECONDARY — engage ONLY for adding or changing product/user-facing/domain
9
- behavior, a new requirement, a feature or bug-fix workflow, or spec-impacting
10
- architecture/domain-model decisions. Includes indirect phrasings, e.g. "I want
11
- to build/add ...", "let's add the ability to ...", "we need to support ...",
12
- "can you implement ...", "users should be able to ...", "the app should also
13
- ...". Do NOT engage for pure refactors, renames, infra/build chores,
14
- formatting, dep bumps, or general code questions ("how does X work", "explain
15
- this"). When engaged by natural language, DO NOT auto-enter a workflow: judge
16
- the intent, suggest the matching /dflow: command, and wait for confirmation.
8
+ SECONDARY — engage for anything Dflow tracks: a product-visible change, or
9
+ one touching a machine-consumed contract (log/export/API/event, env
10
+ var/CLI flag/exit code), data structure, operational semantics
11
+ (security/CVE, safety, resilience, compliance, payment),
12
+ performance/resource/SLA, BR-ID, or architecture/domain model. Indirect
13
+ phrasings: "I want to build/add ...", "we need to support ...", "users
14
+ should be able to ...". Do NOT engage for behavior-preserving routine work
15
+ touching none of those — refactors, renames, chores, formatting, routine
16
+ dependency bumps — or code questions. When engaged by natural language, DO
17
+ NOT auto-enter a workflow: judge the intent, suggest the matching /dflow:
18
+ command, and wait for confirmation.
17
19
  ---
18
20
 
19
21
  <!-- dflow-generated: skill-adapter -->
@@ -165,6 +165,10 @@ Issues:
165
165
  remove the stale scenario reference from behavior.md
166
166
  ```
167
167
 
168
+ When a remediation action adds or updates `rules.md` rows, follow the spec formatting convention:
169
+
170
+ > **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).
171
+
168
172
  The optional domain-doc hygiene checks (below) append their non-blocking signals
169
173
  to this same report — the `events.md` cross-check as `⚠`, the `models.md`
170
174
  Code-Mapping check as `ℹ` — and never change the core pass / fail count.