dflow-sdd-ddd 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +915 -0
- package/CONTRIBUTING.md +11 -1
- package/README.en.md +160 -212
- package/README.md +93 -147
- package/TEMPLATE-COVERAGE.md +13 -6
- package/TEMPLATE-LANGUAGE-GLOSSARY.md +15 -1
- package/bin/dflow.js +90 -4
- package/docs/closeout-check.en.md +204 -0
- package/docs/closeout-check.md +204 -0
- package/docs/commands.en.md +112 -0
- package/docs/commands.md +103 -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/release-versioning-policy.md +6 -0
- package/docs/upgrading.en.md +196 -0
- package/docs/upgrading.md +197 -0
- package/docs/using-with-claude-code.en.md +13 -4
- package/docs/using-with-claude-code.md +13 -4
- package/docs/using-with-codex.en.md +13 -4
- package/docs/using-with-codex.md +13 -4
- package/docs/using-with-github-copilot.en.md +13 -4
- package/docs/using-with-github-copilot.md +13 -4
- package/lib/closeout-check.js +690 -0
- package/lib/doc-shapes.json +1195 -0
- package/lib/doctor-checks.js +2500 -24
- package/lib/init.js +2861 -143
- 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 +648 -98
- package/templates/brownfield/references/finish-feature-follow-up.md +60 -0
- package/templates/brownfield/references/finish-feature-minimal-host.md +427 -0
- package/templates/brownfield/references/finish-feature-post-hoc-hotfix.md +95 -0
- package/templates/brownfield/references/finish-feature-record-correction.md +45 -0
- package/templates/brownfield/references/git-integration.md +205 -26
- package/templates/brownfield/references/init-project-flow.md +23 -3
- package/templates/brownfield/references/modify-existing-flow.md +439 -88
- package/templates/brownfield/references/modify-existing-follow-up.md +122 -0
- package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +200 -0
- package/templates/brownfield/references/new-feature-flow.md +110 -37
- package/templates/brownfield/references/new-phase-flow.md +59 -9
- package/templates/brownfield/references/pr-review-checklist.md +328 -10
- package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +151 -34
- package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +1 -1
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +85 -8
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +88 -10
- package/templates/brownfield/scaffolding/_conventions.md +50 -28
- package/templates/brownfield/scaffolding/_overview.md +1 -0
- package/templates/brownfield/templates/_index.md +158 -10
- 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 +146 -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 +639 -100
- package/templates/greenfield/references/finish-feature-follow-up.md +60 -0
- package/templates/greenfield/references/finish-feature-minimal-host.md +383 -0
- package/templates/greenfield/references/finish-feature-post-hoc-hotfix.md +95 -0
- package/templates/greenfield/references/finish-feature-record-correction.md +45 -0
- package/templates/greenfield/references/git-integration.md +193 -26
- package/templates/greenfield/references/init-project-flow.md +23 -3
- package/templates/greenfield/references/modify-existing-flow.md +395 -87
- package/templates/greenfield/references/modify-existing-follow-up.md +104 -0
- package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +200 -0
- package/templates/greenfield/references/new-feature-flow.md +116 -35
- package/templates/greenfield/references/new-phase-flow.md +58 -9
- package/templates/greenfield/references/pr-review-checklist.md +311 -8
- package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +146 -32
- package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +5 -2
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +84 -8
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +94 -14
- package/templates/greenfield/scaffolding/_conventions.md +50 -28
- package/templates/greenfield/scaffolding/_overview.md +6 -2
- package/templates/greenfield/templates/_index.md +145 -10
- 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
package/bin/dflow.js
CHANGED
|
@@ -14,6 +14,7 @@ Usage:
|
|
|
14
14
|
dflow configure-agents Add or update AI agent instruction shims
|
|
15
15
|
dflow doctor Read-only project health check
|
|
16
16
|
dflow render Render the specs Markdown tree to browsable HTML
|
|
17
|
+
dflow check-closeout Gate a closeout commit (for a pre-commit hook or CI)
|
|
17
18
|
dflow --help Show this help
|
|
18
19
|
dflow --version Show the CLI version
|
|
19
20
|
`);
|
|
@@ -67,9 +68,13 @@ function printRenderHelp() {
|
|
|
67
68
|
dflow render [--src <dir>] [--out <dir>] [--title <text>]
|
|
68
69
|
|
|
69
70
|
Renders the Markdown specs tree into a mirrored static HTML tree for human
|
|
70
|
-
reading (record tables become cards, AI markers become badges
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
reading (record tables become cards, AI markers become badges; the filled
|
|
72
|
+
lifecycle and flow tables in analysis.md are also drawn as diagrams), plus
|
|
73
|
+
an index.html at the output root: a grouped directory of the specs (each
|
|
74
|
+
group collapsed until you open it; a lone group starts open) when --src is
|
|
75
|
+
a Dflow specs root (it holds shared/_conventions.md), or a plain file tree
|
|
76
|
+
otherwise. Open index.html directly in a browser; file:// works, no server
|
|
77
|
+
needed.
|
|
73
78
|
|
|
74
79
|
Markdown stays the AI-facing source of truth. Re-run this command whenever
|
|
75
80
|
the sources change; every run is a full rebuild.
|
|
@@ -113,12 +118,75 @@ Read-only health check for the current project. Reports findings such as:
|
|
|
113
118
|
- active feature _index.md files created from an older template shape
|
|
114
119
|
- root agent files (AGENTS.md / CLAUDE.md / copilot-instructions.md)
|
|
115
120
|
with malformed Dflow markers or unmanaged Dflow wording
|
|
116
|
-
- workflow bundle orphans
|
|
121
|
+
- workflow bundle orphans, a bundle projected by an older Dflow, and a
|
|
122
|
+
bundle manifest that is present but unreadable
|
|
123
|
+
- a partly installed set of /dflow:* command files, or ones still using
|
|
124
|
+
the Dflow 0.5.0 filename
|
|
125
|
+
- a Dflow-generated SKILL.md that has fallen behind this CLI
|
|
126
|
+
|
|
127
|
+
Doctor reports only on command and skill files that are already present:
|
|
128
|
+
whether this project should have them is intent, which nothing records.
|
|
117
129
|
|
|
118
130
|
Doctor never modifies files.
|
|
119
131
|
`);
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
function printCheckCloseoutHelp(specGlobs, docUrl) {
|
|
135
|
+
process.stdout.write(`Usage:
|
|
136
|
+
dflow check-closeout [--staged]
|
|
137
|
+
dflow check-closeout --range <base>..<head>
|
|
138
|
+
|
|
139
|
+
A gate for the closeout commit of /dflow:finish-feature: it checks the
|
|
140
|
+
feature directories newly archived to dflow/specs/features/completed/ and
|
|
141
|
+
exits 1 when one is inconsistent, so a pre-commit hook or a CI job can stop
|
|
142
|
+
the commit or the merge request. It reads git only and never modifies
|
|
143
|
+
anything. Run it from the directory that holds dflow/specs/ (the project may
|
|
144
|
+
sit below the repository root).
|
|
145
|
+
|
|
146
|
+
Modes:
|
|
147
|
+
--staged (default) Check the index (what git commit will record),
|
|
148
|
+
for a pre-commit hook. A host the HEAD commit
|
|
149
|
+
itself archived is checked too unless HEAD is a
|
|
150
|
+
merge, so amending a closeout commit is checked;
|
|
151
|
+
the commit after a closeout re-checks that host.
|
|
152
|
+
--range <base>..<head> Check the final state of <head> against its merge
|
|
153
|
+
base with <base>, for CI on a merge request or
|
|
154
|
+
pull request. Needs the full history.
|
|
155
|
+
|
|
156
|
+
In scope: a directory under completed/ that has files after the change and
|
|
157
|
+
had none before. One that was in active/ under the same name (for --range:
|
|
158
|
+
at the merge base or in any commit of the range) is checked. Any other —
|
|
159
|
+
renamed on the way, created directly in completed/, or an archived host
|
|
160
|
+
renamed — is reported as uncertain, for a person to confirm. Hosts that were
|
|
161
|
+
already in completed/ are not read again, except the ones --staged re-checks
|
|
162
|
+
because the HEAD commit archived them.
|
|
163
|
+
|
|
164
|
+
Checked for each newly archived host:
|
|
165
|
+
1. _index.md exists and its frontmatter says status: completed
|
|
166
|
+
2. nothing is left under features/active/<host>/ (moved, not copied)
|
|
167
|
+
3. every spec file (${specGlobs.join(', ')}) says status: completed
|
|
168
|
+
4. the Checkpoint Log has a closeout row (not the template's placeholder)
|
|
169
|
+
whose Result is committed or skipped
|
|
170
|
+
5. --staged only: nothing under the archived directory is left unstaged
|
|
171
|
+
or untracked in the working tree
|
|
172
|
+
|
|
173
|
+
Not checked, and still up to the AI's Step 4 verification and pr-review: the
|
|
174
|
+
file-by-file comparison with the closeout baseline, that the commit carries
|
|
175
|
+
only the paths closeout may write, the identity of hosted Commit cells, and a
|
|
176
|
+
minimal host's hash evidence and exactly-two-commits rule. Passing does not
|
|
177
|
+
mean the closeout is clean.
|
|
178
|
+
|
|
179
|
+
Exit status: 0 when no host is in scope or every one passes; 1 when a check
|
|
180
|
+
fails, a shape cannot be read with confidence ([uncertain]), or the check
|
|
181
|
+
cannot run (not a git repository, no dflow/specs/ here, bad arguments).
|
|
182
|
+
|
|
183
|
+
Hook and CI templates, and how to handle [uncertain]:
|
|
184
|
+
${docUrl}
|
|
185
|
+
(offline: docs/closeout-check.en.md in the installed package; Traditional
|
|
186
|
+
Chinese: docs/closeout-check.md)
|
|
187
|
+
`);
|
|
188
|
+
}
|
|
189
|
+
|
|
122
190
|
async function main() {
|
|
123
191
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
124
192
|
printHelp();
|
|
@@ -206,6 +274,24 @@ async function main() {
|
|
|
206
274
|
});
|
|
207
275
|
}
|
|
208
276
|
|
|
277
|
+
if (args[0] === 'check-closeout') {
|
|
278
|
+
// Loaded only for this subcommand. It reads lib/doc-shapes.json when it runs,
|
|
279
|
+
// never when it loads, so a damaged registry cannot stop `dflow doctor` from
|
|
280
|
+
// starting and reporting it; here a damaged registry is an error.
|
|
281
|
+
const closeoutCheck = require('../lib/closeout-check');
|
|
282
|
+
if (args.length > 1 && (args[1] === '--help' || args[1] === '-h')) {
|
|
283
|
+
printCheckCloseoutHelp(await closeoutCheck.specFileGlobs(), closeoutCheck.CLOSEOUT_CHECK_DOC_URL);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return await closeoutCheck.runCheckCloseout({
|
|
288
|
+
cwd: process.cwd(),
|
|
289
|
+
args: args.slice(1),
|
|
290
|
+
stdout: process.stdout,
|
|
291
|
+
stderr: process.stderr
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
209
295
|
process.stderr.write(`Unsupported subcommand: ${args[0]}\n\n`);
|
|
210
296
|
printHelp();
|
|
211
297
|
return 1;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Checking the closeout commit: `dflow check-closeout`
|
|
2
|
+
|
|
3
|
+
> [繁體中文](closeout-check.md) | **English**
|
|
4
|
+
|
|
5
|
+
> This page tracks the source `main` branch. `dflow check-closeout` first ships in Dflow `0.16.0` (the public source has it from `0.15.5`); if `dflow check-closeout --help` answers `Unsupported subcommand`, your installed CLI predates it. `@latest` installs the latest **published** CLI, which does not guarantee that a feature on this page has been released — the changelog of a published release says what it includes.
|
|
6
|
+
|
|
7
|
+
`/dflow:finish-feature` closes a feature out: it marks the feature directory completed, moves it from `dflow/specs/features/active/` to `dflow/specs/features/completed/` with `git mv`, records the closeout in its `_index.md`, and commits — then checks the commit. Every one of those steps is an instruction the AI follows. When one is skipped, an inconsistent archive can land without anyone noticing: the directory is in `completed/` while the `_index.md` inside it still says `status: in-progress`, or a copy is left behind in `active/`. Nothing reads an archive after that.
|
|
8
|
+
|
|
9
|
+
`dflow check-closeout` moves the mechanical part of that check from "the AI remembers to run it" to "git runs it": a command whose **exit code is a gate**, for a pre-commit hook or a CI job that you set up once. Dflow does not install either for you — the templates are below.
|
|
10
|
+
|
|
11
|
+
## What it checks
|
|
12
|
+
|
|
13
|
+
It looks only at feature directories **newly archived** by the change: a directory under `dflow/specs/features/completed/` that has files after the change and had none before. Whether git pairs the files as a rename (`R`) or as a delete plus an add (`D` + `A`) does not matter.
|
|
14
|
+
|
|
15
|
+
- A directory that was in `active/` under the same name is a newly archived host, and is checked. For `--range`, "was in `active/`" means at the merge base or at any commit in the range — a feature branch usually opens its host and archives it.
|
|
16
|
+
- Any other new directory — renamed on the way from `active/`, created directly in `completed/`, or an archived host renamed inside `completed/` — is reported as `[uncertain]`: `/dflow:finish-feature` moves a host without renaming it, so the gate does not guess which of these a directory is, and a person confirms it.
|
|
17
|
+
|
|
18
|
+
For each newly archived host:
|
|
19
|
+
|
|
20
|
+
1. `_index.md` exists and its frontmatter says `status: completed`.
|
|
21
|
+
2. Nothing is left under `dflow/specs/features/active/<host>/` — the host was moved, not copied.
|
|
22
|
+
3. Every spec in the host says `status: completed`. The spec files are the ones the shipped templates put in a feature directory with a `status` field: `lightweight-*.md`, `BUG-*.md` and `phase-spec-*.md`. A document your project adds to a host is not checked, whatever its frontmatter says.
|
|
23
|
+
4. The `## Checkpoint Log` in `_index.md` has a `closeout` row whose Result is `committed` or `skipped`. The template's own placeholder row (Timestamp `{YYYY-MM-DD HH:MM}`, Result `committed / skipped / failed`) does not count. A Result of `failed` blocks: the commit you are making would record that its own commit failed — usually a hook refused an earlier attempt and the flow flipped the row.
|
|
24
|
+
5. With `--staged` only: nothing under the archived directory is left out of the commit — a tracked file edited or deleted but not staged, or a new file that is neither added nor ignored.
|
|
25
|
+
|
|
26
|
+
It reads the standard shapes exactly — the template's frontmatter, including a trailing `# in-progress | completed` comment and a quoted value; CRLF line endings; a byte-order mark; a directory name in any language. A document copied whole from a template's projected copy under `dflow/specs/shared/dflow-workflows/templates/` starts with `<!-- dflow-generated: workflow-bundle -->`; the frontmatter under that line is not frontmatter (to `dflow render` and `dflow doctor` either), so it fails, with the repair named: delete the line and the blank line after it.
|
|
27
|
+
|
|
28
|
+
## What it does not check
|
|
29
|
+
|
|
30
|
+
The rest of `/dflow:finish-feature`'s post-commit verification needs the closeout baseline and judgement, so it stays with the AI and with pr-review:
|
|
31
|
+
|
|
32
|
+
- the file-by-file comparison of the archive against the closeout baseline;
|
|
33
|
+
- that the closeout commit carries only the paths closeout may write;
|
|
34
|
+
- that a backfilled `Commit` cell holds that row's own implementation hash;
|
|
35
|
+
- for a minimal (zero-phase) host: that checkpoint 1 still carries the hash Step 1 verified, and that the host has exactly two commits. `--range` looks at the final state, so a minimal host repaired by a third commit passes here; pr-review's whole-history item counts it.
|
|
36
|
+
|
|
37
|
+
It does not re-read hosts archived before the change, with one exception: `--staged` checks once more a host the `HEAD` commit itself archived (see "Running it" below). An archive made before this gate existed may be inconsistent, or written in an older shape; checking every old archive would block commits and merge requests that have nothing to do with it.
|
|
38
|
+
|
|
39
|
+
**Passing does not mean the closeout is clean.** It means the parts a machine can judge are consistent.
|
|
40
|
+
|
|
41
|
+
## Running it
|
|
42
|
+
|
|
43
|
+
Run it from the directory that holds `dflow/specs/`. The project may sit below the repository root.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dflow check-closeout # the same as --staged
|
|
47
|
+
dflow check-closeout --staged # a pre-commit hook: checks the index — what the commit will record
|
|
48
|
+
dflow check-closeout --range <base>..<head> # CI: checks the final state of <head> against its merge base with <base>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `--staged` also checks a host the `HEAD` commit itself archived (unless `HEAD` is a merge commit). A pre-commit hook cannot tell `git commit --amend` from a new commit, and amending is how a minimal host repairs its closeout commit, so a host already in `HEAD` has to stay in scope. The cost: the commit right after a closeout checks that host once more, and passes when it is still consistent.
|
|
52
|
+
- `--range` checks the **final state** of `<head>`, not each commit on the way: a closeout repaired by a later commit on the branch passes. It needs the full history (`git merge-base` has to find the base).
|
|
53
|
+
|
|
54
|
+
### Exit status
|
|
55
|
+
|
|
56
|
+
| Exit | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `0` | No feature directory is newly archived, or every one passed. |
|
|
59
|
+
| `1` | A check failed (`[fail]`), a shape could not be read with confidence (`[uncertain]`), or the check could not run: not a git repository, no `dflow/specs/` in the current directory, bad arguments. |
|
|
60
|
+
|
|
61
|
+
Each problem names the host, what is wrong, and how to fix it.
|
|
62
|
+
|
|
63
|
+
### When it says `[uncertain]`
|
|
64
|
+
|
|
65
|
+
An `[uncertain]` result blocks too: the gate will not report something it cannot read as passing. It appears for a shape the gate does not guess about — a spec with no frontmatter, or with two `status:` fields; no `## Checkpoint Log`, or one without `Checkpoint` and `Result` columns; a closeout row written in an older way (the explanation and the checkpoint in one cell, such as `closeout(/dflow:finish-feature):…`); a new directory that was never in `active/` under its name — including an archived host you renamed.
|
|
66
|
+
|
|
67
|
+
1. **Fix the shape if you can.** The message says how: restore the template's `status:` line, rewrite the row as Checkpoint `closeout` and Result `committed` or `skipped`. That is the durable fix — the next run reads it.
|
|
68
|
+
2. **If you read it and it is right,** let that one commit through: `git commit --no-verify` for the hook; for CI, whatever your team uses to accept a merge request with a known failing job.
|
|
69
|
+
|
|
70
|
+
⚠ Because of the amend rule above, a host that `HEAD` moved from `active/` is checked once more by the commit after it. So when you let such a host through with `--no-verify`, the next commit is blocked by it again; fixing the shape avoids that, otherwise that commit needs `--no-verify` too. (A directory reported because it was never in `active/` is not checked again.)
|
|
71
|
+
|
|
72
|
+
## Pre-commit hook
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
#!/bin/sh
|
|
76
|
+
# dflow check-closeout pre-commit hook
|
|
77
|
+
# https://github.com/weilung/dflow-sdd-ddd/blob/main/docs/closeout-check.en.md
|
|
78
|
+
#
|
|
79
|
+
# If the Dflow project is not at the repository root, uncomment and set this:
|
|
80
|
+
# cd path/to/project || exit 1
|
|
81
|
+
|
|
82
|
+
# The same "before" that dflow check-closeout --staged uses: HEAD's parent when
|
|
83
|
+
# HEAD is an ordinary commit (so amending a closeout commit is still checked),
|
|
84
|
+
# HEAD itself when it is a merge or the first commit, and the empty tree before
|
|
85
|
+
# any commit exists.
|
|
86
|
+
if head=$(git rev-list --parents -n 1 HEAD 2>/dev/null); then
|
|
87
|
+
set -- $head
|
|
88
|
+
if [ $# -eq 2 ]; then before=HEAD^; else before=HEAD; fi
|
|
89
|
+
else
|
|
90
|
+
before=$(git hash-object -t tree /dev/null)
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Nothing under completed/ changed: an ordinary commit, and Node is not started.
|
|
94
|
+
if git diff --cached --quiet "$before" -- dflow/specs/features/completed/; then
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
if ! command -v dflow >/dev/null 2>&1; then
|
|
99
|
+
echo "dflow check-closeout skipped: no dflow CLI on this machine (a CI job is the second line)." >&2
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
exec dflow check-closeout --staged
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
What it does, in order:
|
|
107
|
+
|
|
108
|
+
1. It compares the index with the same "before" as `--staged`. When nothing under `completed/` changed, it exits at once — an ordinary commit costs a couple of git calls and never starts Node. A message-only amend of a closeout commit still reaches the gate, and so does the commit right after a closeout.
|
|
109
|
+
2. When the `dflow` CLI is not installed on this machine, it says so in one line and lets the commit through: not everyone on a team has the CLI, and a CI job is the second line.
|
|
110
|
+
3. Otherwise it runs `dflow check-closeout --staged`, and its exit code decides.
|
|
111
|
+
|
|
112
|
+
**Installing it** — pick one:
|
|
113
|
+
|
|
114
|
+
- **Shared with the team.** Save it as `.githooks/pre-commit` in the repository, make it executable (`chmod +x .githooks/pre-commit`), commit it, and have every clone run once:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git config core.hooksPath .githooks
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **This clone only.** Save it as `.git/hooks/pre-commit` and make it executable.
|
|
121
|
+
- **You already use a hook manager.** Save the script in the repository (for example `.githooks/dflow-closeout.sh`) and call it from the manager's configuration:
|
|
122
|
+
- husky — add a line to `.husky/pre-commit`: `sh .githooks/dflow-closeout.sh`
|
|
123
|
+
- lefthook — in `lefthook.yml`:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
pre-commit:
|
|
127
|
+
commands:
|
|
128
|
+
dflow-closeout:
|
|
129
|
+
run: sh .githooks/dflow-closeout.sh
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- pre-commit — in `.pre-commit-config.yaml`:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
repos:
|
|
136
|
+
- repo: local
|
|
137
|
+
hooks:
|
|
138
|
+
- id: dflow-closeout
|
|
139
|
+
name: dflow check-closeout
|
|
140
|
+
entry: sh .githooks/dflow-closeout.sh
|
|
141
|
+
language: system
|
|
142
|
+
pass_filenames: false
|
|
143
|
+
always_run: true
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
⚠ The pre-commit framework sets unstaged edits to tracked files aside before it runs hooks, so check 5 does not see those there. Checks 1–4 read what is being committed either way.
|
|
147
|
+
|
|
148
|
+
On Windows, Git for Windows runs the hook with its own `sh`. When the Dflow project is below the repository root, uncomment the `cd` line: a hook starts at the repository root.
|
|
149
|
+
|
|
150
|
+
## CI
|
|
151
|
+
|
|
152
|
+
The CI job is the second line: it catches what reached the branch without the hook — a clone that never set it up, a machine without the CLI, a `--no-verify`. Both templates run the CLI with `npx`, taking the version from your project's workflow bundle (`"version"` in `dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json`), so the gate follows the rules of the Dflow version your project uses — and follows it again after you upgrade. That version must include `check-closeout`; a CLI of another version judges by its own rules. The job needs Node 22 or later (`package.json` `engines`), and the full history.
|
|
153
|
+
|
|
154
|
+
### GitLab (merge request pipeline)
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
dflow-closeout:
|
|
158
|
+
image: node:22
|
|
159
|
+
rules:
|
|
160
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
161
|
+
variables:
|
|
162
|
+
GIT_DEPTH: "0"
|
|
163
|
+
script:
|
|
164
|
+
# - cd path/to/project # if the Dflow project is not at the repository root
|
|
165
|
+
- DFLOW_VERSION=$(node -p "require('./dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json').version")
|
|
166
|
+
# A merged results pipeline builds on a merge commit; the source branch head is what this merge request archived.
|
|
167
|
+
- npx --yes "dflow-sdd-ddd@$DFLOW_VERSION" check-closeout --range "$CI_MERGE_REQUEST_DIFF_BASE_SHA..${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The image needs `git` as well as Node — `node:22` has it; an Alpine image does not.
|
|
171
|
+
|
|
172
|
+
### GitHub Actions (pull request)
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
name: Dflow closeout check
|
|
176
|
+
on: pull_request
|
|
177
|
+
jobs:
|
|
178
|
+
closeout:
|
|
179
|
+
runs-on: ubuntu-latest
|
|
180
|
+
steps:
|
|
181
|
+
- uses: actions/checkout@v6
|
|
182
|
+
with:
|
|
183
|
+
fetch-depth: 0
|
|
184
|
+
- uses: actions/setup-node@v6
|
|
185
|
+
with:
|
|
186
|
+
node-version: 22
|
|
187
|
+
- name: dflow check-closeout
|
|
188
|
+
# working-directory: path/to/project # if the Dflow project is not at the repository root
|
|
189
|
+
env:
|
|
190
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
191
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
192
|
+
run: |
|
|
193
|
+
DFLOW_VERSION=$(node -p "require('./dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json').version")
|
|
194
|
+
npx --yes "dflow-sdd-ddd@$DFLOW_VERSION" check-closeout --range "$PR_BASE..$PR_HEAD"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`--range` uses the pull request's head commit, not the merge commit GitHub checks out: a merge commit also carries whatever reached the base branch in the meantime, and someone else's archive should not decide your pull request.
|
|
198
|
+
|
|
199
|
+
## What is left open
|
|
200
|
+
|
|
201
|
+
- **Nothing is protected until it is set up.** This is set up once per repository (CI) and once per clone (the hook); it is never on by default.
|
|
202
|
+
- **It can be bypassed:** `git commit --no-verify`, or a machine without the CLI. Only a CI job closes that.
|
|
203
|
+
- **Only the mechanical part is checked** (above). The AI's verification in `/dflow:finish-feature` and pr-review still carry the rest.
|
|
204
|
+
- **It follows the templates.** If a later template changes how `status` or the Checkpoint Log is written, the gate changes with it in the same release; a CLI and a bundle of different versions can disagree.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# 收尾 commit 的機械檢查:`dflow check-closeout`
|
|
2
|
+
|
|
3
|
+
> **繁體中文** | [English](closeout-check.en.md)
|
|
4
|
+
|
|
5
|
+
> 本頁跟著原始碼的 `main` 分支走。`dflow check-closeout` 從 Dflow `0.16.0` 起才有(公開的原始碼從 `0.15.5` 起就有);如果 `dflow check-closeout --help` 回 `Unsupported subcommand`,就是你裝的 CLI 比它早。`@latest` 裝的是最新**已發布**的 CLI,不保證本頁講的功能已經發布——一個已發布版本包含什麼,看它的 changelog。
|
|
6
|
+
|
|
7
|
+
`/dflow:finish-feature` 收尾一個 feature 時:把 feature 目錄標成完成、用 `git mv` 從 `dflow/specs/features/active/` 搬到 `dflow/specs/features/completed/`、在 `_index.md` 記下收尾,然後 commit——commit 完再檢查一遍。這些步驟每一步都是交給 AI 照做的指示。其中一步被跳過,不一致的歸檔就可能沒有人發現地落地:目錄已經在 `completed/`,裡面的 `_index.md` 卻還寫著 `status: in-progress`;或是 `active/` 裡留著一份副本。落地之後,不會再有任何東西去讀這份歸檔。
|
|
8
|
+
|
|
9
|
+
`dflow check-closeout` 把這份檢查裡機械判得準的部分,從「靠 AI 記得跑」改成「由 git 觸發」:它是一個**以結束碼當閘**的指令,接在 pre-commit hook 或 CI job 上,設定一次就好。Dflow 不會替你安裝這兩者——接法範本在下面。
|
|
10
|
+
|
|
11
|
+
## 檢查什麼
|
|
12
|
+
|
|
13
|
+
它只看這次變動**新歸檔**的 feature 目錄:`dflow/specs/features/completed/` 底下,變動之後有檔、變動之前一個檔都沒有的目錄。git 把這些檔配成改名(`R`)還是刪除加新增(`D` + `A`),不影響判斷。
|
|
14
|
+
|
|
15
|
+
- 原本在 `active/`、而且同名的目錄,就是新歸檔的 host(掛載這次變更的規格紀錄目錄,不是程式目錄),要檢查。用 `--range` 時,「原本在 `active/`」指的是 merge base 當時在,或範圍裡任何一顆 commit 碰過它——一條 feature 分支通常自己建 host、也自己收尾。
|
|
16
|
+
- 其餘新出現的目錄——從 `active/` 搬過來時順便改了名、直接建在 `completed/`、或替 `completed/` 裡已歸檔的 host 改名——報成 `[uncertain]`:`/dflow:finish-feature` 搬 host 時不改名,所以閘不去猜它是其中哪一種,交給人確認。
|
|
17
|
+
|
|
18
|
+
每一個新歸檔的 host 檢查五項:
|
|
19
|
+
|
|
20
|
+
1. `_index.md` 存在,frontmatter 寫的是 `status: completed`。
|
|
21
|
+
2. `dflow/specs/features/active/<host>/` 底下沒有留下任何檔——host 是搬走的,不是複製的。
|
|
22
|
+
3. host 裡每一份 spec 都寫著 `status: completed`。哪些是 spec,看出貨範本放進 feature 目錄、而且帶 `status` 欄位的那幾種:`lightweight-*.md`、`BUG-*.md`、`phase-spec-*.md`。專案自己加進 host 的文件不檢查,不管它的 frontmatter 寫了什麼。
|
|
23
|
+
4. `_index.md` 的 `## Checkpoint Log` 有一列 `closeout`,Result 是 `committed` 或 `skipped`。範本自帶的那一列佔位(Timestamp `{YYYY-MM-DD HH:MM}`、Result `committed / skipped / failed`)不算。Result 是 `failed` 會擋下:你正要做的這顆 commit 會記下「它自己的 commit 失敗了」——通常是先前一次被 hook 擋下,flow 把那一列改成了 `failed`。
|
|
24
|
+
5. 只有 `--staged`:歸檔目錄底下沒有被這顆 commit 漏掉的東西——已追蹤的檔改了或刪了卻沒 stage,或是新檔既沒 add、也沒被忽略。
|
|
25
|
+
|
|
26
|
+
標準寫法它都判得準:範本的 frontmatter(包括行尾的 `# in-progress | completed` 註解、加了引號的值)、CRLF 換行、BOM(檔案開頭的位元組順序標記)、任何語言的目錄名。整份照 `dflow/specs/shared/dflow-workflows/templates/` 底下投影出去的範本複製的文件,第一行會是 `<!-- dflow-generated: workflow-bundle -->`;那一行底下的 frontmatter 不算 frontmatter(`dflow render` 與 `dflow doctor` 也這樣讀),所以會擋下,並寫明修法:刪掉那一行和它後面的空行。
|
|
27
|
+
|
|
28
|
+
## 不檢查什麼
|
|
29
|
+
|
|
30
|
+
`/dflow:finish-feature` 在 commit 之後的驗證,其餘部分需要收尾時的基準(baseline)與判斷,所以仍然由 AI 做、由 pr-review 把關:
|
|
31
|
+
|
|
32
|
+
- 歸檔內容對照收尾基準的逐檔比對;
|
|
33
|
+
- 收尾 commit 只帶收尾准許寫的路徑;
|
|
34
|
+
- 回填的 `Commit` 格填的是那一列自己的實作 hash;
|
|
35
|
+
- 最小 host(沒有 phase 的 host):checkpoint 1 仍帶著 Step 1 驗過的 hash,以及這個 host 剛好兩顆 commit。`--range` 看的是最終狀態,所以用第三顆 commit 修好的最小 host 在這裡會通過;那一顆由 pr-review 檢查整段歷史的那一項數到。
|
|
36
|
+
|
|
37
|
+
這次變動之前就歸檔的 host,它不回頭讀,只有一個例外:`--staged` 會再檢查一次 `HEAD` 這顆 commit 自己歸檔的 host(見下面「怎麼跑」)。這個閘出現之前的歸檔,可能本來就不一致,或是用較早的寫法寫成;每次都把舊歸檔全部讀一遍,會擋下跟它們毫無關係的 commit 與 merge request。
|
|
38
|
+
|
|
39
|
+
**通過不代表收尾是乾淨的。** 它只代表機器判得準的那一部分是一致的。
|
|
40
|
+
|
|
41
|
+
## 怎麼跑
|
|
42
|
+
|
|
43
|
+
在含有 `dflow/specs/` 的目錄執行。專案可以在 repository 根目錄底下的子目錄。
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dflow check-closeout # 同 --staged
|
|
47
|
+
dflow check-closeout --staged # pre-commit hook:檢查 index,也就是這顆 commit 要記下的內容
|
|
48
|
+
dflow check-closeout --range <base>..<head> # CI:拿 <head> 的最終狀態,和它與 <base> 的 merge base 比
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `--staged` 也會檢查 `HEAD` 這顆 commit 自己歸檔的 host(`HEAD` 是 merge commit 時除外)。pre-commit hook 分不出 `git commit --amend` 和一顆新 commit,而最小 host 修正收尾 commit 的方式正是 amend,所以已經在 `HEAD` 裡的 host 必須留在範圍內。代價是:收尾之後的下一顆 commit 會再檢查一次那個 host——它仍然一致就照樣通過。
|
|
52
|
+
- `--range` 檢查 `<head>` 的**最終狀態**,不是一顆一顆 commit 查:收尾之後在分支上用另一顆 commit 修好的,照樣通過。它需要完整的歷史(`git merge-base` 要找得到基準)。
|
|
53
|
+
|
|
54
|
+
### 結束碼
|
|
55
|
+
|
|
56
|
+
| 結束碼 | 意思 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `0` | 沒有新歸檔的 feature 目錄,或每一個都通過。 |
|
|
59
|
+
| `1` | 有檢查沒過(`[fail]`)、有它看不準的寫法(`[uncertain]`),或跑不起來:不是 git repository、目前目錄沒有 `dflow/specs/`、參數錯。 |
|
|
60
|
+
|
|
61
|
+
每一個問題都會寫出是哪個 host、哪裡不對、怎麼修。
|
|
62
|
+
|
|
63
|
+
### 它說 `[uncertain]` 的時候
|
|
64
|
+
|
|
65
|
+
`[uncertain]` 一樣會擋下:閘不會把它讀不準的東西報成通過。它出現在閘不去猜的寫法上——spec 沒有 frontmatter,或有兩個 `status:`;沒有 `## Checkpoint Log`,或那張表沒有 `Checkpoint` 與 `Result` 欄;closeout 列用了較早的寫法(說明與 checkpoint 寫在同一格,例如 `closeout(/dflow:finish-feature):…`);新出現的目錄從來沒以這個名字在 `active/` 裡——包括你替已歸檔的 host 改了名。
|
|
66
|
+
|
|
67
|
+
1. **能改成標準寫法就改。** 訊息會說怎麼改:把範本的 `status:` 那一行放回去、把那一列改寫成 Checkpoint `closeout`、Result `committed` 或 `skipped`。這是一勞永逸的做法——下一次就讀得到。
|
|
68
|
+
2. **你讀過、確認沒問題的話**,只讓這一顆 commit 通過:hook 用 `git commit --no-verify`;CI 則照你們團隊接受「已知某個 job 失敗」的 merge request 的方式處理。
|
|
69
|
+
|
|
70
|
+
⚠ 因為上面那條 amend 的規則,`HEAD` 從 `active/` 搬過來的 host,下一顆 commit 還會再檢查一次。所以這樣的 host 用 `--no-verify` 放過之後,下一顆 commit 會再被它擋一次;改成標準寫法就沒有這個問題,不改的話,那一顆也要 `--no-verify`。(因為從來沒在 `active/` 裡而被報出來的目錄,不會再檢查一次。)
|
|
71
|
+
|
|
72
|
+
## pre-commit hook
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
#!/bin/sh
|
|
76
|
+
# dflow check-closeout pre-commit hook
|
|
77
|
+
# https://github.com/weilung/dflow-sdd-ddd/blob/main/docs/closeout-check.en.md
|
|
78
|
+
#
|
|
79
|
+
# If the Dflow project is not at the repository root, uncomment and set this:
|
|
80
|
+
# cd path/to/project || exit 1
|
|
81
|
+
|
|
82
|
+
# The same "before" that dflow check-closeout --staged uses: HEAD's parent when
|
|
83
|
+
# HEAD is an ordinary commit (so amending a closeout commit is still checked),
|
|
84
|
+
# HEAD itself when it is a merge or the first commit, and the empty tree before
|
|
85
|
+
# any commit exists.
|
|
86
|
+
if head=$(git rev-list --parents -n 1 HEAD 2>/dev/null); then
|
|
87
|
+
set -- $head
|
|
88
|
+
if [ $# -eq 2 ]; then before=HEAD^; else before=HEAD; fi
|
|
89
|
+
else
|
|
90
|
+
before=$(git hash-object -t tree /dev/null)
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Nothing under completed/ changed: an ordinary commit, and Node is not started.
|
|
94
|
+
if git diff --cached --quiet "$before" -- dflow/specs/features/completed/; then
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
if ! command -v dflow >/dev/null 2>&1; then
|
|
99
|
+
echo "dflow check-closeout skipped: no dflow CLI on this machine (a CI job is the second line)." >&2
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
exec dflow check-closeout --staged
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
它依序做三件事:
|
|
107
|
+
|
|
108
|
+
1. 拿 index 和 `--staged` 用的同一個「之前」比。`completed/` 底下什麼都沒變,就直接放行——一般的 commit 只多幾次 git 呼叫,不會啟動 Node。只改訊息的 amend 收尾 commit 照樣會呼叫閘,收尾之後的下一顆 commit 也會。
|
|
109
|
+
2. 這台電腦沒裝 `dflow` CLI 時,印一行說明,然後放行:團隊裡不一定每個人都裝了 CLI,第二道防線是 CI job。
|
|
110
|
+
3. 其餘情況執行 `dflow check-closeout --staged`,由它的結束碼決定放不放行。
|
|
111
|
+
|
|
112
|
+
**安裝方式**——三選一:
|
|
113
|
+
|
|
114
|
+
- **跟團隊共用。** 存成 repository 裡的 `.githooks/pre-commit`,設為可執行(`chmod +x .githooks/pre-commit`)並 commit;每個 clone 各執行一次:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git config core.hooksPath .githooks
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **只給這個 clone 用。** 存成 `.git/hooks/pre-commit`,設為可執行。
|
|
121
|
+
- **已經在用 hook 管理工具。** 把腳本存進 repository(例如 `.githooks/dflow-closeout.sh`),在管理工具的設定裡呼叫它:
|
|
122
|
+
- husky——在 `.husky/pre-commit` 加一行:`sh .githooks/dflow-closeout.sh`
|
|
123
|
+
- lefthook——在 `lefthook.yml`:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
pre-commit:
|
|
127
|
+
commands:
|
|
128
|
+
dflow-closeout:
|
|
129
|
+
run: sh .githooks/dflow-closeout.sh
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- pre-commit——在 `.pre-commit-config.yaml`:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
repos:
|
|
136
|
+
- repo: local
|
|
137
|
+
hooks:
|
|
138
|
+
- id: dflow-closeout
|
|
139
|
+
name: dflow check-closeout
|
|
140
|
+
entry: sh .githooks/dflow-closeout.sh
|
|
141
|
+
language: system
|
|
142
|
+
pass_filenames: false
|
|
143
|
+
always_run: true
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
⚠ pre-commit 這套工具在跑 hook 之前,會把已追蹤檔案上沒 stage 的修改先收起來,所以在它底下,第 5 項看不到這些修改。第 1–4 項讀的本來就是要 commit 的內容,不受影響。
|
|
147
|
+
|
|
148
|
+
在 Windows 上,Git for Windows 用它自帶的 `sh` 跑 hook。Dflow 專案不在 repository 根目錄時,把 `cd` 那一行的註解拿掉並改成實際路徑:hook 一開始是在 repository 根目錄。
|
|
149
|
+
|
|
150
|
+
## CI
|
|
151
|
+
|
|
152
|
+
CI job 是第二道防線:它抓的是沒經過 hook 就進了分支的東西——沒設定 hook 的 clone、沒裝 CLI 的電腦、`--no-verify`。兩份範本都用 `npx` 跑 CLI,版號取自專案的 workflow bundle(`dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json` 的 `"version"`),所以閘照專案正在用的那一版 Dflow 的規則判,升級之後也會跟著換。那一版必須已經有 `check-closeout`;換成別的版本,就照那一版自己的規則判。job 需要 Node 22 以上(`package.json` 的 `engines`),以及完整的歷史。
|
|
153
|
+
|
|
154
|
+
### GitLab(merge request pipeline)
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
dflow-closeout:
|
|
158
|
+
image: node:22
|
|
159
|
+
rules:
|
|
160
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
161
|
+
variables:
|
|
162
|
+
GIT_DEPTH: "0"
|
|
163
|
+
script:
|
|
164
|
+
# - cd path/to/project # if the Dflow project is not at the repository root
|
|
165
|
+
- DFLOW_VERSION=$(node -p "require('./dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json').version")
|
|
166
|
+
# A merged results pipeline builds on a merge commit; the source branch head is what this merge request archived.
|
|
167
|
+
- npx --yes "dflow-sdd-ddd@$DFLOW_VERSION" check-closeout --range "$CI_MERGE_REQUEST_DIFF_BASE_SHA..${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
image 除了 Node 還要有 `git`——`node:22` 有;Alpine 的 image 沒有。
|
|
171
|
+
|
|
172
|
+
### GitHub Actions(pull request)
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
name: Dflow closeout check
|
|
176
|
+
on: pull_request
|
|
177
|
+
jobs:
|
|
178
|
+
closeout:
|
|
179
|
+
runs-on: ubuntu-latest
|
|
180
|
+
steps:
|
|
181
|
+
- uses: actions/checkout@v6
|
|
182
|
+
with:
|
|
183
|
+
fetch-depth: 0
|
|
184
|
+
- uses: actions/setup-node@v6
|
|
185
|
+
with:
|
|
186
|
+
node-version: 22
|
|
187
|
+
- name: dflow check-closeout
|
|
188
|
+
# working-directory: path/to/project # if the Dflow project is not at the repository root
|
|
189
|
+
env:
|
|
190
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
191
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
192
|
+
run: |
|
|
193
|
+
DFLOW_VERSION=$(node -p "require('./dflow/specs/shared/dflow-workflows/.dflow-bundle-manifest.json').version")
|
|
194
|
+
npx --yes "dflow-sdd-ddd@$DFLOW_VERSION" check-closeout --range "$PR_BASE..$PR_HEAD"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`--range` 用的是 pull request 的 head commit,不是 GitHub checkout 出來的那顆 merge commit:merge commit 還帶著這段期間進了 base 分支的其他東西,別人的歸檔不該決定你的 pull request。
|
|
198
|
+
|
|
199
|
+
## 做了之後仍然存在的風險
|
|
200
|
+
|
|
201
|
+
- **沒接就沒有保護。** 這是每個 repository 設定一次(CI)、每個 clone 設定一次(hook)的東西,不是預設開啟。
|
|
202
|
+
- **可以繞過:** `git commit --no-verify`,或一台沒裝 CLI 的電腦。只有 CI job 補得上這一塊。
|
|
203
|
+
- **只檢查機械判得準的那一部分**(見上)。其餘仍然靠 `/dflow:finish-feature` 裡 AI 的驗證與 pr-review。
|
|
204
|
+
- **它跟著範本走。** 之後的範本若改了 `status` 或 Checkpoint Log 的寫法,閘會在同一個版本跟著改;CLI 與 bundle 版本不同時,兩者可能判得不一樣。
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Dflow Command Reference
|
|
2
|
+
|
|
3
|
+
> [繁體中文](commands.md) | **English**
|
|
4
|
+
|
|
5
|
+
> **You do not need this page for normal use.** Describe what you want to do and the AI
|
|
6
|
+
> picks the workflow, stopping at each decision point to confirm with you — that is how
|
|
7
|
+
> Dflow is meant to be used, see
|
|
8
|
+
> [README "You don't need to learn the commands first"](../README.en.md#you-dont-need-to-learn-the-commands-first).
|
|
9
|
+
>
|
|
10
|
+
> This page is for three situations: you want to name a flow directly, the AI picked the
|
|
11
|
+
> wrong one and you want to correct it, or you are taking stock of what Dflow covers.
|
|
12
|
+
|
|
13
|
+
## Command Inventory
|
|
14
|
+
|
|
15
|
+
Dflow commands fall into four roles.
|
|
16
|
+
|
|
17
|
+
### Entry commands (start a workflow)
|
|
18
|
+
|
|
19
|
+
Start a workflow run; usable with no existing feature. The three are independent — none
|
|
20
|
+
is a prerequisite for another.
|
|
21
|
+
|
|
22
|
+
| Flow | When | Typical output |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `/dflow:new-feature` | A brand-new capability, or a new business rule the system must implement | Feature directory + `_index.md` + first phase-spec (always T1) |
|
|
25
|
+
| `/dflow:modify-existing` | Changing existing behavior — use it when **you are not sure which category the change falls into**; the AI routes internally | T1 → escalate to new-phase / new-feature; T2 → lightweight-spec; T3 → one inline row in `_index.md` |
|
|
26
|
+
| `/dflow:bug-fix` | A defect you can state as expected vs actual behavior | The AI judges the tier (usually a T2 lightweight-spec). An orphan bug with no host feature opens a minimal (zero-phase) host: a **functional bug** goes to `bugfix/BUG-{NUMBER}-{slug}`, other standalone T2 / T3 work to `feature/{SPEC-ID}-{slug}` |
|
|
27
|
+
|
|
28
|
+
⚠ `/dflow:bug-fix` and `/dflow:modify-existing` **run the same flow document**; the tier
|
|
29
|
+
comes from the ceremony cascade, not from which command you typed. Picking the wrong one
|
|
30
|
+
of these two has no consequence.
|
|
31
|
+
|
|
32
|
+
### Feature-internal commands (active feature only)
|
|
33
|
+
|
|
34
|
+
Available only inside a started, active feature. Pointing them at a `completed/` feature
|
|
35
|
+
is rejected.
|
|
36
|
+
|
|
37
|
+
| Flow | When | Typical output |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `/dflow:new-phase` | An active feature needs another implementation slice | A new `phase-spec-{date}-{slug}.md` + Implementation Tasks + implementation / verification + phase marked complete (always T1) |
|
|
40
|
+
| `/dflow:finish-feature` | Every phase is done and the feature is being closed out | `git mv` the whole feature dir to `completed/`, sync the BR Snapshot to the BC layer, emit an Integration Summary (no auto-merge) |
|
|
41
|
+
|
|
42
|
+
### Workflow control (manage an in-progress run)
|
|
43
|
+
|
|
44
|
+
| Flow | When |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `/dflow:status` | See which workflow / Step you are in and how far along |
|
|
47
|
+
| `/dflow:next` | Confirm a Step Gate (same as saying "OK" / "continue") |
|
|
48
|
+
| `/dflow:cancel` | Abandon the current run and return to free conversation. Artifacts already created are kept |
|
|
49
|
+
|
|
50
|
+
### Standalone tools (callable any time, not tied to a feature or workflow)
|
|
51
|
+
|
|
52
|
+
| Flow | When | Typical output |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `/dflow:verify` | You need to confirm docs, code, tests and debt records still agree | A drift report across specs, domain docs, implementation, tests and debt |
|
|
55
|
+
| `/dflow:pr-review` | A change is ready for review | An SDD/DDD compliance review list with risks, gaps and follow-ups |
|
|
56
|
+
| `/dflow:report-dflow-feedback` | You or the AI hit a problem in Dflow itself | A sanitized local draft, field-by-field aligned with the upstream issue form and ready to paste; nothing is sent automatically |
|
|
57
|
+
|
|
58
|
+
## What should I run? (rule of thumb)
|
|
59
|
+
|
|
60
|
+
**If you are not sure, you do not have to choose** — say what you want, or just run
|
|
61
|
+
`/dflow:modify-existing` and let the AI route it. The table below is for when you want to
|
|
62
|
+
skip that and name the flow yourself.
|
|
63
|
+
|
|
64
|
+
| What I want to do | Command |
|
|
65
|
+
|---|---|
|
|
66
|
+
| A brand-new capability (unrelated to any existing feature) | `/dflow:new-feature` |
|
|
67
|
+
| Add the next planned phase to an active feature | `/dflow:new-phase` |
|
|
68
|
+
| Fix a specific bug | `/dflow:bug-fix` |
|
|
69
|
+
| **Not sure** how to classify it, but it changes something that exists | `/dflow:modify-existing` |
|
|
70
|
+
| Every phase of a feature is done and it needs closing out | `/dflow:finish-feature` |
|
|
71
|
+
| Run a change review | `/dflow:pr-review` |
|
|
72
|
+
| Check for doc / code drift | `/dflow:verify` |
|
|
73
|
+
|
|
74
|
+
## How to type them in each AI tool
|
|
75
|
+
|
|
76
|
+
`/dflow:*` is Dflow's canonical shared vocabulary; each AI tool's `/` parser behaves
|
|
77
|
+
differently. Type them like this:
|
|
78
|
+
|
|
79
|
+
| Tool | How to invoke |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Claude Code (after installing `--command-adapters`) | `/dflow:<id>`, e.g. `/dflow:new-feature` |
|
|
82
|
+
| GitHub Copilot (VS Code Chat) | Use `/dflow-<id>` as the command entry (hyphen, needs `--command-adapters`); natural-language auto-trigger also works. `/dflow:<id>` (colon) is only a way to refer to it in prose, not a command |
|
|
83
|
+
| GitHub Copilot CLI | No per-id commands; type `/dflow` to invoke the skill, then describe the workflow in natural language |
|
|
84
|
+
| Codex CLI | Plain text without a slash: `dflow:<id>`, e.g. `dflow:new-feature` |
|
|
85
|
+
|
|
86
|
+
If your tool has no custom slash commands, type the workflow name as an ordinary chat
|
|
87
|
+
message. Dflow is Markdown-based workflow material plus a scaffolding CLI, and works with
|
|
88
|
+
any AI coding assistant that can read project instructions and repository context.
|
|
89
|
+
|
|
90
|
+
**Command adapters are not required.** They are opt-in (`dflow configure-agents
|
|
91
|
+
--command-adapters`) and only turn these names into native `/` menu entries; natural
|
|
92
|
+
language triggering and plain-text invocation work without them.
|
|
93
|
+
|
|
94
|
+
## CLI commands (run in a terminal, not said to the AI)
|
|
95
|
+
|
|
96
|
+
The `/dflow:*` names above are workflows for your AI assistant. These five are the
|
|
97
|
+
`dflow` CLI itself:
|
|
98
|
+
|
|
99
|
+
| Command | Purpose |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `dflow init` | Initialize Dflow in a project: asks for the track, Git policy, AI commit marking, and which AI tools to configure |
|
|
102
|
+
| `dflow configure-agents` | Idempotent re-projection: add AI tools, refresh the workflow bundle; `--skills` regenerates skills, `--command-adapters` generates native `/` commands |
|
|
103
|
+
| `dflow doctor` | Read-only health check and drift detection |
|
|
104
|
+
| `dflow render` | Turn `dflow/specs/` into browsable static HTML for humans |
|
|
105
|
+
| `dflow check-closeout` | A mechanical check of the closeout commit: its exit code is a gate, for a pre-commit hook or CI |
|
|
106
|
+
|
|
107
|
+
Each command’s full set of flags is in its own `--help` (for example `dflow render --help`).
|
|
108
|
+
When to reach for `init`, `configure-agents` and `render`, and the version-control advice, are
|
|
109
|
+
in [README "Get Started"](../README.en.md#get-started); what to do about what `doctor` reports
|
|
110
|
+
is in [Upgrading an Existing Dflow Project](upgrading.en.md) and
|
|
111
|
+
[When `dflow doctor` is not sure](doctor-uncertainty.en.md); what `check-closeout` checks, and the hook and CI
|
|
112
|
+
templates, are in [Checking the closeout commit](closeout-check.en.md).
|