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.
- package/CHANGELOG.md +824 -1
- package/CONTRIBUTING.md +16 -10
- package/README.en.md +156 -200
- package/README.md +89 -144
- package/TEMPLATE-COVERAGE.md +15 -8
- package/TEMPLATE-LANGUAGE-GLOSSARY.md +15 -1
- package/bin/dflow.js +36 -4
- package/docs/commands.en.md +110 -0
- package/docs/commands.md +101 -0
- package/docs/doctor-uncertainty.en.md +212 -0
- package/docs/doctor-uncertainty.md +212 -0
- package/docs/evaluating-dflow.en.md +29 -11
- package/docs/evaluating-dflow.md +8 -6
- package/docs/npm-publish-checklist.md +3 -1
- package/docs/release-versioning-policy.md +8 -2
- package/docs/upgrading.en.md +196 -0
- package/docs/upgrading.md +197 -0
- package/docs/using-with-claude-code.en.md +25 -10
- package/docs/using-with-claude-code.md +20 -7
- package/docs/using-with-codex.en.md +18 -6
- package/docs/using-with-codex.md +16 -5
- package/docs/using-with-github-copilot.en.md +25 -10
- package/docs/using-with-github-copilot.md +21 -8
- package/lib/doc-shapes.json +997 -0
- package/lib/doctor-checks.js +2654 -0
- package/lib/init.js +3583 -107
- package/lib/render-diagrams.js +1474 -0
- package/lib/render.js +865 -49
- package/package.json +2 -2
- package/templates/brownfield/references/drift-verification.md +4 -0
- package/templates/brownfield/references/finish-feature-flow.md +635 -88
- package/templates/brownfield/references/finish-feature-follow-up.md +60 -0
- package/templates/brownfield/references/finish-feature-minimal-host.md +406 -0
- package/templates/brownfield/references/finish-feature-post-hoc-hotfix.md +95 -0
- package/templates/brownfield/references/git-integration.md +160 -15
- package/templates/brownfield/references/init-project-flow.md +26 -4
- package/templates/brownfield/references/modify-existing-flow.md +412 -87
- package/templates/brownfield/references/modify-existing-follow-up.md +121 -0
- package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +82 -0
- package/templates/brownfield/references/new-feature-flow.md +61 -6
- package/templates/brownfield/references/new-phase-flow.md +57 -7
- package/templates/brownfield/references/pr-review-checklist.md +303 -10
- package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +158 -34
- package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +1 -1
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +75 -6
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +82 -8
- package/templates/brownfield/scaffolding/_conventions.md +50 -28
- package/templates/brownfield/scaffolding/_overview.md +1 -0
- package/templates/brownfield/templates/_index.md +151 -7
- package/templates/brownfield/templates/analysis.md +79 -0
- package/templates/brownfield/templates/behavior.md +1 -0
- package/templates/brownfield/templates/context-definition.md +1 -0
- package/templates/brownfield/templates/context-map.md +2 -1
- package/templates/brownfield/templates/glossary.md +1 -0
- package/templates/brownfield/templates/lightweight-spec.md +154 -11
- package/templates/brownfield/templates/models.md +1 -0
- package/templates/brownfield/templates/phase-spec.md +9 -1
- package/templates/brownfield/templates/rules.md +1 -0
- package/templates/brownfield/templates/tech-debt.md +1 -0
- package/templates/common/references/ddd-modeling-guide.md +33 -16
- package/templates/{greenfield → common}/references/dflow-feedback-flow.md +2 -1
- package/templates/common/references/flow-rationale-registry.md +130 -0
- package/templates/common/skill/SKILL.md +13 -11
- package/templates/greenfield/references/drift-verification.md +4 -0
- package/templates/greenfield/references/finish-feature-flow.md +625 -89
- package/templates/greenfield/references/finish-feature-follow-up.md +60 -0
- package/templates/greenfield/references/finish-feature-minimal-host.md +363 -0
- package/templates/greenfield/references/finish-feature-post-hoc-hotfix.md +95 -0
- package/templates/greenfield/references/git-integration.md +148 -15
- package/templates/greenfield/references/init-project-flow.md +28 -8
- package/templates/greenfield/references/modify-existing-flow.md +378 -85
- package/templates/greenfield/references/modify-existing-follow-up.md +103 -0
- package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +82 -0
- package/templates/greenfield/references/new-feature-flow.md +67 -4
- package/templates/greenfield/references/new-phase-flow.md +56 -7
- package/templates/greenfield/references/pr-review-checklist.md +287 -8
- package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +153 -32
- package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +5 -2
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +74 -6
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +88 -12
- package/templates/greenfield/scaffolding/_conventions.md +50 -28
- package/templates/greenfield/scaffolding/_overview.md +6 -2
- package/templates/greenfield/templates/_index.md +137 -7
- package/templates/greenfield/templates/aggregate-design.md +1 -0
- package/templates/greenfield/templates/analysis.md +79 -0
- package/templates/greenfield/templates/behavior.md +1 -0
- package/templates/greenfield/templates/context-definition.md +1 -0
- package/templates/greenfield/templates/context-map.md +2 -1
- package/templates/greenfield/templates/events.md +4 -1
- package/templates/greenfield/templates/glossary.md +1 -0
- package/templates/greenfield/templates/lightweight-spec.md +154 -11
- package/templates/greenfield/templates/models.md +1 -0
- package/templates/greenfield/templates/phase-spec.md +9 -1
- package/templates/greenfield/templates/rules.md +1 -0
- package/templates/greenfield/templates/tech-debt.md +1 -0
- package/templates/brownfield/references/dflow-feedback-flow.md +0 -251
|
@@ -13,6 +13,11 @@ This project uses Dflow for spec-first AI-assisted development.
|
|
|
13
13
|
| Migration / legacy context | {migration-context} |
|
|
14
14
|
| Prose language | {prose-language} |
|
|
15
15
|
|
|
16
|
+
<!-- dflow-generated: guide-canonical START -->
|
|
17
|
+
<!-- Everything between these markers is Dflow-managed canonical guide content.
|
|
18
|
+
`dflow configure-agents` refreshes it in place on upgrade. Put project-specific
|
|
19
|
+
notes in "## Project Context" above (or outside the markers), not in here. -->
|
|
20
|
+
|
|
16
21
|
## Why This Matters
|
|
17
22
|
|
|
18
23
|
This project has business logic embedded in delivery/entrypoint code
|
|
@@ -59,8 +64,8 @@ are not available in the current AI tool:
|
|
|
59
64
|
|
|
60
65
|
| Workflow | Use when |
|
|
61
66
|
|---|---|
|
|
62
|
-
| `/dflow:new-feature` | A new user-
|
|
63
|
-
| `/dflow:modify-existing` | Existing behavior
|
|
67
|
+
| `/dflow:new-feature` | A genuinely new feature, page, or capability — a **newly created** navigable surface (its own route and its own content tree), a new user-executable domain operation, or a new independently-consumable output. Adding a control to an existing surface — or a menu entry pointing at a route that already exists — is modify-existing. |
|
|
68
|
+
| `/dflow:modify-existing` | Existing behavior or an existing surface changes — including adding a presentation / interaction control (copy button, filter, sort, quick-view) to an existing screen. |
|
|
64
69
|
| `/dflow:bug-fix` | A defect can be described with expected vs actual behavior. |
|
|
65
70
|
| `/dflow:new-phase` | An active feature needs another implementation slice. |
|
|
66
71
|
| `/dflow:finish-feature` | Implementation is complete and needs drift closure. |
|
|
@@ -76,8 +81,8 @@ Machine-readable source for rendering tool-specific thin wrappers:
|
|
|
76
81
|
<!-- dflow-command-registry:start -->
|
|
77
82
|
| id | label | description | arg-hint | scope |
|
|
78
83
|
|---|---|---|---|---|
|
|
79
|
-
| new-feature | /dflow:new-feature | Start a new
|
|
80
|
-
| modify-existing | /dflow:modify-existing | Change existing behavior. | change request | workflow |
|
|
84
|
+
| new-feature | /dflow:new-feature | Start a genuinely new feature, page, or capability (new navigable surface, domain operation, or consumable output). | feature request | workflow |
|
|
85
|
+
| modify-existing | /dflow:modify-existing | Change existing behavior, or add a control to an existing surface. | change request | workflow |
|
|
81
86
|
| bug-fix | /dflow:bug-fix | Investigate a defect described by expected vs actual behavior. | expected vs actual | workflow |
|
|
82
87
|
| new-phase | /dflow:new-phase | Add another implementation slice to an active feature. | feature id or phase goal | workflow |
|
|
83
88
|
| finish-feature | /dflow:finish-feature | Close implementation with drift checks and archived feature state. | feature id | workflow |
|
|
@@ -98,16 +103,36 @@ input like this (supporting files live in the workflow bundle at
|
|
|
98
103
|
- **"Quick question about..." / "How does X work?"** → check
|
|
99
104
|
`dflow/specs/domain/` first and answer from the documented domain knowledge.
|
|
100
105
|
If no spec exists yet, suggest documenting the answer as domain knowledge.
|
|
106
|
+
- **Knowledge that no single context document holds** — an ordered flow that
|
|
107
|
+
crosses contexts (always the domain-root copy), the lifecycle a status field
|
|
108
|
+
moves through, a figure computed rather than stored, the mechanism behind a
|
|
109
|
+
behavior no single rule explains, which roles reach which function (always
|
|
110
|
+
the domain-root copy), or a spot in one of these that this project keeps
|
|
111
|
+
working around until a domain decision settles it (in the copy that holds
|
|
112
|
+
the knowledge it is stuck on) → **record it in
|
|
113
|
+
`analysis.md`**: `dflow/specs/domain/{context}/analysis.md` when one context
|
|
114
|
+
owns what it describes, otherwise `dflow/specs/domain/analysis.md`. Create
|
|
115
|
+
the file from `templates/analysis.md` the first time there is something to
|
|
116
|
+
record. With no workflow running, offer to commit that record on its own.
|
|
101
117
|
- **"What should I work on next?" / sprint planning** → review
|
|
102
118
|
`dflow/specs/features/backlog/` and suggest work based on migration value.
|
|
103
119
|
- **"I'm creating a branch"** → read `references/git-integration.md`; verify
|
|
104
|
-
branch naming and
|
|
120
|
+
branch naming, and make sure the tier's own record exists before coding starts
|
|
121
|
+
(T1 / T2: a spec; T3: the host `_index.md` row — a T3 branch has no spec file).
|
|
105
122
|
- **"I'm designing a domain model" / "How should I model X?" / building or
|
|
106
123
|
reshaping an Aggregate** → read `references/ddd-modeling-guide.md` (DDD
|
|
107
124
|
tactical patterns: aggregates, invariants, value objects, domain events). It
|
|
108
125
|
is written with Greenfield artifact names; see its **Edition note** for where
|
|
109
126
|
Brownfield records the same decisions (`models.md` / `rules.md` /
|
|
110
127
|
`behavior.md` / `migration/tech-debt.md`).
|
|
128
|
+
- **"Why does this rule exist?" / "This rule seems wrong"** → look the rule up in
|
|
129
|
+
`references/flow-rationale-registry.md`. Grep the rule id when you have one
|
|
130
|
+
(`grep -E '^R-FF-BRANCH-02:'`), otherwise a **short** fragment of the rule —
|
|
131
|
+
four to eight words, because the flow files are hard-wrapped and a longer
|
|
132
|
+
phrase spans a line break. A lookup may return two lines tagged `gf ·` / `bf ·`;
|
|
133
|
+
take this project's edition, which the workflow bundle's
|
|
134
|
+
`.dflow-bundle-manifest.json` records in its `edition` field. **Do not read
|
|
135
|
+
that file whole** — it is a lookup table, not a document.
|
|
111
136
|
- **"Turn the specs into HTML" / "make the specs easier to read"** → run the
|
|
112
137
|
CLI command `dflow render` (a human-readability tool, not a `/dflow:*`
|
|
113
138
|
workflow). It mirrors `dflow/specs/` into a browsable static HTML tree
|
|
@@ -116,10 +141,18 @@ input like this (supporting files live in the workflow bundle at
|
|
|
116
141
|
- **"Dflow seems wrong" / "this template is confusing"** (or you notice Dflow
|
|
117
142
|
guidance drift) → suggest `/dflow:report-dflow-feedback`; never submit
|
|
118
143
|
anything upstream automatically.
|
|
119
|
-
- **Anything else code-related** →
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
- **Anything else code-related** → run it through § Ceremony Scaling's ordered
|
|
145
|
+
cascade rather than a business-logic yes/no. Only the cascade's step 2 sends
|
|
146
|
+
work outside Dflow, and it is narrower than "not business logic": a display
|
|
147
|
+
typo, an appearance change, a machine-consumed contract edit, security / CVE
|
|
148
|
+
work and a deliberate runtime performance change are all tracked. If the
|
|
149
|
+
cascade lands the request anywhere at or above T3, suggest the matching
|
|
150
|
+
`/dflow:*` command and wait for confirmation (see § Workflow Transparency);
|
|
151
|
+
only if it lands below workflow, help directly with no ceremony. If step 0
|
|
152
|
+
takes it out of the cascade instead — observation-only work — it is neither:
|
|
153
|
+
say so and record it, do not treat "no tier" as "no ceremony". This applies
|
|
154
|
+
once you are already reading this guide — what makes a tool load it in the
|
|
155
|
+
first place is that tool's own trigger configuration, which is narrower.
|
|
123
156
|
|
|
124
157
|
## Status / Control Commands
|
|
125
158
|
|
|
@@ -245,6 +278,8 @@ Dflow-owned project documents live under `dflow/specs/`.
|
|
|
245
278
|
| System overview | `dflow/specs/shared/_overview.md` |
|
|
246
279
|
| Domain glossary | `dflow/specs/domain/glossary.md` |
|
|
247
280
|
| Context map | `dflow/specs/domain/context-map.md` |
|
|
281
|
+
| Domain analysis (cross-context) | `dflow/specs/domain/analysis.md` |
|
|
282
|
+
| Domain analysis (per context) | `dflow/specs/domain/{context}/analysis.md` |
|
|
248
283
|
| Active feature specs | `dflow/specs/features/active/` |
|
|
249
284
|
| Completed feature snapshots | `dflow/specs/features/completed/` |
|
|
250
285
|
| Technical debt | `dflow/specs/architecture/tech-debt.md` or `dflow/specs/migration/tech-debt.md` |
|
|
@@ -260,10 +295,13 @@ dflow/specs/
|
|
|
260
295
|
│ └── _conventions.md # Spec writing conventions
|
|
261
296
|
├── domain/ # Domain knowledge (DDD preparation)
|
|
262
297
|
│ ├── glossary.md # Ubiquitous Language
|
|
298
|
+
│ ├── context-map.md # Bounded Context relationships (created when needed)
|
|
299
|
+
│ ├── analysis.md # Cross-context flows and role reach
|
|
263
300
|
│ └── {bounded-context}/ # e.g., expense/, hr/, leave/
|
|
264
301
|
│ ├── context.md # Context boundary & responsibilities
|
|
265
302
|
│ ├── models.md # Entity, VO, Aggregate definitions
|
|
266
303
|
│ ├── rules.md # Business rules index (BR-ID + one-line)
|
|
304
|
+
│ ├── analysis.md # Lifecycles, derived figures, mechanisms
|
|
267
305
|
│ └── behavior.md # Consolidated behavior (Given/When/Then)
|
|
268
306
|
├── features/
|
|
269
307
|
│ ├── active/ # Currently in development
|
|
@@ -292,29 +330,112 @@ dflow/specs/
|
|
|
292
330
|
## Ceremony Scaling
|
|
293
331
|
|
|
294
332
|
Not everything needs full ceremony — match effort to impact. Dflow uses three
|
|
295
|
-
tiers — **T1 Heavy / T2 Light / T3 Trivial** —
|
|
296
|
-
`/dflow:new-feature` and `/dflow:new-phase`
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
333
|
+
tiers — **T1 Heavy / T2 Light / T3 Trivial** — plus a **below-workflow** level
|
|
334
|
+
Dflow doesn't track at all. `/dflow:new-feature` and `/dflow:new-phase` are
|
|
335
|
+
always T1 (no judgement). For `/dflow:modify-existing` and `/dflow:bug-fix`,
|
|
336
|
+
classify the change with the **ordered cascade — the first step that matches
|
|
337
|
+
wins** (order matters: a later catch-all would otherwise swallow the trivial and
|
|
338
|
+
below-workflow cases):
|
|
339
|
+
|
|
340
|
+
0. **Scope.** The cascade classifies a *modification*. Genuinely new work — a
|
|
341
|
+
new feature, page, or capability — is `/dflow:new-feature` (T1), even with no
|
|
342
|
+
new BR / Domain / schema. Adding a presentation or interaction control to an
|
|
343
|
+
**existing** surface (copy button, date-range filter, sort control, quick-view
|
|
344
|
+
drawer / tab, or filling a missing `aria-label`) is a *modification* (stays in
|
|
345
|
+
the cascade). A new user-executable domain operation (e.g. one-click return), a
|
|
346
|
+
new independently-consumable output or delivery channel (Download-PDF / export /
|
|
347
|
+
emailed report), or a **newly created** navigable surface (its own route and its
|
|
348
|
+
own content tree) **is** new-feature. Adding a menu entry or link that points at
|
|
349
|
+
a route that already exists is an access control on an existing surface —
|
|
350
|
+
a modification, not a new surface. **Unit of classification**: classify one
|
|
351
|
+
coherent requested change. Split a compound request into its atomic changes,
|
|
352
|
+
run each through the cascade, and let the highest tier govern the whole — each
|
|
353
|
+
lower-tier part still records its own artifact under the same host. Mechanical
|
|
354
|
+
fallout you did not ask for (formatter noise, lint churn) neither raises nor
|
|
355
|
+
lowers the result.
|
|
356
|
+
**Observation-only work is also outside the
|
|
357
|
+
cascade**: capturing existing behaviour as a baseline (Brownfield) changes no
|
|
358
|
+
output, so it carries no tier at all — record it and do not read step 2's
|
|
359
|
+
"below workflow" as permission to skip the record.
|
|
360
|
+
1. **T1 — Heavy.** A **new** business rule or normative constraint (permission /
|
|
361
|
+
eligibility / threshold / approval / required outcome — regardless of whether a
|
|
362
|
+
BR-ID already exists). *New* means a rule that can be stated and broken on its
|
|
363
|
+
own: take the change away and the original rule still works exactly as before.
|
|
364
|
+
Changing an existing rule's parameters is **T2** — a different threshold value,
|
|
365
|
+
or adding to a set the rule already checks (one more allowed country, one more
|
|
366
|
+
approver role). Adding an independent condition the rule never had (an approver
|
|
367
|
+
predicate for a specific payment channel) is a new rule, and T1. A Domain concept (Aggregate / Entity / VO / Event)
|
|
368
|
+
**added or structurally changed** (identity, VO equality components, aggregate
|
|
369
|
+
boundary, or removing / renaming / redefining a spec-tracked member — a Domain
|
|
370
|
+
concept **rename** is T1); a persistent **data-structure** change (table /
|
|
371
|
+
column / relation / index added, removed, renamed, or its type / nullability /
|
|
372
|
+
constraint / logical definition changed); an **architectural** change
|
|
373
|
+
(inter-layer dependency direction, deployment / module boundary, runtime /
|
|
374
|
+
platform ownership, or overturning an architecture decision — behavior-
|
|
375
|
+
preserving still counts); or a **breaking** cross-consumer contract (API /
|
|
376
|
+
event). Contracts run **inbound** too — a required env var / deployment config
|
|
377
|
+
key, a CLI flag, an exit code, a scheduled-automation calling convention. What
|
|
378
|
+
breaks a caller is T1: removing or renaming one, changing what one means, or
|
|
379
|
+
adding a **required** key with no backward-compatible default (deployments must
|
|
380
|
+
act) — making an existing operation's parameter **required** breaks callers the
|
|
381
|
+
same way. Adding an *optional* key or parameter with a backward-compatible
|
|
382
|
+
default is non-breaking — T2 via step 4.
|
|
383
|
+
2. **Below workflow — not tracked.** No audience-perceivable output delta **and**
|
|
384
|
+
no machine-consumed-contract or operational-semantics touch: auto-formatter
|
|
385
|
+
runs, whitespace, internal comments, internal-doc typos, commit messages, and
|
|
386
|
+
behavior-preserving **routine** refactors / dependency bumps. **Routine** =
|
|
387
|
+
touches no operational-semantics axis (security / safety / resilience /
|
|
388
|
+
compliance / payment) and is not architectural — **breadth is not the test** (a
|
|
389
|
+
behavior-preserving refactor across hundreds of call sites is still below
|
|
390
|
+
workflow; a single-file change that touches an operational axis is not). Never
|
|
391
|
+
below workflow, even when invisible: any BR-ID delta, any machine-consumed
|
|
392
|
+
structured-log / export / contract change, any deliberate runtime performance /
|
|
393
|
+
resource / SLA change, or any security / CVE / compliance work — those reach
|
|
394
|
+
step 1 or 4 by audience. `git commit` directly.
|
|
395
|
+
3. **T3 — Trivial.** A **local, low-risk, meaning-preserving pure copy or
|
|
396
|
+
appearance change a product audience perceives** — display-text typo / wording
|
|
397
|
+
polish, colour, spacing, layout polish, or polishing existing alt / ARIA
|
|
398
|
+
wording — on a **single screen / component or single independently-consumed
|
|
399
|
+
page / file**, at the **element level**. **Excluded from T3**: high-consequence
|
|
400
|
+
content (security warnings, password hints, consent, payment / legal notices);
|
|
401
|
+
meaning-changing appearance (danger / status-colour semantics); anything
|
|
402
|
+
affecting operability, default state, interaction order, or task completion
|
|
403
|
+
("just CSS" is no exemption); adding a **missing** accessible name (compliance
|
|
404
|
+
remediation); and text a machine consumes — an API error string or code a client
|
|
405
|
+
parses is contract, not display copy, so it is never T3: route it by the contract
|
|
406
|
+
axis instead (breaks the consumer → step 1, T1; otherwise → step 4, T2). **Locality is
|
|
407
|
+
the rendered audience footprint, not the number of source files**: one shared
|
|
408
|
+
component or localization key whose output appears on several screens is beyond
|
|
409
|
+
the local unit (T2), while edits to several files that surface in one place stay
|
|
410
|
+
T3. Anything beyond that unit is T2 — a typo swept across many screens, a
|
|
411
|
+
whole-screen visual rewrite, a cross-page sweep. The limit follows the whole
|
|
412
|
+
intent, so splitting one cross-screen typo sweep into per-screen requests does
|
|
413
|
+
not turn it into several T3s.
|
|
414
|
+
4. **T2 — Light (catch-all — everything else).** Functional / semantic behavior
|
|
415
|
+
delta (logic, validation, interaction flow, display calculation, an
|
|
416
|
+
**existing** BR's delta, non-breaking contract semantics); performance-only
|
|
417
|
+
changes (perceivable or not); and copy / appearance sweeps escalated from step
|
|
418
|
+
3. Functional bug fixes are T2 (root-cause + regression notes).
|
|
419
|
+
|
|
420
|
+
| Tier | Output | Command / Trigger |
|
|
421
|
+
|---|---|---|
|
|
422
|
+
| **T1 Heavy** | Independent `phase-spec-YYYY-MM-DD-{slug}.md` placed in the feature directory + `_index.md` Phase Specs row + refresh BR Snapshot | `/dflow:new-feature` / `/dflow:new-phase` — also where a `/dflow:modify-existing` or `/dflow:bug-fix` request goes when the cascade lands it at T1 |
|
|
423
|
+
| **T2 Light** | Independent `lightweight-YYYY-MM-DD-{slug}.md` (or `BUG-{NUMBER}-{slug}.md`) inside the feature directory + `_index.md` Lightweight Changes row (outbound link) + refresh BR Snapshot | `/dflow:modify-existing` or `/dflow:bug-fix` — the usual landing for a functional defect |
|
|
424
|
+
| **T3 Trivial** | **Inline row in the host `_index.md` Lightweight Changes only** (no independent spec file) | `/dflow:modify-existing` or `/dflow:bug-fix` (`_index`-only branch — e.g. a defect that is only a display-text typo) |
|
|
425
|
+
|
|
426
|
+
**Audience** = any human audience of the product perceives the output: UI
|
|
427
|
+
(including alt / ARIA read by assistive tech), email, exports. Public / adopter-
|
|
428
|
+
facing docs (product README, public API reference) and operator surfaces
|
|
429
|
+
(dashboard labels, alerts) are product-audience: pure wording = T3 (single page;
|
|
430
|
+
a cross-page sweep escalates to T2 at step 3), meaning change = T2. Raw internal
|
|
431
|
+
diagnostic logs / traces are internal (copy-only fixes are below workflow).
|
|
432
|
+
Structured logs / machine-readable exports a downstream consumer depends on are
|
|
433
|
+
contract: breaking cross-consumer = step 1 T1, non-breaking = step 4 T2.
|
|
434
|
+
|
|
435
|
+
**Project conventions can only escalate.** `_conventions.md` Ceremony (Project
|
|
436
|
+
Application) rows are a floor-raising overlay applied *after* the cascade: a
|
|
437
|
+
project may push a T2 up to T1, but may not lower a cascade T1, nor drop a
|
|
438
|
+
tracked class below workflow.
|
|
318
439
|
|
|
319
440
|
**Lightweight spec** = Problem + Expected behavior + 1–2 Given/When/Then. The
|
|
320
441
|
instantiated file is placed inside the feature directory (see
|
|
@@ -388,7 +509,8 @@ during the completion checklist, not via template section markers.
|
|
|
388
509
|
- Did we discover scattered business logic? → Record in tech-debt.md
|
|
389
510
|
- Are there duplicated calculations? → Record
|
|
390
511
|
- Direct SQL in delivery/entrypoint code? → Record
|
|
391
|
-
- Magic numbers
|
|
512
|
+
- Magic numbers? → Record and add to glossary
|
|
513
|
+
- Undocumented status values? → List them in their field's lifecycle in `analysis.md`
|
|
392
514
|
|
|
393
515
|
## Workflow Steps
|
|
394
516
|
|
|
@@ -406,7 +528,7 @@ directory first. For example:
|
|
|
406
528
|
|---|---|
|
|
407
529
|
| `/dflow:new-feature` | `dflow/specs/shared/dflow-workflows/references/new-feature-flow.md` |
|
|
408
530
|
| `/dflow:modify-existing` | `dflow/specs/shared/dflow-workflows/references/modify-existing-flow.md` |
|
|
409
|
-
| `/dflow:bug-fix` | `dflow/specs/shared/dflow-workflows/references/modify-existing-flow.md` (
|
|
531
|
+
| `/dflow:bug-fix` | `dflow/specs/shared/dflow-workflows/references/modify-existing-flow.md` (same flow; tier comes from the cascade, not from the command) |
|
|
410
532
|
| `/dflow:new-phase` | `dflow/specs/shared/dflow-workflows/references/new-phase-flow.md` |
|
|
411
533
|
| `/dflow:finish-feature` | `dflow/specs/shared/dflow-workflows/references/finish-feature-flow.md` |
|
|
412
534
|
| `/dflow:verify` | `dflow/specs/shared/dflow-workflows/references/drift-verification.md` |
|
|
@@ -426,3 +548,5 @@ should stay thin and point back here.
|
|
|
426
548
|
If a tool does not support Dflow slash commands, treat the command names as
|
|
427
549
|
plain workflow names and follow the matching flow file from the workflow bundle
|
|
428
550
|
at `dflow/specs/shared/dflow-workflows/`.
|
|
551
|
+
|
|
552
|
+
<!-- dflow-generated: guide-canonical END -->
|
|
@@ -114,7 +114,7 @@ AI 的完整決策樹、Workflow Transparency、Ceremony Scaling 三層判準
|
|
|
114
114
|
- `/dflow:new-feature` — 新功能開發
|
|
115
115
|
- `/dflow:new-phase` — 在 active feature 內新增階段
|
|
116
116
|
- `/dflow:modify-existing` — 修改既有功能
|
|
117
|
-
- `/dflow:bug-fix` —
|
|
117
|
+
- `/dflow:bug-fix` — Bug 修復
|
|
118
118
|
- `/dflow:finish-feature` — feature 收尾
|
|
119
119
|
- `/dflow:pr-review` — PR 審查
|
|
120
120
|
- `/dflow:verify` — rules.md ↔ behavior.md 漂移檢查
|
|
@@ -17,6 +17,8 @@ trunk-based, GitHub Flow), use `Git-principles-trunk.md` instead.
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
<!-- dflow-generated: git-principles-canonical START -->
|
|
21
|
+
|
|
20
22
|
## 1. Branch Structure
|
|
21
23
|
|
|
22
24
|
| Branch | Naming | Cut from | Merges to |
|
|
@@ -24,6 +26,7 @@ trunk-based, GitHub Flow), use `Git-principles-trunk.md` instead.
|
|
|
24
26
|
| `main` / `master` | `main` | — | release / hotfix only |
|
|
25
27
|
| `develop` | `develop` | — | integration branch for features |
|
|
26
28
|
| feature | `feature/{SPEC-ID}-{slug}` | `develop` | `develop` |
|
|
29
|
+
| bugfix | `bugfix/{BUG-ID}-{slug}` | `develop` | `develop` |
|
|
27
30
|
| release | `release/{version}` | `develop` | `main` + `develop` |
|
|
28
31
|
| hotfix | `hotfix/{version}-hotfix{n}` | `main` | `main` + `develop` |
|
|
29
32
|
|
|
@@ -31,6 +34,17 @@ The `feature/{SPEC-ID}-{slug}` pattern is a **Dflow requirement** (not
|
|
|
31
34
|
a Git Flow requirement). It ties each feature branch to its
|
|
32
35
|
corresponding `dflow/specs/features/active/{SPEC-ID}-{slug}/` directory.
|
|
33
36
|
|
|
37
|
+
A **`bugfix/{BUG-ID}-{slug}`** branch is the same shape as a feature branch and
|
|
38
|
+
follows the same topology — cut from the same base, pushed the same way,
|
|
39
|
+
integrated the same way, and **closed out the same way**. It is used when a
|
|
40
|
+
defect owns its own host rather than being picked up by a feature already in
|
|
41
|
+
flight. Its host is a **minimal (zero-phase) host**, so it carries no
|
|
42
|
+
phase-spec: the SPEC-ID lives in the directory name while the branch name
|
|
43
|
+
carries the BUG-NUMBER, and the host `_index.md` `branch:` field is
|
|
44
|
+
authoritative for both. Closeout is not optional because the branch prefix
|
|
45
|
+
differs — `/dflow:finish-feature` runs and the directory is archived to
|
|
46
|
+
`completed/` before the branch merges, exactly as for `feature/`.
|
|
47
|
+
|
|
34
48
|
### Feature Branch Workflow
|
|
35
49
|
|
|
36
50
|
```bash
|
|
@@ -107,10 +121,18 @@ git branch -d hotfix/{version}-hotfix{n}
|
|
|
107
121
|
```
|
|
108
122
|
|
|
109
123
|
**Hotfix spec requirement (team convention)**: Hotfixes often skip the
|
|
110
|
-
upfront SDD cycle for speed. This project commits to
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
upfront SDD cycle for speed. This project commits to documenting the fix
|
|
125
|
+
within **24 hours** after it lands. Run `/dflow:modify-existing` in
|
|
126
|
+
**post-hoc mode** (`references/modify-existing-flow.md` Step 1.8): it opens a
|
|
127
|
+
minimal host of its own for the fix, records the implementation checkpoint as
|
|
128
|
+
`reconciled ({merged-hotfix-hash})`, and reconciles rather than re-running work
|
|
129
|
+
that is already on the mainline.
|
|
130
|
+
What gets written is whatever the cascade's tier calls for — a **T2** lands a
|
|
131
|
+
lightweight spec (root cause + fix + a `dflow/specs/migration/tech-debt.md` entry
|
|
132
|
+
if the bug reveals a systemic issue); a **T3** lands one `_index.md` Lightweight
|
|
133
|
+
Changes row and no spec file. Step 1.8 admits **T2 / T3 only**: a **T1**
|
|
134
|
+
post-hoc keeps the normal phase-bearing route and documents the merged work
|
|
135
|
+
there.
|
|
114
136
|
This is a **human-to-human commitment** — Dflow / AI cannot track the
|
|
115
137
|
24-hour clock; the team enforces it in retros.
|
|
116
138
|
|
|
@@ -156,7 +178,10 @@ before making key Git operations:
|
|
|
156
178
|
justification in the spec's notes section)
|
|
157
179
|
- [ ] `_index.md` status reflects the current work (Phase Specs row
|
|
158
180
|
updated, `Resume Pointer` refreshed if the commit reaches a meaningful
|
|
159
|
-
checkpoint)
|
|
181
|
+
checkpoint). A **minimal (zero-phase) host** has no Phase Specs row to
|
|
182
|
+
update — its record is the Lightweight Changes row, and that row must
|
|
183
|
+
already be written **before** this commit, not after it. Refresh the
|
|
184
|
+
Resume Pointer as usual.
|
|
160
185
|
|
|
161
186
|
### Before merging a feature branch to `develop`
|
|
162
187
|
|
|
@@ -170,6 +195,23 @@ before making key Git operations:
|
|
|
170
195
|
- [ ] `dflow/specs/migration/tech-debt.md` updated with any debt discovered
|
|
171
196
|
- [ ] Domain layer (`src/Domain/`) has no delivery-framework references
|
|
172
197
|
|
|
198
|
+
> **Minimal (zero-phase) host.** The items about this host's own record —
|
|
199
|
+
> `/dflow:finish-feature` having run, `_index.md` status `completed`, and the
|
|
200
|
+
> archival move — apply **unchanged**: they are what stops a host merging with
|
|
201
|
+
> its record still open. Every item naming a **Domain or bounded-context
|
|
202
|
+
> artifact** applies only to what this change **actually touched**: a **no-BC**
|
|
203
|
+
> host has no context to sync or document, a **T3** does no Domain work at all,
|
|
204
|
+
> and a **baseline capture** wrote Domain documents directly rather than syncing
|
|
205
|
+
> to them — so for those they read N/A. **Bounded-context-scoped only** —
|
|
206
|
+
> `glossary.md` and `migration/tech-debt.md` belong to no bounded context, stay
|
|
207
|
+
> in a no-BC host's sweep, and are a baseline capture's own capture
|
|
208
|
+
> destinations, so they are **not** N/A for either. **Code invariants are not
|
|
209
|
+
> artifacts** — the items here that state a rule about the **source** rather
|
|
210
|
+
> than name a document to update are **never N/A**: they hold for any change
|
|
211
|
+
> that touches code at all, and a host wrongly claiming to be no-BC is exactly
|
|
212
|
+
> what they catch. Record the N/A; do not create a context, a Domain document,
|
|
213
|
+
> or a BR row to tick a box.
|
|
214
|
+
|
|
173
215
|
---
|
|
174
216
|
|
|
175
217
|
## 4. Integration Commit Message Conventions
|
|
@@ -200,6 +242,31 @@ Related BR-IDs:
|
|
|
200
242
|
Related SPEC-IDs: {SPEC-ID}{, follow-up SPEC-IDs if any}
|
|
201
243
|
```
|
|
202
244
|
|
|
245
|
+
**Zero-phase (minimal host) form.** A minimal host has no phases and may have
|
|
246
|
+
no bounded context, so its Change Scope block carries the values that are
|
|
247
|
+
actually true rather than padded ones:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
Change Scope:
|
|
251
|
+
- BC: none # or the real context, when the change has one
|
|
252
|
+
- Phase Count: 0
|
|
253
|
+
- Lightweight Changes: 1 T2 + 0 T3 # at least one row, always
|
|
254
|
+
|
|
255
|
+
Related BR-IDs: {empty, or the per-family no-BR marker this change carries}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`none` and `0` are the honest values here, not placeholders waiting to be
|
|
259
|
+
filled. **`Related BR-IDs` is the exception — never force it to `none`.**
|
|
260
|
+
It reports what this change's own record carries, not what was synced, so it
|
|
261
|
+
takes the same values a BC-bearing host would: empty, or the per-family no-BR
|
|
262
|
+
marker when the T2 carries one. Writing `none` there erases the marker the
|
|
263
|
+
zero-phase shape requires (`references/finish-feature-flow.md` Step 5 states
|
|
264
|
+
the rule, and a baseline host leaves the field empty). A minimal host that genuinely touches a bounded context reports that
|
|
265
|
+
context and its real BR delta exactly as a phase-bearing feature would — the
|
|
266
|
+
zero is the **phase count**, not the significance. A **baseline capture** is
|
|
267
|
+
the one variant that always reports a real `BC:` — capturing it is the whole
|
|
268
|
+
point of the host.
|
|
269
|
+
|
|
203
270
|
### Concrete example
|
|
204
271
|
|
|
205
272
|
```bash
|
|
@@ -252,7 +319,7 @@ release.
|
|
|
252
319
|
Example entry:
|
|
253
320
|
|
|
254
321
|
```markdown
|
|
255
|
-
## [1.2.3] — {
|
|
322
|
+
## [1.2.3] — {2026-04-21}
|
|
256
323
|
|
|
257
324
|
### Added
|
|
258
325
|
- {SPEC-20260421-001}: JPY currency support in Money value object
|
|
@@ -266,6 +333,8 @@ Example entry:
|
|
|
266
333
|
|
|
267
334
|
---
|
|
268
335
|
|
|
336
|
+
<!-- dflow-generated: git-principles-canonical END -->
|
|
337
|
+
|
|
269
338
|
## 6. AI Collaboration Rules (Project Policy)
|
|
270
339
|
|
|
271
340
|
Three categories:
|
|
@@ -22,6 +22,8 @@ and long-lived release branches.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
<!-- dflow-generated: git-principles-canonical START -->
|
|
26
|
+
|
|
25
27
|
## 1. Branch Structure
|
|
26
28
|
|
|
27
29
|
| Branch | Naming | Cut from | Merges to |
|
|
@@ -35,6 +37,17 @@ happens on short-lived feature / bugfix branches cut from `main` and
|
|
|
35
37
|
merged back into `main` quickly (ideally within a day, always
|
|
36
38
|
within a week).
|
|
37
39
|
|
|
40
|
+
A **`bugfix/{BUG-ID}-{slug}`** branch is the same shape as a feature branch and
|
|
41
|
+
follows the same topology — cut from the same base, pushed the same way,
|
|
42
|
+
integrated the same way, and **closed out the same way**. It is used when a
|
|
43
|
+
defect owns its own host rather than being picked up by a feature already in
|
|
44
|
+
flight. Its host is a **minimal (zero-phase) host**, so it carries no
|
|
45
|
+
phase-spec: the SPEC-ID lives in the directory name while the branch name
|
|
46
|
+
carries the BUG-NUMBER, and the host `_index.md` `branch:` field is
|
|
47
|
+
authoritative for both. Closeout is not optional because the branch prefix
|
|
48
|
+
differs — `/dflow:finish-feature` runs and the directory is archived to
|
|
49
|
+
`completed/` before the branch merges, exactly as for `feature/`.
|
|
50
|
+
|
|
38
51
|
The `feature/{SPEC-ID}-{slug}` pattern is a **Dflow requirement** (not
|
|
39
52
|
a trunk-based requirement). It ties each feature branch to its
|
|
40
53
|
corresponding `dflow/specs/features/active/{SPEC-ID}-{slug}/` directory.
|
|
@@ -76,8 +89,8 @@ Commits must tie back to a SPEC-ID:
|
|
|
76
89
|
|
|
77
90
|
### Conventional Commits style (recommended, optional)
|
|
78
91
|
|
|
79
|
-
|
|
80
|
-
adopted, the format is:
|
|
92
|
+
Conventional Commits is optional. **Record whether this project requires it
|
|
93
|
+
in § 6, under "Commit and merge choices".** When adopted, the format is:
|
|
81
94
|
|
|
82
95
|
```
|
|
83
96
|
{type}({scope}): {short description}
|
|
@@ -103,9 +116,9 @@ Example: `feat(expense): add JPY currency support` with
|
|
|
103
116
|
## 3. Merge Strategy (Project Chooses)
|
|
104
117
|
|
|
105
118
|
Trunk-based strategies typically pick **one** merge style and stick to
|
|
106
|
-
it.
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
it. The table below gives the trade-offs; **record this project's choice
|
|
120
|
+
in § 6, under "Commit and merge choices"** — that section is yours, this
|
|
121
|
+
one is not.
|
|
109
122
|
|
|
110
123
|
| Strategy | How the feature lands on `main` | When to prefer |
|
|
111
124
|
|----------|--------------------------------|----------------|
|
|
@@ -120,7 +133,10 @@ and circle the chosen one.
|
|
|
120
133
|
justification in the spec's notes section)
|
|
121
134
|
- [ ] `_index.md` status reflects the current work (Phase Specs row
|
|
122
135
|
updated, `Resume Pointer` refreshed if the commit reaches a meaningful
|
|
123
|
-
checkpoint)
|
|
136
|
+
checkpoint). A **minimal (zero-phase) host** has no Phase Specs row to
|
|
137
|
+
update — its record is the Lightweight Changes row, and that row must
|
|
138
|
+
already be written **before** this commit, not after it. Refresh the
|
|
139
|
+
Resume Pointer as usual.
|
|
124
140
|
|
|
125
141
|
### Gate Checks — Before merging a feature PR to `main`
|
|
126
142
|
|
|
@@ -136,6 +152,23 @@ and circle the chosen one.
|
|
|
136
152
|
- [ ] CI green
|
|
137
153
|
- [ ] PR has at least one review approval
|
|
138
154
|
|
|
155
|
+
> **Minimal (zero-phase) host.** The items about this host's own record —
|
|
156
|
+
> `/dflow:finish-feature` having run, `_index.md` status `completed`, and the
|
|
157
|
+
> archival move — apply **unchanged**: they are what stops a host merging with
|
|
158
|
+
> its record still open. Every item naming a **Domain or bounded-context
|
|
159
|
+
> artifact** applies only to what this change **actually touched**: a **no-BC**
|
|
160
|
+
> host has no context to sync or document, a **T3** does no Domain work at all,
|
|
161
|
+
> and a **baseline capture** wrote Domain documents directly rather than syncing
|
|
162
|
+
> to them — so for those they read N/A. **Bounded-context-scoped only** —
|
|
163
|
+
> `glossary.md` and `migration/tech-debt.md` belong to no bounded context, stay
|
|
164
|
+
> in a no-BC host's sweep, and are a baseline capture's own capture
|
|
165
|
+
> destinations, so they are **not** N/A for either. **Code invariants are not
|
|
166
|
+
> artifacts** — the items here that state a rule about the **source** rather
|
|
167
|
+
> than name a document to update are **never N/A**: they hold for any change
|
|
168
|
+
> that touches code at all, and a host wrongly claiming to be no-BC is exactly
|
|
169
|
+
> what they catch. Record the N/A; do not create a context, a Domain document,
|
|
170
|
+
> or a BR row to tick a box.
|
|
171
|
+
|
|
139
172
|
---
|
|
140
173
|
|
|
141
174
|
## 4. Integration Commit Message Conventions
|
|
@@ -170,6 +203,31 @@ Related BR-IDs:
|
|
|
170
203
|
Related SPEC-IDs: {SPEC-ID}{, follow-up SPEC-IDs if any}
|
|
171
204
|
```
|
|
172
205
|
|
|
206
|
+
**Zero-phase (minimal host) form.** A minimal host has no phases and may have
|
|
207
|
+
no bounded context, so its Change Scope block carries the values that are
|
|
208
|
+
actually true rather than padded ones:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
Change Scope:
|
|
212
|
+
- BC: none # or the real context, when the change has one
|
|
213
|
+
- Phase Count: 0
|
|
214
|
+
- Lightweight Changes: 1 T2 + 0 T3 # at least one row, always
|
|
215
|
+
|
|
216
|
+
Related BR-IDs: {empty, or the per-family no-BR marker this change carries}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`none` and `0` are the honest values here, not placeholders waiting to be
|
|
220
|
+
filled. **`Related BR-IDs` is the exception — never force it to `none`.**
|
|
221
|
+
It reports what this change's own record carries, not what was synced, so it
|
|
222
|
+
takes the same values a BC-bearing host would: empty, or the per-family no-BR
|
|
223
|
+
marker when the T2 carries one. Writing `none` there erases the marker the
|
|
224
|
+
zero-phase shape requires (`references/finish-feature-flow.md` Step 5 states
|
|
225
|
+
the rule, and a baseline host leaves the field empty). A minimal host that genuinely touches a bounded context reports that
|
|
226
|
+
context and its real BR delta exactly as a phase-bearing feature would — the
|
|
227
|
+
zero is the **phase count**, not the significance. A **baseline capture** is
|
|
228
|
+
the one variant that always reports a real `BC:` — capturing it is the whole
|
|
229
|
+
point of the host.
|
|
230
|
+
|
|
173
231
|
Example:
|
|
174
232
|
|
|
175
233
|
```
|
|
@@ -208,11 +266,17 @@ losing the feature-level summary.
|
|
|
208
266
|
|
|
209
267
|
### 4.3 Fast-forward (feature has exactly 1 commit)
|
|
210
268
|
|
|
211
|
-
If the entire feature was a single commit (
|
|
212
|
-
|
|
269
|
+
If the entire feature was a single commit (a hosted T2 / T3 whose work and
|
|
270
|
+
record landed together), fast-forward is fine. Use the commit
|
|
213
271
|
message format from § 4.1 (squash) for that single commit — it already
|
|
214
272
|
reads as the feature's single narrative.
|
|
215
273
|
|
|
274
|
+
**A minimal (zero-phase) host is never this case.** It records **exactly two**
|
|
275
|
+
commits — the implementation (or `spec-baseline`) checkpoint, then the closeout
|
|
276
|
+
commit that carries the archival `git mv` — so it does not arrive here with
|
|
277
|
+
one. A minimal host showing a single commit has not closed out: run
|
|
278
|
+
`/dflow:finish-feature` first rather than fast-forwarding it as it stands.
|
|
279
|
+
|
|
216
280
|
```bash
|
|
217
281
|
git checkout main
|
|
218
282
|
git pull --ff-only origin main
|
|
@@ -244,6 +308,8 @@ style.
|
|
|
244
308
|
|
|
245
309
|
---
|
|
246
310
|
|
|
311
|
+
<!-- dflow-generated: git-principles-canonical END -->
|
|
312
|
+
|
|
247
313
|
## 6. AI Collaboration Rules (Project Policy)
|
|
248
314
|
|
|
249
315
|
Three categories:
|
|
@@ -292,6 +358,14 @@ can always decline. If your team also wants vendor attribution, appending the
|
|
|
292
358
|
assistant's documented line (e.g. `Co-Authored-By: Claude
|
|
293
359
|
<noreply@anthropic.com>`) is an independent, optional convention on top.
|
|
294
360
|
|
|
361
|
+
### Commit and merge choices
|
|
362
|
+
|
|
363
|
+
Dflow does not choose these for you; §§ 2-3 give the trade-offs. Record what
|
|
364
|
+
this project settled on:
|
|
365
|
+
|
|
366
|
+
- **Conventional Commits**: {required / not required — fill in}
|
|
367
|
+
- **Merge strategy**: {squash / rebase / fast-forward — fill in}
|
|
368
|
+
|
|
295
369
|
---
|
|
296
370
|
|
|
297
371
|
## 7. CI / CD
|