dflow-sdd-ddd 0.11.0 → 0.12.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.
- package/CHANGELOG.md +36 -0
- package/bin/dflow.js +0 -0
- package/package.json +1 -1
- package/templates/brownfield/references/finish-feature-flow.md +85 -29
- package/templates/brownfield/references/git-integration.md +29 -9
- package/templates/brownfield/references/modify-existing-flow.md +23 -0
- package/templates/brownfield/references/new-feature-flow.md +34 -1
- package/templates/brownfield/references/new-phase-flow.md +12 -1
- package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +40 -5
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +1 -1
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +2 -2
- package/templates/brownfield/templates/_index.md +23 -4
- package/templates/brownfield/templates/lightweight-spec.md +3 -3
- package/templates/brownfield/templates/phase-spec.md +3 -3
- package/templates/common/references/ddd-modeling-guide.md +197 -3
- package/templates/greenfield/references/finish-feature-flow.md +86 -29
- package/templates/greenfield/references/git-integration.md +29 -9
- package/templates/greenfield/references/modify-existing-flow.md +23 -0
- package/templates/greenfield/references/new-feature-flow.md +35 -1
- package/templates/greenfield/references/new-phase-flow.md +11 -0
- package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +40 -5
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +1 -1
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +4 -2
- package/templates/greenfield/templates/_index.md +23 -4
- package/templates/greenfield/templates/aggregate-design.md +2 -1
- package/templates/greenfield/templates/events.md +2 -1
- package/templates/greenfield/templates/lightweight-spec.md +3 -3
- package/templates/greenfield/templates/phase-spec.md +3 -3
|
@@ -101,6 +101,9 @@ Is it identified by an ID that persists over time?
|
|
|
101
101
|
└─ No → Re-examine — it's probably one of the above
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
A stateful multi-step flow is not a Domain Service — see "Long-Running
|
|
105
|
+
Processes".
|
|
106
|
+
|
|
104
107
|
## Aggregate Design
|
|
105
108
|
|
|
106
109
|
Aggregates are the most important and most commonly misunderstood DDD concept.
|
|
@@ -186,6 +189,9 @@ When designing a new Aggregate:
|
|
|
186
189
|
6. **Will any child collection grow without bound over time?** (history,
|
|
187
190
|
comments, audit entries) Unbounded growth is a split signal — move it to
|
|
188
191
|
its own Aggregate or a read model and reference by ID.
|
|
192
|
+
7. **What would make this boundary wrong?** Record the answer in the
|
|
193
|
+
worksheet's Design Decisions as a re-evaluation condition ("revisit
|
|
194
|
+
when …") — it is what "Revising an Established Model" re-reads later.
|
|
189
195
|
|
|
190
196
|
### Invariant Classification
|
|
191
197
|
|
|
@@ -255,6 +261,79 @@ serialization tactics — distributed locks, per-key actors, aggregate-per-key
|
|
|
255
261
|
sharding — exist but are advanced; reach for a store-level constraint or version
|
|
256
262
|
check first.)
|
|
257
263
|
|
|
264
|
+
## Revising an Established Model
|
|
265
|
+
|
|
266
|
+
The sections above are about getting a boundary right the first time. This
|
|
267
|
+
one is about the other half of a model's life: an established model whose
|
|
268
|
+
original decision was right — until the conditions changed. **A recorded
|
|
269
|
+
design decision is not settled law.** It is a decision *plus the conditions
|
|
270
|
+
under which it was right*; when those conditions expire, the decision is
|
|
271
|
+
due for review, not deference.
|
|
272
|
+
|
|
273
|
+
### The re-read rule
|
|
274
|
+
|
|
275
|
+
**When extending an existing Aggregate, re-read its recorded Design
|
|
276
|
+
Decisions before adding to it** (the `aggregate-design.md` worksheet;
|
|
277
|
+
Brownfield — see the Edition note). You are not reading for format — you
|
|
278
|
+
are checking two things:
|
|
279
|
+
|
|
280
|
+
1. Does any recorded **re-evaluation condition** ("revisit when …") match
|
|
281
|
+
the change in front of you?
|
|
282
|
+
2. Did the original rationale assume something that is no longer true?
|
|
283
|
+
|
|
284
|
+
### Signals that the model is resisting
|
|
285
|
+
|
|
286
|
+
Any of these appearing in your change is a signal — go to the ladder below:
|
|
287
|
+
|
|
288
|
+
- **Bending a field to fit** — a field that was previously required by the
|
|
289
|
+
entity's lifecycle is made nullable to fit a new case.
|
|
290
|
+
- **Discriminator creep** — adding a `Purpose` / `Type` discriminator so
|
|
291
|
+
one entity carries materially different lifecycles, required fields, or
|
|
292
|
+
rules.
|
|
293
|
+
- **Stacking another branch** — a third or later *distinct business branch*
|
|
294
|
+
on the same decision axis (validation / error variants don't count; the
|
|
295
|
+
count is an anchor, not an automatic refactor rule).
|
|
296
|
+
- **Qualifying a term to use it** — you keep saying "the Order here means
|
|
297
|
+
the cart-order"; one glossary term now covers two lifecycles.
|
|
298
|
+
- **A recorded re-evaluation condition matches** the current change.
|
|
299
|
+
- **Cross-instance transaction pressure** — a new invariant or operation
|
|
300
|
+
needs same-transaction writes across Aggregate instances because the
|
|
301
|
+
current boundary cannot own the rule (see Aggregate Design Rules #2; a
|
|
302
|
+
flow over *time* is a different topic — see "Long-Running Processes").
|
|
303
|
+
- **A child collection grows without bound** — the split signal from
|
|
304
|
+
Aggregate Design Rules #4 and Common Mistakes #7 applies to established
|
|
305
|
+
models too.
|
|
306
|
+
|
|
307
|
+
### What to do when a signal fires — take the lowest rung that fits
|
|
308
|
+
|
|
309
|
+
1. **Name it in the spec** (mandatory when a signal fires; zero design
|
|
310
|
+
cost). One short passage in the spec's design decisions / open
|
|
311
|
+
questions: which signal fired, the options, and the decision —
|
|
312
|
+
**proceed as-is, split, or rename — with the reason**. Deciding *not*
|
|
313
|
+
to split, recorded, is a perfectly good outcome when the rationale
|
|
314
|
+
still holds. What is not acceptable is extending the model as if the
|
|
315
|
+
question did not exist.
|
|
316
|
+
2. **Treat the revision as its own change** when the answer is "split" or
|
|
317
|
+
"rename": record it as tech debt or a follow-up feature — or, if the
|
|
318
|
+
feature cannot proceed sanely on the old boundary, make the split /
|
|
319
|
+
rename the feature's first phase (T1 ceremony; the glossary moves
|
|
320
|
+
first, RENAMED deltas, code follows).
|
|
321
|
+
|
|
322
|
+
Two guards, so this section cannot become its own kind of
|
|
323
|
+
over-engineering:
|
|
324
|
+
|
|
325
|
+
> Signals are the trigger, not a schedule. Do not re-litigate the model on
|
|
326
|
+
> every touch: no signal → no ceremony; one signal → name it; several
|
|
327
|
+
> signals, or a matched re-evaluation condition → evaluate seriously.
|
|
328
|
+
|
|
329
|
+
> A signal triggers **review, not redesign**. Do not split or rename just
|
|
330
|
+
> because a signal fired. A short recorded decision to keep the current
|
|
331
|
+
> model is a valid outcome when the rationale still holds.
|
|
332
|
+
|
|
333
|
+
This applies where a model exists to revise — core / supporting contexts.
|
|
334
|
+
A generic context's thin wrapper (see "Subdomain-Aware Modeling Depth")
|
|
335
|
+
has no worksheet and needs none of this ceremony.
|
|
336
|
+
|
|
258
337
|
## Value Objects
|
|
259
338
|
|
|
260
339
|
### When to Use Value Objects
|
|
@@ -286,11 +365,16 @@ public record Money(decimal Amount, Currency Currency)
|
|
|
286
365
|
}
|
|
287
366
|
|
|
288
367
|
// DateRange
|
|
289
|
-
public record DateRange
|
|
368
|
+
public record DateRange
|
|
290
369
|
{
|
|
291
|
-
public
|
|
370
|
+
public DateOnly Start { get; }
|
|
371
|
+
public DateOnly End { get; }
|
|
372
|
+
|
|
373
|
+
public DateRange(DateOnly start, DateOnly end)
|
|
292
374
|
{
|
|
293
|
-
if (
|
|
375
|
+
if (start > end) throw new DomainException("Start must be before End.");
|
|
376
|
+
Start = start;
|
|
377
|
+
End = end;
|
|
294
378
|
}
|
|
295
379
|
|
|
296
380
|
public bool Contains(DateOnly date) => date >= Start && date <= End;
|
|
@@ -433,6 +517,95 @@ the values that changed — not a snapshot of the whole Aggregate.
|
|
|
433
517
|
delivery. If no dispatcher is wired yet, record it as deferred tech debt — but
|
|
434
518
|
still clear on a successful save so events cannot accumulate unbounded.
|
|
435
519
|
|
|
520
|
+
## Long-Running Processes
|
|
521
|
+
|
|
522
|
+
The Aggregate design rules above already cover the simple case: one Aggregate
|
|
523
|
+
per transaction, with cross-Aggregate work flowing through Domain Events and
|
|
524
|
+
eventual consistency. This section is for the flows where that is not the
|
|
525
|
+
whole story — where the multi-step flow is itself a business thing with
|
|
526
|
+
state, and that state needs a home.
|
|
527
|
+
|
|
528
|
+
### Process or event chain?
|
|
529
|
+
|
|
530
|
+
Use an event chain when each reaction can succeed or fail independently — a
|
|
531
|
+
later failure does not change the earlier domain commitment. Treat the flow
|
|
532
|
+
as a **process** when the business must track and decide the multi-step
|
|
533
|
+
**outcome**: compensate or reverse an earlier commitment, answer current
|
|
534
|
+
progress, enforce a deadline, or enforce an ordered cross-Aggregate workflow
|
|
535
|
+
whose intermediate state matters.
|
|
536
|
+
|
|
537
|
+
The quickest entry test: **if a later step fails, must an earlier step be
|
|
538
|
+
undone?** "Payment failed → release the reserved stock" is a process, not an
|
|
539
|
+
event chain.
|
|
540
|
+
|
|
541
|
+
Further signals that the flow is a process:
|
|
542
|
+
|
|
543
|
+
- The business asks "where is order #123 in the flow?" and no single
|
|
544
|
+
Aggregate can answer.
|
|
545
|
+
- A deadline is part of the rules ("if payment is not confirmed within 30
|
|
546
|
+
minutes, release the seats").
|
|
547
|
+
- Steps across Aggregates must run in a prescribed order **and** the
|
|
548
|
+
intermediate state matters to the business — being ordered by itself is
|
|
549
|
+
not enough.
|
|
550
|
+
|
|
551
|
+
**Not a process:** fire-and-forget notifications, read-model updates,
|
|
552
|
+
logging, and other best-effort side effects — even when ordered, even when
|
|
553
|
+
they cross Aggregates. Those remain plain event chains; their failure
|
|
554
|
+
handling is already covered by the failure-path guideline under Event
|
|
555
|
+
Handling Guidelines.
|
|
556
|
+
|
|
557
|
+
### Where does the process state live? Take the lowest rung that fits
|
|
558
|
+
|
|
559
|
+
1. **A status field on the Aggregate that owns the flow** — the first choice
|
|
560
|
+
when the process is naturally part of one Aggregate's lifecycle:
|
|
561
|
+
`Order.Status = PendingPayment → Paid → Shipped`. Event handlers advance
|
|
562
|
+
the status; each compensation is an explicit state-transition method
|
|
563
|
+
(`order.Cancel(reason)`) with its own BR. Most mid-size flows stop here.
|
|
564
|
+
The boundary: a status field is enough only when the process is naturally
|
|
565
|
+
part of that Aggregate's lifecycle — do not store every downstream
|
|
566
|
+
system's bookkeeping on the owner just to avoid a process Aggregate.
|
|
567
|
+
|
|
568
|
+
2. **A dedicated process Aggregate** (the DDD shape of a *process manager*) —
|
|
569
|
+
when the coordination belongs to no existing Aggregate, or the flow needs
|
|
570
|
+
its own bookkeeping (steps completed, retries, deadline): a small
|
|
571
|
+
Aggregate (e.g. `OrderFulfillment`) whose state *is* the flow's progress.
|
|
572
|
+
It reacts to events, issues commands, and its invariants are process
|
|
573
|
+
rules ("cannot ship before payment is confirmed"). It is an ordinary
|
|
574
|
+
Aggregate — same aggregate-design worksheet, same Invariants table,
|
|
575
|
+
events cataloged in `events.md` like any other (Brownfield — see the
|
|
576
|
+
Edition note). Two boundaries: a process Aggregate **coordinates
|
|
577
|
+
progress** — it does not pull the participating Aggregates into one
|
|
578
|
+
transaction or take over their invariants. And do not create one for a
|
|
579
|
+
two-step event reaction with no compensation, no deadline, and no
|
|
580
|
+
business-visible progress to answer — keep that as an event chain, or as
|
|
581
|
+
the owner Aggregate's normal state if it already has one.
|
|
582
|
+
|
|
583
|
+
3. **An orchestration framework / workflow engine** — the Phase 2+ upgrade,
|
|
584
|
+
worth it only at operational scale (versioning long-lived in-flight
|
|
585
|
+
flows, visibility dashboards). Like event sourcing, this is a separate,
|
|
586
|
+
heavyweight architecture decision: record it as an ADR if genuinely
|
|
587
|
+
needed; never introduce it as a side effect of modeling.
|
|
588
|
+
|
|
589
|
+
### Compensation is business behavior, not plumbing
|
|
590
|
+
|
|
591
|
+
A compensating action is not a rollback — the mail was sent, the money
|
|
592
|
+
moved; they cannot un-happen. Compensation is a **new domain fact**
|
|
593
|
+
(`RefundIssued`, `ReservationReleased`) with its own BR and Given/When/Then
|
|
594
|
+
in `behavior.md`. The failure-path guideline under Event Handling Guidelines
|
|
595
|
+
says *which* final failures must become BRs; this section says *where the
|
|
596
|
+
logic that answers them lives*.
|
|
597
|
+
|
|
598
|
+
### Deadlines
|
|
599
|
+
|
|
600
|
+
A deadline is part of the process state. Detecting expiry is infrastructure
|
|
601
|
+
(a scheduled check), but the decision — "expired → release the seats" — is a
|
|
602
|
+
domain rule, expressed as a domain event (`BookingExpired`) and handled like
|
|
603
|
+
any other.
|
|
604
|
+
|
|
605
|
+
Two search terms, so you can find the literature: *choreography* is a plain
|
|
606
|
+
event chain; *orchestration* means an explicit process owner. These are
|
|
607
|
+
search terms, not a framework choice.
|
|
608
|
+
|
|
436
609
|
## Specifications
|
|
437
610
|
|
|
438
611
|
For complex query logic that belongs to the domain:
|
|
@@ -504,6 +677,10 @@ Use Domain Services for operations that:
|
|
|
504
677
|
- Require external information (through interfaces) to make domain decisions
|
|
505
678
|
- Don't naturally belong to any single Entity
|
|
506
679
|
|
|
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.
|
|
683
|
+
|
|
507
684
|
```csharp
|
|
508
685
|
// Domain Service — in Domain layer
|
|
509
686
|
public class ExpenseApprovalService
|
|
@@ -641,3 +818,20 @@ The less-obvious three — when to reach for them:
|
|
|
641
818
|
Aggregates just to display a list / report causes object-graph bloat and N+1
|
|
642
819
|
→ Use a read model (denormalized projection / DTO) for the display path; see
|
|
643
820
|
"Read Models (Query Side)"
|
|
821
|
+
|
|
822
|
+
11. **Compensation logic scattered across event handlers** — Each handler
|
|
823
|
+
patches state on its own; no one owns the process, and nobody can answer
|
|
824
|
+
"where is order #123 in the flow"
|
|
825
|
+
→ Give the process a home (a status field on the owning Aggregate or a
|
|
826
|
+
dedicated process Aggregate); write each compensating action as a BR; see
|
|
827
|
+
"Long-Running Processes"
|
|
828
|
+
|
|
829
|
+
12. **Treating recorded design decisions as settled law** — Extending an
|
|
830
|
+
Aggregate while bending it to fit (a lifecycle-required field turned
|
|
831
|
+
nullable for a new case, a `Purpose` / `Type` discriminator making one
|
|
832
|
+
entity carry materially different lifecycles or rules), never re-reading
|
|
833
|
+
the Design Decisions whose re-evaluation condition the change just
|
|
834
|
+
triggered
|
|
835
|
+
→ Re-read recorded decisions when extending an existing Aggregate; when a
|
|
836
|
+
resistance signal fires, name the revision question in the spec (proceed /
|
|
837
|
+
split / rename, with reason); see "Revising an Established Model"
|
|
@@ -56,6 +56,10 @@ proceeding (do not flip status, do not archive, do not emit summary).
|
|
|
56
56
|
- [ ] Every phase-spec file referenced in the Phase Specs table exists at
|
|
57
57
|
the path the table claims
|
|
58
58
|
- [ ] Every phase-spec file's frontmatter has `status: completed`
|
|
59
|
+
- [ ] Every Tier = T2 row in `_index.md` Lightweight Changes references an
|
|
60
|
+
existing `lightweight-*.md` / `BUG-*.md` file in the feature directory
|
|
61
|
+
- [ ] Every such lightweight / BUG spec file's frontmatter has
|
|
62
|
+
`status: completed`
|
|
59
63
|
- [ ] `_index.md` has no obvious open items in Resume Pointer (e.g. "phase-N
|
|
60
64
|
drafting" / "implementation pending" / "TODO" markers)
|
|
61
65
|
- [ ] Current BR Snapshot table is non-empty (or feature is intentionally
|
|
@@ -66,6 +70,7 @@ If any check fails:
|
|
|
66
70
|
> found:
|
|
67
71
|
> ✗ phase-spec-2026-04-15-foo.md status is still `in-progress`
|
|
68
72
|
> ✗ Phase Specs table row 3 references missing file phase-spec-...
|
|
73
|
+
> ✗ lightweight-2026-06-20-rounding.md frontmatter status is still `in-progress`
|
|
69
74
|
>
|
|
70
75
|
> Address these (run `/dflow:new-phase` to add missing work, or fix the
|
|
71
76
|
> stale status manually), then re-run `/dflow:finish-feature`."
|
|
@@ -93,11 +98,17 @@ branch: feature/{SPEC-ID}-{slug}
|
|
|
93
98
|
---
|
|
94
99
|
```
|
|
95
100
|
|
|
96
|
-
Also update the **Resume Pointer** to reflect closeout
|
|
101
|
+
Also update the **Resume Pointer** to reflect closeout — this writes the
|
|
102
|
+
cursor's terminal state (after closeout no workflow is active on this
|
|
103
|
+
feature; do not edit the cursor again after the Step 4 closeout commit):
|
|
97
104
|
|
|
98
105
|
```
|
|
99
106
|
**Current Progress**: feature completed ({date}); all phase-specs status = completed.
|
|
100
107
|
**Next Action**: integration — push / merge / PR per the selected Git policy.
|
|
108
|
+
**Active Workflow**: none
|
|
109
|
+
**Current Step**: n/a
|
|
110
|
+
**Gates Passed**: n/a
|
|
111
|
+
**Awaiting**: none
|
|
101
112
|
```
|
|
102
113
|
|
|
103
114
|
**→ Transition (step-internal)**: Step 2 complete. Announce "Step 2 complete (status flipped). Entering Step 3: Sync BR Snapshot to BC layer." and continue.
|
|
@@ -184,7 +195,8 @@ AI runs:
|
|
|
184
195
|
```bash
|
|
185
196
|
git mv dflow/specs/features/active/{SPEC-ID}-{slug} \
|
|
186
197
|
dflow/specs/features/completed/{SPEC-ID}-{slug}
|
|
187
|
-
git status # confirm rename detection
|
|
198
|
+
git status # confirm rename detection AND check for `RM` — an `M` next to
|
|
199
|
+
# a rename means unstaged edits you must re-add before committing
|
|
188
200
|
```
|
|
189
201
|
|
|
190
202
|
`git mv` is mandatory — never use plain `mv` + `git add`. This preserves
|
|
@@ -193,37 +205,75 @@ PR diff quality stays intact across the move. See
|
|
|
193
205
|
`references/git-integration.md` § "Directory Moves Must Use git mv" for
|
|
194
206
|
the full rule set.
|
|
195
207
|
|
|
196
|
-
After the move, also `git add` any modified files from Step 3 (the
|
|
197
|
-
updated `rules.md`, `behavior.md`, `events.md`, `context-map.md`,
|
|
198
|
-
`glossary.md`, `architecture/tech-debt.md`, etc.) into the same stage.
|
|
199
|
-
|
|
200
208
|
**Closeout commit checkpoint** (completes the offline Local-closeout gate):
|
|
201
209
|
|
|
202
210
|
```
|
|
203
|
-
✓ Feature archived to completed/ and closeout
|
|
211
|
+
✓ Feature archived to completed/ and closeout ready to stage
|
|
204
212
|
Commit this closeout now?
|
|
205
213
|
[Y] Yes — the AI commits with your Git identity (marker per _conventions.md § AI Commit Policy)
|
|
206
214
|
[N] No — skip; you commit yourself
|
|
207
215
|
```
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
hash
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
217
|
+
Then, in this order:
|
|
218
|
+
|
|
219
|
+
1. **Record the checkpoint row first.** Write one row in the moved
|
|
220
|
+
`_index.md` Checkpoint Log — `closeout | committed` for Y, `closeout |
|
|
221
|
+
skipped` for N. The closeout row carries **no commit hash**: the closeout
|
|
222
|
+
commit cannot contain its own hash. Trace it later via
|
|
223
|
+
`git log -1 -- dflow/specs/features/completed/{SPEC-ID}-{slug}` (or the
|
|
224
|
+
optional `Dflow-Checkpoint` trailer). The "hash only after success" rule
|
|
225
|
+
still applies to spec / implementation rows — closeout is the documented
|
|
226
|
+
exception (see `references/git-integration.md` § Commit Checkpoints,
|
|
227
|
+
Branch Gate & AI Commits).
|
|
228
|
+
2. **Stage the whole archived feature directory:**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
git add dflow/specs/features/completed/{SPEC-ID}-{slug}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This is required, not optional: `git mv` stages the rename with the
|
|
235
|
+
**last-committed** content, so working-tree edits made earlier in this
|
|
236
|
+
flow to the moved files — the Step 2 status flip and Resume Pointer
|
|
237
|
+
update, plus the checkpoint row you just wrote — stay **unstaged** until
|
|
238
|
+
this `git add`. In `git status`, the moved `_index.md` showing `RM`
|
|
239
|
+
instead of plain `R` is exactly this signal. Then also `git add` the
|
|
240
|
+
files updated in Step 3 (the updated `rules.md`, `behavior.md`,
|
|
241
|
+
`events.md`, `context-map.md`, `glossary.md`,
|
|
242
|
+
`architecture/tech-debt.md`, etc.) into the same stage.
|
|
243
|
+
3. **Commit (Y) or stop (N).** For Y the AI commits. If a pre-commit hook
|
|
244
|
+
rejects it or the commit fails, flip the checkpoint row to `failed` (the
|
|
245
|
+
row is not committed yet — edit it directly), surface the error, and
|
|
246
|
+
treat the gate as unsatisfied.
|
|
247
|
+
|
|
248
|
+
**Post-commit closeout verification** — after a successful commit, and before
|
|
249
|
+
declaring the Local-closeout gate satisfied, AI runs and reports `✓` / `✗` for
|
|
250
|
+
every item:
|
|
251
|
+
|
|
252
|
+
- [ ] `git show HEAD:dflow/specs/features/completed/{SPEC-ID}-{slug}/_index.md`
|
|
253
|
+
— one blob read verifying **two** things: frontmatter `status: completed`
|
|
254
|
+
**and** the Checkpoint Log contains the closeout row. This reads the
|
|
255
|
+
**committed** content, not the working tree — the former catches "rename
|
|
256
|
+
carried stale content", the latter catches "row never made it into the
|
|
257
|
+
commit".
|
|
258
|
+
- [ ] `dflow/specs/features/active/{SPEC-ID}-{slug}/` no longer exists (the
|
|
259
|
+
directory was moved, not copied)
|
|
260
|
+
- [ ] `git status --short` shows no leftovers related to this feature
|
|
261
|
+
(working tree clean; identify any unrelated dirty files explicitly)
|
|
262
|
+
|
|
263
|
+
If any item fails, do **not** declare closeout complete — fix it (re-add and
|
|
264
|
+
amend, or a follow-up commit; the developer chooses) and re-verify.
|
|
265
|
+
|
|
266
|
+
The Local-closeout gate is satisfied **only when the closeout is committed and
|
|
267
|
+
the verification above passes**. If you declined the commit (chose N) or it
|
|
268
|
+
failed, Local-closeout is **not** satisfied yet — commit the staged closeout
|
|
269
|
+
yourself before continuing; do not enter the Integration / PR gate with
|
|
270
|
+
uncommitted changes. Once committed and verified, the gate stands on its own
|
|
271
|
+
offline; integration happens in Step 5 when you have network.
|
|
272
|
+
|
|
273
|
+
**→ Transition (step-internal)**: Step 4 complete. Branch on the verification result:
|
|
274
|
+
|
|
275
|
+
- **Closeout commit landed and post-commit verification passed** → announce "Step 4 complete (feature archived; Local-closeout gate satisfied). Entering Step 5: Integration / PR gate." and continue.
|
|
276
|
+
- **Closeout commit was declined (N), failed, or verification reported `✗`** → **stop here.** Announce "Step 4 complete (feature archived), but the Local-closeout gate is not satisfied yet — the closeout is staged but uncommitted, or the committed content failed verification. Commit the staged changes (or fix the failure), then resume to Step 5." Do **not** enter Step 5 with uncommitted or unverified closeout changes.
|
|
227
277
|
|
|
228
278
|
## Step 5: Emit Integration Summary (Git-strategy-neutral)
|
|
229
279
|
|
|
@@ -286,10 +336,17 @@ the developer:
|
|
|
286
336
|
|
|
287
337
|
If no `follow-up-of` field, skip Step 6 and announce closeout complete:
|
|
288
338
|
> "`/dflow:finish-feature` complete for `{SPEC-ID}-{slug}`. Feature
|
|
289
|
-
> directory is now at `dflow/specs/features/completed/{SPEC-ID}-{slug}
|
|
290
|
-
>
|
|
291
|
-
>
|
|
292
|
-
>
|
|
339
|
+
> directory is now at `dflow/specs/features/completed/{SPEC-ID}-{slug}/`,
|
|
340
|
+
> with the Local-closeout gate satisfied (closeout committed and verified).
|
|
341
|
+
> Integration — merge / push / PR — follows the selected Git policy, at
|
|
342
|
+
> your discretion."
|
|
343
|
+
|
|
344
|
+
**In-flight reminder** — after the closeout announcement (with or without
|
|
345
|
+
Step 6), run the in-flight overview scan (see `AI-AGENT-GUIDE.md` § Status /
|
|
346
|
+
Control Commands) and list any other unfinished features in `active/` and any
|
|
347
|
+
in-flight feature / bugfix branches. Surfacing them at closeout is deliberate:
|
|
348
|
+
attention is about to move elsewhere, and this is exactly where half-done work
|
|
349
|
+
sinks.
|
|
293
350
|
|
|
294
351
|
## Step 6: Reverse-Update Follow-up Tracking (only if follow-up)
|
|
295
352
|
|
|
@@ -43,8 +43,8 @@ bugfix/{BUG-ID}-{slug}
|
|
|
43
43
|
Examples:
|
|
44
44
|
|
|
45
45
|
```
|
|
46
|
-
feature/
|
|
47
|
-
feature/
|
|
46
|
+
feature/SPEC-20260424-002-submit-expense-report
|
|
47
|
+
feature/SPEC-20260430-001-leave-approval-workflow
|
|
48
48
|
bugfix/BUG-042-money-rounding
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -149,10 +149,30 @@ existing Step Gate prompt (it does not add a separate question):
|
|
|
149
149
|
Tier sets how many checkpoints a change has: T1 three (spec / implementation /
|
|
150
150
|
closeout), T2 two (spec+implementation merged / closeout), T3 a single commit.
|
|
151
151
|
Whether you choose Y or N, the AI records one row in the feature `_index.md`
|
|
152
|
-
Checkpoint Log
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
Checkpoint Log — every checkpoint is accounted for (`committed` / `skipped` /
|
|
153
|
+
`failed`), even when no commit happens. A commit hash is written only after the
|
|
154
|
+
commit succeeds; a hook rejection or failed commit is recorded as `failed`
|
|
155
|
+
(never a fake hash). **Exception — the closeout row**: the closeout commit
|
|
156
|
+
cannot contain its own hash, so the closeout row is written before the commit
|
|
157
|
+
as `closeout | committed` with **no hash** (see
|
|
158
|
+
`references/finish-feature-flow.md` Step 4); trace that commit via
|
|
159
|
+
`git log -1 -- dflow/specs/features/completed/{SPEC-ID}-{slug}` or the optional
|
|
160
|
+
`Dflow-Checkpoint` trailer below. After several consecutive skips in a project
|
|
161
|
+
the AI mentions you can turn checkpoints off in config — it does not turn them
|
|
162
|
+
off for you.
|
|
163
|
+
|
|
164
|
+
**Optional machine-greppable trailer.** Teams that want cross-flow checkpoint
|
|
165
|
+
accounting can append a commit trailer at checkpoint commits:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Dflow-Checkpoint: {SPEC-ID} {spec|impl|closeout}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The `_index.md` Checkpoint Log **remains the source of truth**; the trailer is
|
|
172
|
+
a cheap derived mirror (`git log --grep 'Dflow-Checkpoint: {SPEC-ID}'`). Use
|
|
173
|
+
role names, not (k/N) counts — the checkpoint total can change mid-feature
|
|
174
|
+
(tier escalation, follow-ups), and a role gap ("impl exists but no closeout for
|
|
175
|
+
this SPEC-ID") is detectable without predicting N, even across flows.
|
|
156
176
|
|
|
157
177
|
### AI commits
|
|
158
178
|
|
|
@@ -312,9 +332,9 @@ with the rule.
|
|
|
312
332
|
```
|
|
313
333
|
[SPEC-ID] Short description
|
|
314
334
|
|
|
315
|
-
[
|
|
316
|
-
[
|
|
317
|
-
[
|
|
335
|
+
[SPEC-20260424-002] Define ExpenseReport Aggregate with submission invariants
|
|
336
|
+
[SPEC-20260424-002] Add CreateExpenseReport command and handler
|
|
337
|
+
[SPEC-20260424-002] Implement persistence configuration for ExpenseReport
|
|
318
338
|
[BUG-042] Fix rounding in Money value object
|
|
319
339
|
```
|
|
320
340
|
|
|
@@ -9,6 +9,8 @@ Triggered by `/dflow:modify-existing` or `/dflow:bug-fix` (or natural language i
|
|
|
9
9
|
- Step 3 → Step 4 (DDD impact decision → implement)
|
|
10
10
|
- Step 4 → Step 5 (implementation done → update documentation)
|
|
11
11
|
|
|
12
|
+
Crossing any step gate above also updates the host feature's `_index.md` Resume Pointer cursor (Active Workflow / Current Step / Gates Passed / Awaiting) once the host feature directory exists — fold it into that gate's existing `_index.md` / Resume Pointer edit, no separate ceremony (see the `_index.md` template's Resume Pointer notes).
|
|
13
|
+
|
|
12
14
|
All other step transitions are **step-internal**: announce "Step N complete, entering Step N+1" and proceed without waiting. See AI-AGENT-GUIDE.md § Workflow Transparency for the full transparency protocol and confirmation signals.
|
|
13
15
|
|
|
14
16
|
**Note on step count**: Greenfield edition has 5 steps (Brownfield has
|
|
@@ -61,6 +63,16 @@ Walk through these in order:
|
|
|
61
63
|
this is a new concern. For T1, use `/dflow:new-feature`. For T2 / T3
|
|
62
64
|
on a standalone bug, see Step 1.5 — `/dflow:bug-fix` will create a
|
|
63
65
|
minimal feature directory to host the lightweight-spec.
|
|
66
|
+
4. **In-flight overlap scan (cross-branch)**: this branch's `active/` is not
|
|
67
|
+
everything in flight. Run the in-flight scan (classification and dedup
|
|
68
|
+
rules in `AI-AGENT-GUIDE.md` § Status / Control Commands) — `git fetch`
|
|
69
|
+
when the network allows, then
|
|
70
|
+
`git branch --all --list '*feature/*' --list '*bugfix/*'` — and also list
|
|
71
|
+
other unfinished features in this branch's `active/` (one cursor line
|
|
72
|
+
each). If a scanned branch classified as in flight elsewhere, closed out
|
|
73
|
+
awaiting integration, or unknown — or an unfinished feature — semantically
|
|
74
|
+
overlaps this change, surface it and wait for the developer to decide
|
|
75
|
+
before creating anything new (stale branches are non-blocking).
|
|
64
76
|
|
|
65
77
|
> **Why scan completed too?** Completed features are frozen history
|
|
66
78
|
> and **cannot accept** any T2 / T3 directly
|
|
@@ -237,6 +249,17 @@ Changes that require Aggregate redesign:
|
|
|
237
249
|
boundary still make sense, or do we need to split/merge?"
|
|
238
250
|
```
|
|
239
251
|
|
|
252
|
+
**Established-model re-read.** When the change extends an existing
|
|
253
|
+
Aggregate, re-read that Aggregate's recorded Design Decisions — its
|
|
254
|
+
`aggregate-design.md` worksheet in the feature directory that introduced it
|
|
255
|
+
(usually under `features/completed/`). If this change matches a recorded
|
|
256
|
+
re-evaluation condition ("revisit when …") or trips a model-resistance
|
|
257
|
+
signal, follow `references/ddd-modeling-guide.md` § "Revising an
|
|
258
|
+
Established Model": record one short passage in the spec's Design
|
|
259
|
+
Decisions / Open Questions — proceed as-is, split, or rename, with the
|
|
260
|
+
reason. Deciding to keep the current model, recorded, is a valid outcome;
|
|
261
|
+
extending silently is not.
|
|
262
|
+
|
|
240
263
|
### Do we need new Domain Events?
|
|
241
264
|
|
|
242
265
|
If the behavior change means other parts of the system need to react differently:
|
|
@@ -10,6 +10,8 @@ Triggered by `/dflow:new-feature` (or natural language implying a new-feature ta
|
|
|
10
10
|
- Step 6 → Step 7 (branch ready → start implementation)
|
|
11
11
|
- Step 7 → Step 8 (implementation done → completion)
|
|
12
12
|
|
|
13
|
+
Crossing any step gate above also updates the host feature's `_index.md` Resume Pointer cursor (Active Workflow / Current Step / Gates Passed / Awaiting) once the feature directory exists — fold it into that gate's existing `_index.md` / Resume Pointer edit, no separate ceremony (see the `_index.md` template's Resume Pointer notes).
|
|
14
|
+
|
|
13
15
|
All other step transitions are **step-internal**: announce "Step N complete, entering Step N+1" and proceed without waiting. See AI-AGENT-GUIDE.md § Workflow Transparency for the full transparency protocol and confirmation signals.
|
|
14
16
|
|
|
15
17
|
**Ceremony**: this flow always defaults to **T1 Heavy** — the first phase of a brand-new feature is by definition a full SDD cycle. Tier judgement (T1 / T2 / T3) only applies to `/dflow:modify-existing` (see `references/modify-existing-flow.md` and AI-AGENT-GUIDE.md § Ceremony Scaling).
|
|
@@ -31,6 +33,27 @@ Check existing assets:
|
|
|
31
33
|
- Search `dflow/specs/features/` for related features
|
|
32
34
|
- Check `dflow/specs/domain/glossary.md` and `context-map.md`
|
|
33
35
|
|
|
36
|
+
**In-flight overlap scan (cross-branch + other unfinished features)** — this
|
|
37
|
+
branch's `dflow/specs/` does not show everything in flight. Run the in-flight
|
|
38
|
+
scan (classification and dedup rules in `AI-AGENT-GUIDE.md` § Status / Control
|
|
39
|
+
Commands):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git fetch # when the network allows; skip gracefully offline
|
|
43
|
+
git branch --all --list '*feature/*' --list '*bugfix/*'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- List other unfinished features already in this branch's `active/` (one
|
|
47
|
+
cursor line each, from their `_index.md` Resume Pointer).
|
|
48
|
+
- Classify every listed branch by the guide's rules — in flight elsewhere /
|
|
49
|
+
closed out awaiting integration / stale (completed here) / unknown — do
|
|
50
|
+
not shortcut the classification. If a branch classified as **in flight
|
|
51
|
+
elsewhere, closed out awaiting integration, or unknown** has an ID / slug
|
|
52
|
+
that semantically overlaps this request, surface it and wait for the
|
|
53
|
+
developer to decide — continue there / integrate it first / treat as
|
|
54
|
+
related / unrelated — **before creating any new directory, spec, or
|
|
55
|
+
branch**. Only stale (completed here) branches are non-blocking.
|
|
56
|
+
|
|
34
57
|
**→ Transition (step-internal)**: Step 1 complete. Announce "Step 1 complete (intake). Entering Step 2: Identify the Bounded Context." and continue.
|
|
35
58
|
|
|
36
59
|
## Step 2: Identify the Bounded Context
|
|
@@ -96,6 +119,17 @@ Those things form an Aggregate. Everything else is eventually consistent."
|
|
|
96
119
|
- What entities belong inside this Aggregate?
|
|
97
120
|
- What Value Objects can we extract?
|
|
98
121
|
|
|
122
|
+
**Established-model re-read (when the feature reuses an existing
|
|
123
|
+
Aggregate).** Re-read that Aggregate's recorded Design Decisions — its
|
|
124
|
+
`aggregate-design.md` worksheet in the feature directory that introduced it
|
|
125
|
+
(usually under `features/completed/`) — before extending it. If this change
|
|
126
|
+
matches a recorded re-evaluation condition ("revisit when …") or trips a
|
|
127
|
+
model-resistance signal, follow `references/ddd-modeling-guide.md`
|
|
128
|
+
§ "Revising an Established Model": record one short passage in the
|
|
129
|
+
phase-spec's Design Decisions / Open Questions — proceed as-is, split, or
|
|
130
|
+
rename, with the reason. Deciding to keep the current model, recorded, is a
|
|
131
|
+
valid outcome; extending silently is not.
|
|
132
|
+
|
|
99
133
|
### Domain Events
|
|
100
134
|
```
|
|
101
135
|
"After this happens, what else in the system needs to know?"
|
|
@@ -192,7 +226,7 @@ dflow/specs/features/active/{SPEC-ID}-{slug}/
|
|
|
192
226
|
- Current BR Snapshot: initialise from the first phase's planned BRs
|
|
193
227
|
(will be refreshed when the phase-spec finalises)
|
|
194
228
|
- Lightweight Changes: empty table at start
|
|
195
|
-
- Resume Pointer: "phase-1 in progress: drafting phase-spec." / "Next Action: finish phase-spec, then implement Domain layer."
|
|
229
|
+
- Resume Pointer: "phase-1 in progress: drafting phase-spec." / "Next Action: finish phase-spec, then implement Domain layer." / cursor fields: Active Workflow `new-feature`, Current Step `Step 4 — write the spec`, Gates Passed `3→3.5`, Awaiting `none (mid-step)`
|
|
196
230
|
3. **Create the first phase-spec** at `phase-spec-{YYYY-MM-DD}-{slug}.md`
|
|
197
231
|
using `templates/phase-spec.md`. The "Delta from prior phases" section
|
|
198
232
|
is filled with "首 phase,無前置 Delta" (first phase has nothing to
|
|
@@ -19,6 +19,8 @@ adds a new phase to an in-progress feature only.
|
|
|
19
19
|
- Step 5 → Step 6 (`_index.md` refreshed → start implementation)
|
|
20
20
|
- Step 6 → Step 7 (implementation done → complete the phase)
|
|
21
21
|
|
|
22
|
+
Crossing any step gate above also updates the feature's `_index.md` Resume Pointer cursor (Active Workflow / Current Step / Gates Passed / Awaiting) — fold it into that gate's existing `_index.md` / Resume Pointer edit, no separate ceremony (see the `_index.md` template's Resume Pointer notes).
|
|
23
|
+
|
|
22
24
|
All other step transitions are **step-internal**: announce "Step N complete,
|
|
23
25
|
entering Step N+1" and proceed without waiting. See AI-AGENT-GUIDE.md § Workflow
|
|
24
26
|
Transparency for the full transparency protocol and confirmation signals.
|
|
@@ -66,6 +68,11 @@ AI must locate the target feature and load its current state:
|
|
|
66
68
|
- Cross-reference the bounded context's `dflow/specs/domain/{context}/rules.md`
|
|
67
69
|
and `behavior.md` if the new phase is likely to touch system-level
|
|
68
70
|
state (BC-level current state lives there, not in `_index.md`)
|
|
71
|
+
- Run the in-flight overlap scan (classification and dedup rules in
|
|
72
|
+
`AI-AGENT-GUIDE.md` § Status / Control Commands): list other unfinished
|
|
73
|
+
features in `active/` and any feature / bugfix branches whose work is
|
|
74
|
+
not visible on this branch — if the incoming phase scope overlaps one
|
|
75
|
+
of them, surface it before writing the phase-spec.
|
|
69
76
|
|
|
70
77
|
4. **Branch gate — ensure you are on this feature's branch (before any commit)**
|
|
71
78
|
|
|
@@ -99,6 +106,10 @@ Walk the developer through what the new phase covers:
|
|
|
99
106
|
at the depth set by the BC's Subdomain Type (see
|
|
100
107
|
`references/ddd-modeling-guide.md` § Subdomain-Aware Modeling Depth) — don't
|
|
101
108
|
bypass the classification just because this is a phase, not a new feature.
|
|
109
|
+
If the phase **extends an existing Aggregate**, apply the established-model
|
|
110
|
+
re-read from `references/ddd-modeling-guide.md` § "Revising an Established
|
|
111
|
+
Model" (match recorded re-evaluation conditions; record proceed / split /
|
|
112
|
+
rename in the phase-spec).
|
|
102
113
|
4. **Cross-context impact?** Does this phase introduce / change Domain
|
|
103
114
|
Events that other contexts consume? (If yes, plan for `context-map.md`
|
|
104
115
|
updates at finish-feature time.)
|