dflow-sdd-ddd 0.15.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 +156 -0
- package/CONTRIBUTING.md +1 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/bin/dflow.js +75 -0
- package/docs/closeout-check.en.md +204 -0
- package/docs/closeout-check.md +204 -0
- package/docs/commands.en.md +4 -2
- package/docs/commands.md +4 -2
- package/docs/release-versioning-policy.md +1 -0
- package/lib/closeout-check.js +690 -0
- package/lib/doc-shapes.json +198 -0
- package/lib/init.js +17 -2
- package/package.json +2 -2
- package/templates/brownfield/references/finish-feature-flow.md +29 -26
- package/templates/brownfield/references/finish-feature-minimal-host.md +38 -17
- package/templates/brownfield/references/finish-feature-record-correction.md +45 -0
- package/templates/brownfield/references/git-integration.md +62 -28
- package/templates/brownfield/references/modify-existing-flow.md +55 -29
- package/templates/brownfield/references/modify-existing-follow-up.md +2 -1
- package/templates/brownfield/references/modify-existing-post-hoc-hotfix.md +121 -3
- package/templates/brownfield/references/new-feature-flow.md +49 -31
- package/templates/brownfield/references/new-phase-flow.md +2 -2
- package/templates/brownfield/references/pr-review-checklist.md +29 -4
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +13 -5
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +8 -4
- package/templates/brownfield/templates/_index.md +17 -13
- package/templates/common/references/flow-rationale-registry.md +20 -4
- package/templates/greenfield/references/finish-feature-flow.md +29 -26
- package/templates/greenfield/references/finish-feature-minimal-host.md +36 -16
- package/templates/greenfield/references/finish-feature-record-correction.md +45 -0
- package/templates/greenfield/references/git-integration.md +63 -29
- package/templates/greenfield/references/modify-existing-flow.md +42 -27
- package/templates/greenfield/references/modify-existing-follow-up.md +2 -1
- package/templates/greenfield/references/modify-existing-post-hoc-hotfix.md +121 -3
- package/templates/greenfield/references/new-feature-flow.md +49 -31
- package/templates/greenfield/references/new-phase-flow.md +2 -2
- package/templates/greenfield/references/pr-review-checklist.md +28 -4
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +13 -5
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +8 -4
- package/templates/greenfield/templates/_index.md +18 -13
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,162 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.16.0 — 2026-09-28 — 收尾歸檔的機械檢查 `dflow check-closeout`、單 phase 也走 finish-feature、commit 檢查點與 post-hoc 的紀錄、最小 host 更正自己的紀錄
|
|
10
|
+
|
|
11
|
+
**Proposals**:PROPOSAL-094(`new-feature` 8.4 不再是收尾路)、PROPOSAL-103(`modify-existing` 的 commit 檢查點移到更新文件之後)、PROPOSAL-102(post-hoc 補文件記哪一顆 commit、從哪裡切)、PROPOSAL-104(最小 host 在 checkpoint 1 之後更正它自己的紀錄)、PROPOSAL-105(收尾歸檔的機械檢查 `dflow check-closeout`)
|
|
12
|
+
|
|
13
|
+
### 從 `0.15.0` 升上來
|
|
14
|
+
|
|
15
|
+
照 [`docs/upgrading.md`](docs/upgrading.md) 升級:先升 CLI,在專案根目錄跑 `dflow configure-agents`,再跑 `dflow doctor`。這一版另外要注意:
|
|
16
|
+
|
|
17
|
+
1. **收尾做到一半的 feature,先用你現在這一版收完,再升級。**
|
|
18
|
+
2. **已經走舊 8.4 收掉、commit 進 `completed/` 的 feature:不用改。** 它們是凍結的歷史:`completed/` 裡的 feature
|
|
19
|
+
不再接受新的規格或變更(follow-up 在原 feature 的 Follow-up Tracking 加列、收尾時翻那一列,這類追蹤更新例外)。
|
|
20
|
+
這些 feature 會留著兩個過期的欄位:Resume Pointer 停在「new-feature/Step 8」、Phase Specs 表那一列是
|
|
21
|
+
`in-progress`。`/dflow:status` 不讀它們(它只讀 `active/` 底下的 cursor),`dflow render` 會照原樣畫出來;
|
|
22
|
+
之後若有人對這樣的 feature 跑 PR review,它會把「Phase Specs 那一列與 phase-spec 的狀態不一致」標出來——那是
|
|
23
|
+
舊路徑留下的歷史,一樣不用改。
|
|
24
|
+
3. **`_index.md` 範本升成形狀第 3 號**(下面 P-103、P-102 兩項;只改說明文字,段落與欄位都沒變):`dflow doctor` 看到帶著舊標記的
|
|
25
|
+
`_index.md` 會多一條 `info`,說明怎麼處理(見下面 P-103 那一項);`completed/` 底下的 feature 不檢查。
|
|
26
|
+
4. **新子指令 `dflow check-closeout`** 要用才接(pre-commit hook 或 CI),接法見 [`docs/closeout-check.md`](docs/closeout-check.md);
|
|
27
|
+
沒接就沒有任何變化。
|
|
28
|
+
5. 其餘各項都沒有遷移動作。dist 投影過的 `0.15.1`–`0.15.5` 都在這一版裡。
|
|
29
|
+
|
|
30
|
+
**驗證**:發布前 `scripts/check-repo-consistency.sh` 全數通過(含 `npm test` 十支與 `npm pack --dry-run`)。
|
|
31
|
+
|
|
32
|
+
### 各項變更
|
|
33
|
+
|
|
34
|
+
- **收尾歸檔的機械檢查:新子指令 `dflow check-closeout`(PROPOSAL-105,dist issue #5)**:
|
|
35
|
+
`/dflow:finish-feature` 收尾時的一致性檢查全部由 AI 執行;某一步被跳過,不一致的歸檔就會沒人發現地落地——
|
|
36
|
+
目錄已經搬進 `completed/`,裡面的 `_index.md` 卻還寫著 `status: in-progress`(#5 回報的正是這種:`git mv` 之後的
|
|
37
|
+
修改沒有 `git add`)。現在多了一個以結束碼當閘的檢查,由團隊自己接到 pre-commit hook 或 CI:
|
|
38
|
+
- **`dflow check-closeout`**(同 `--staged`,給 pre-commit hook)檢查 index,也就是這顆 commit 要記下的內容;
|
|
39
|
+
**`--range <base>..<head>`**(給 CI)檢查 merge request 的最終狀態。只看這次變動**新歸檔**的 feature 目錄;
|
|
40
|
+
已經在 `completed/` 的不回頭讀,只有一個例外:`--staged` 會再檢查一次 `HEAD` 這顆 commit 自己歸檔的(好讓修正
|
|
41
|
+
收尾 commit 的 amend 也被檢查)。
|
|
42
|
+
- 每個新歸檔的目錄檢查五項:`_index.md` 與每一份 spec(`lightweight-*`、`BUG-*`、`phase-spec-*`,從出貨範本推導)
|
|
43
|
+
都寫著 `status: completed`;`active/` 沒有留下副本;Checkpoint Log 有一列真正的 `closeout`,Result 是
|
|
44
|
+
`committed` 或 `skipped`(範本自帶的佔位列不算,`failed` 擋下);`--staged` 時,歸檔目錄底下沒有漏 stage、
|
|
45
|
+
也沒有還沒 add 的檔。
|
|
46
|
+
- 結束碼 `0` 放行、`1` 擋下,每一條都寫出是哪個目錄、哪裡不對、怎麼修。讀不準的寫法(沒有 frontmatter、較早的
|
|
47
|
+
closeout 列寫法、不是從 `active/` 同名搬來的目錄——包括替已歸檔的 host 改名……)報成 `[uncertain]`,一樣擋下,
|
|
48
|
+
不報成通過,由人確認後放行。
|
|
49
|
+
- **通過不代表收尾是乾淨的**:`/dflow:finish-feature` Step 4 其餘的驗證(對照收尾基準的逐檔比對、收尾 commit
|
|
50
|
+
只帶准許的路徑、回填 hash 的身分、最小 host 剛好兩顆 commit)仍由 AI 做、由 pr-review 把關。
|
|
51
|
+
- **接法範本**在新文件 [`docs/closeout-check.md`](docs/closeout-check.md)(英文版 `closeout-check.en.md`):
|
|
52
|
+
pre-commit hook(POSIX sh;一般 commit 不啟動 Node;沒裝 `dflow` 的電腦印一行後放行),以及 GitLab 與
|
|
53
|
+
GitHub Actions 的 CI job(版號取自專案 workflow bundle 的 manifest)。**Dflow 不在你的專案裡安裝任何 hook 或
|
|
54
|
+
CI 檔**;`dflow doctor` 不變(仍然只回報,結束碼仍是 0)。
|
|
55
|
+
- 兩軌 `git-integration.md` 的「CI / hook automation」一節改寫成出貨的現況。既有專案再跑一次
|
|
56
|
+
`dflow configure-agents` 即取得那一節;**沒有遷移動作**,hook 與 CI 要用才接。
|
|
57
|
+
|
|
58
|
+
- **最小 host 在 checkpoint 1 之後更正它自己的紀錄(PROPOSAL-104,dist issue #8)**:
|
|
59
|
+
checkpoint 1 之後、關帳之前才發現 checkpoint 1 自己的紀錄寫錯了(規格內文、某一列的文字、baseline capture 寫進的
|
|
60
|
+
文件、follow-up 在原 feature 寫的反向連結),關帳會擋下,卻沒有任何地方說怎麼辦。現在:
|
|
61
|
+
- **關帳照舊擋下,並指到新的分支檔 `finish-feature-record-correction.md`**(只在這種情況才打開,平常的關帳不讀它):
|
|
62
|
+
還沒 push、而且 checkpoint 1 就是 `HEAD` → 只 stage 更正、`git diff --cached` 確認、`git commit --amend`,再把記
|
|
63
|
+
checkpoint 1 hash 的格子改成新的 hash;checkpoint 1 之後已經有別的 commit、或已經 push → 停下來交給你決定。
|
|
64
|
+
另成一顆的更正不加 Checkpoint Log 列,並在 PR 說明裡指名。
|
|
65
|
+
- **關帳讀每支檔的變動改用 `git diff HEAD -- {path}`**:原本的 `git diff -- {path}` 看不到已經 `git add` 的變動 ——
|
|
66
|
+
已 stage 的更正在最小 host 的工作樹檢查裡會顯示成沒有變動,可能就這樣搭上關帳那顆 commit。最小 host 的工作樹檢查與
|
|
67
|
+
關帳 commit 要 stage 哪些變動的那一步(每一種 host)都改了。
|
|
68
|
+
- **pr-review 的「剛好兩顆 commit」規則不變**:另成一顆的更正碰到 host 目錄時,那一項照樣標出偏離,並說明它可能是
|
|
69
|
+
這種更正,接不接受由審查的人讀完它的 diff 與 PR 說明之後判斷;只碰 host 目錄以外的紀錄(baseline capture 的文件、
|
|
70
|
+
原 feature 的 `_index.md`)時,那一項看不到它,只有 PR 說明會提到它。
|
|
71
|
+
- **post-hoc 補文件記成了別的修正**(關帳時的 item 6):照同一支分支檔更正 checkpoint 1,並把 Checkpoint Log 的
|
|
72
|
+
`reconciled (...)` 改記正確那個修正的 commit,再照 item 6 驗一次。`modify-existing` 裡「`reconciled (...)` 不得覆寫」
|
|
73
|
+
那句同時寫明例外:只有關帳照 item 6 更正它。
|
|
74
|
+
- **#8 的另一半 —— 改動被 cherry-pick 到第二條長期分支之後 `Commit` 格填哪一顆 —— 不在出貨檔加規則。**
|
|
75
|
+
GitFlow starter 用 merge 把 hotfix 帶回 `main` 與 `develop`;Dflow 的 host 分支(`feature/`、`bugfix/`)又是在
|
|
76
|
+
merge 之前就收尾完成,關帳在 host 自己的分支上驗它自己的那一顆。收尾之後再被 cherry-pick 到別的分支,跟著過去的
|
|
77
|
+
紀錄仍指原本那一顆,那是預期的樣子。`Commit` 格只放一個 hash:寫成 `A / B` 這種多個值,關帳一律當成「從未填入」。
|
|
78
|
+
- 兩軌對稱(baseline capture 只在 brownfield)。既有專案再跑一次 `dflow configure-agents` 即取得(workflow bundle
|
|
79
|
+
多一支分支檔);**沒有遷移動作**。
|
|
80
|
+
|
|
81
|
+
- **post-hoc 補文件:記哪一顆 commit、文件分支從哪裡切,都寫明了(PROPOSAL-102,dist issue #13)**:
|
|
82
|
+
急修(hotfix)合併之後才補 Dflow 文件(post-hoc 模式)時,關帳會檢查記下的那顆 commit 是不是文件分支的祖先。
|
|
83
|
+
但記哪一顆沒有定,文件分支從哪裡切也沒寫,於是 GitFlow 專案照 Dflow 自己的 starter 做 —— 記 `main` 上打 tag
|
|
84
|
+
的那顆 merge commit、從 `develop` 切文件分支 —— 一路順利,**要到關帳才被擋**,訊息只說「不是祖先」。現在:
|
|
85
|
+
- **記修正自己的 commit**(修正分成幾顆的,記碰到宣告路徑的最後一顆):**不記任何 merge commit**,GitFlow 下也不是
|
|
86
|
+
打 tag 的那顆(合回的是 hotfix 分支,那顆不會跟著進 `develop`)。squash 合併記那顆 squash commit;rebase 合併或
|
|
87
|
+
cherry-pick 進 base 的,記它落到 base 上的副本。修正自己的 commit 在 base 上時就記它,即使先進來的是副本(例如先
|
|
88
|
+
cherry-pick、之後才合回)。出處照舊引用 PR/事件/追蹤單,不寫 hash。
|
|
89
|
+
- **建立任何東西之前先檢查切點**:先 fetch base 所在的 remote,確認那顆是 commit、不是 merge commit、它的改動碰到要宣告的實作路徑、而且已經在
|
|
90
|
+
要切的 base 上;不在的話說出兩種成因(記錯了顆,或還沒合回),**不要改從 `main` 切**。切的時候不追蹤 base。
|
|
91
|
+
- **關帳時祖先檢查不過,會說原因並給更正路徑**:記錯了顆 → 只改 Checkpoint Log 那一列,跟著關帳 commit 進去;
|
|
92
|
+
文件分支切得比修正進 base 還早 → 先把 base 合進來;**絕不把 `main` 或 tag 合進文件分支**。
|
|
93
|
+
- 關帳對那顆 commit 的路徑檢查寫明為「**至少碰到一條**宣告的路徑」—— 修正分成幾顆 commit 時,記下的那一顆只帶自己那一份。
|
|
94
|
+
- **pr-review 確認身分:對照出處**。打開出處(PR/事件/追蹤單),確認它指名的是這個修正,以及記下的那一顆(修正自己的
|
|
95
|
+
commit,或 squash commit)—— 有 PR 時,PR 頁面上直接看得到;沒有 PR 的急修,靠出處與 base 的歷史(hotfix 分支名、
|
|
96
|
+
commit 訊息裡的編號)認出修正的 commit。修正以副本落到 base 的(rebase 合併、
|
|
97
|
+
cherry-pick),出處不一定列得到那顆副本:清單寫明 git 證明不了一顆 commit 是另一顆的副本(祖先關係追不到副本;
|
|
98
|
+
`git cherry` 這類 patch 比對分不出只差在空白的兩個改動),`(cherry picked from commit …)` 那一行只是線索;遇到時
|
|
99
|
+
照出處跟作者確認,不拿指令輸出當成已確認。
|
|
100
|
+
- GitFlow starter 的「Hotfix spec requirement」多一句:記 hotfix 自己的 commit,不是打 tag 的那顆(記下的從來不是
|
|
101
|
+
merge commit,而且第 4 步合回的是 hotfix 分支,打 tag 那顆不會跟著進 `develop`)。tutorial 的 brownfield 範例跟著帶上。
|
|
102
|
+
- **`_index.md` 範本升成形狀第 3 號**:Lightweight Changes 與 Checkpoint Log 的兩段說明改成指向 post-hoc 分支檔的規則
|
|
103
|
+
(只改說明文字,段落與欄位都沒變)。`dflow doctor` 對帶著較舊標記的 `_index.md` 照舊只給 `info`:說明改了措辭、
|
|
104
|
+
不影響結構,要不要同步由你和 AI 決定;還沒收尾的最小 host 請先別動它,`completed/` 底下的 feature 不檢查。
|
|
105
|
+
- 兩軌對稱。既有專案再跑一次 `dflow configure-agents` 即取得(starter 那一句在可刷新區內);**沒有遷移動作**。
|
|
106
|
+
|
|
107
|
+
- **`modify-existing`(含 `/dflow:bug-fix`)的 commit 詢問,移到更新文件那一步做完之後(P-103,issue #7)**:
|
|
108
|
+
以前在「實作完成」那一關(brownfield Step 5 → Step 6、greenfield Step 4 → Step 5)就問要不要 commit,接著才更新
|
|
109
|
+
文件(規格標成完成、`rules.md`/`behavior.md` 等)。那一步寫的東西原本要等收尾那一顆 commit 才帶走,但流程在那一步
|
|
110
|
+
沒講;照直覺另外 commit,就多出一顆——在最小 host(只為一個小改動開的 feature 目錄)上,PR review 的「剛好兩顆」會
|
|
111
|
+
把它標出來。現在:
|
|
112
|
+
- 那一關只問要不要進更新文件那一步;**那一步(brownfield Step 6、greenfield Step 5)全部做完、宣告完成時才問要不要
|
|
113
|
+
commit**。這一顆 checkpoint 1 同時帶著程式、規格和那一步更新的文件,T2 就是這一顆加上收尾那一顆。那一步的驗證
|
|
114
|
+
因此在 commit 之前做完,驗不過先修再 commit。
|
|
115
|
+
- 最小 host 的收尾工作分成兩半:規格標成完成、Current BR Snapshot 的最後一次更新在 commit 之前做;commit 之後只補記
|
|
116
|
+
checkpoint 1 的 hash(Checkpoint Log、`Commit` 欄)與 Resume Pointer。
|
|
117
|
+
- 在新位置回答 N(不要 AI 代 commit):程式連同更新好的文件由你自己 commit。之後照舊補帳:最小 host 把 Checkpoint Log
|
|
118
|
+
那一列從 `skipped` 補成 `committed ({hash})`(post-hoc host 維持 `reconciled (...)`),各列 `Commit` 欄填你那顆 commit
|
|
119
|
+
的 hash;有 phase 的 feature 底下的變更,那一列照舊記 `skipped`,`Commit` 欄照舊由之後的 commit 補上(最晚在收尾時)。
|
|
120
|
+
- `/dflow:new-feature`、`/dflow:new-phase` 的檢查點位置不變:它們完成那一步寫的東西,照舊搭這個 feature 的下一顆
|
|
121
|
+
commit——不管下一顆是什麼:下一個 phase 的第一個檢查點、掛在同一個 feature 底下的小改動那一顆,或收尾。
|
|
122
|
+
- 照舊時序做完的 feature(在實作完成那一關就 commit、文件等收尾才帶)用新版收尾,照樣過得了檢查。
|
|
123
|
+
- **「commit 帶不了自己的 hash」寫成一條規則(P-103,issue #7)**:記一顆 commit 的 hash 的那幾格(Checkpoint Log 的
|
|
124
|
+
`committed ({hash})`、Lightweight Changes 的 `Commit` 欄)一律在那顆 commit 之後才寫,搭這個 feature 的下一顆 commit;
|
|
125
|
+
收尾那一列不填 hash,因為這個 feature 之後再也沒有 commit 會帶上它。以前只有收尾那一列講清楚。
|
|
126
|
+
- **`_index.md` 範本升成形狀第 2 號**:Checkpoint Log 那段說明跟著上面兩項改寫(只改說明文字,段落與欄位都沒變)。
|
|
127
|
+
`dflow doctor` 看到帶著第 1 號標記的 `_index.md`:`active/` 底下有 phase 的 feature,多一條 `info`,說明那段 `>`
|
|
128
|
+
說明改了措辭、不影響結構,要不要同步由你和 AI 決定;還沒收尾的最小 host,同一條 `info` 只請你先別動它(收尾後它移到
|
|
129
|
+
`completed/`,就不再檢查);`completed/` 底下的 feature 不檢查。
|
|
130
|
+
|
|
131
|
+
- **單 phase 的 feature 也一律用 `/dflow:finish-feature` 收尾(P-094)**:
|
|
132
|
+
`/dflow:new-feature` 的 8.4 原本是第二條收尾路——只有一個 phase 的 feature 可以不跑 finish-feature,直接在這裡翻
|
|
133
|
+
`_index.md` 的 `status`、把目錄 `git mv` 進 `completed/`。但它不把 Resume Pointer 設回 `none`,也沒有 `git add`、
|
|
134
|
+
沒有 commit:走這條路收掉的 feature,`_index.md` 永遠停在 `Active Workflow: new-feature`/`Current Step: Step 8`。
|
|
135
|
+
現在 8.4 改名 **Phase completion and handoff**,只做兩件事:
|
|
136
|
+
- **完成這個 phase**:phase-spec 的 `status`、`_index.md` Phase Specs 表那一列都翻成 `completed`;Current BR Snapshot
|
|
137
|
+
對照這個 phase-spec 的 Business Rules 對帳(只動這個 phase 自己的列);Resume Pointer 的 Current Progress、
|
|
138
|
+
Next Action 兩行都改寫。這四項正是 finish-feature 接手時會檢查、或直接採信的。
|
|
139
|
+
- **交出去**:還有下一個 phase → `/dflow:new-phase`;沒有了 → `/dflow:finish-feature`,唯一的收尾路。
|
|
140
|
+
new-feature 不再宣告「feature complete」——那要等 finish-feature 宣告收尾完成才說。
|
|
141
|
+
|
|
142
|
+
**你會多遇到的**:單 phase 的 feature 收尾變成 finish-feature 的完整流程——開頭要讀得到 `_conventions.md` 的
|
|
143
|
+
`## Prose Language`;Step 1 的結構檢查與分支檢查(要在這個 feature 的分支上,或它記錄過的 override 分支;
|
|
144
|
+
「已經切到別的分支才想起要歸檔」會被擋下);兩道 Step Gate;收尾 commit checkpoint(AI 問要不要替你 commit——
|
|
145
|
+
可以選的是誰 commit,不是要不要 commit:選 N 就要自己 commit)與 commit 之後的驗證;AI 先說明收尾驗證是怎麼
|
|
146
|
+
算出來的,再給最後的 Integration Summary;宣告收尾完成之後,還會列出其他還在進行的 feature 與分支。
|
|
147
|
+
bounded context 的文件同步會跑兩次:8.3 先依實作同步,finish-feature 的 Step 3 再以 Current BR Snapshot 為準對一次
|
|
148
|
+
(8.4 已經對過帳,第二次通常只是確認一致)。
|
|
149
|
+
多 phase feature 的第一個 phase 也照新的 8.4 標成完成——以前沒有任何 flow 替它做,到 finish-feature 時會被 Step 1 擋下、
|
|
150
|
+
只能手修。
|
|
151
|
+
|
|
152
|
+
- **merge 前的關統一成同一個「收尾完成」(P-094)**:`git-integration.md` 的 `feature/`、`bugfix/` merge 前清單,
|
|
153
|
+
與四支 `Git-principles-*.md` starter 的 merge 前清單,原本只看「目錄可以 `git mv`/已經搬了」或「finish-feature 跑過
|
|
154
|
+
(或 8.4 手動歸檔)」——都擋不住收尾做到一半的 feature:`status` 在 finish-feature 的 Step 2 就翻了,`git mv` 在收尾
|
|
155
|
+
commit 之前。現在一律是 **Closeout complete**:finish-feature 已宣告收尾完成,也就是收尾 commit 做了、commit 之後的
|
|
156
|
+
驗證過了;帶 `follow-up-of` 的,Step 6 的追蹤 commit 也做了、驗證過。
|
|
157
|
+
starter 那一格在 `git-principles-canonical` 區內:採用了 marker 的專案重跑 `dflow configure-agents` 就會刷新——前提是
|
|
158
|
+
`_conventions.md` 的 `## Git Policy` 讀得到所選的 policy,讀不到時它不刷新這支檔、並說明原因;還沒採用 marker 的,
|
|
159
|
+
`dflow doctor` 照舊會提示。
|
|
160
|
+
|
|
161
|
+
- **README 的流程圖**:進 `/dflow:finish-feature` 那條箭頭的標籤改成「沒有下一個 phase」(英文版 `no further phase`),
|
|
162
|
+
前一格改成「這個 phase 完成」(`Phase done`);中文版進 `/dflow:new-phase` 那條箭頭跟著改成「還有下一個 phase」。
|
|
163
|
+
「feature 完成」要等 finish-feature 收尾完成才說。
|
|
164
|
+
|
|
9
165
|
## 0.15.0 — 2026-09-27 — 分級改為順序判定、analysis.md 與 render 畫圖、doctor 看不準就直說
|
|
10
166
|
|
|
11
167
|
**Proposals**:PROPOSAL-077(A1 — spec 人讀可讀性:render 長欄位排版)、PROPOSAL-078 phase 1(formatting convention 投遞與偵測)、PROPOSAL-079(render index completed/ 年度分頁)、PROPOSAL-081(README 瘦身重組+防過度設計特點露出)、PROPOSAL-082(Tier 邊界語意改為順序 cascade)、PROPOSAL-083(standalone minimal host 生命週期)、PROPOSAL-084(`doctor` 誠實揭露不確定性)、PROPOSAL-085(flow reference 執行期體積)、PROPOSAL-086(受限標頭比讀者窄)、PROPOSAL-087(finish-feature 罕見路徑抽離)、PROPOSAL-090(`Git-principles-*.md` 的 canonical 區改為可刷新)、PROPOSAL-091(`doctor` 的兩條 false-clean 路徑)、PROPOSAL-093(closeout 尾巴的 cursor 矛盾)、PROPOSAL-095(BR Snapshot 範例列移出資料面)、PROPOSAL-096(closeout baseline 的鑰匙改為可推導)、PROPOSAL-099(系統級知識的落點)、PROPOSAL-100(render 把生命週期與流程畫成圖)、PROPOSAL-101(render 首頁改成分組目錄)、PROPOSAL-092(規格文件的形狀版號)
|
package/CONTRIBUTING.md
CHANGED
|
@@ -92,6 +92,7 @@ Common shared flows include:
|
|
|
92
92
|
- `finish-feature-follow-up.md`
|
|
93
93
|
- `finish-feature-minimal-host.md`
|
|
94
94
|
- `finish-feature-post-hoc-hotfix.md`
|
|
95
|
+
- `finish-feature-record-correction.md`
|
|
95
96
|
- `modify-existing-follow-up.md`
|
|
96
97
|
- `modify-existing-post-hoc-hotfix.md`
|
|
97
98
|
- `drift-verification.md`
|
package/README.en.md
CHANGED
|
@@ -132,7 +132,7 @@ confirm direction with you — the flow is drawn above in
|
|
|
132
132
|
|
|
133
133
|
To name a flow yourself (when you already know this is a new feature, say), or to see
|
|
134
134
|
everything Dflow covers, read the [Command Reference](docs/commands.en.md): the 11
|
|
135
|
-
workflows, how to type them in each AI tool, and the
|
|
135
|
+
workflows, how to type them in each AI tool, and the five `dflow` CLI commands.
|
|
136
136
|
|
|
137
137
|
For the first adoption pass, use a branch or disposable sample project so your
|
|
138
138
|
team can inspect the generated `dflow/specs/` workspace before bringing the
|
|
@@ -287,6 +287,8 @@ Once `/dflow:finish-feature` moves a feature directory into `completed/`, **no d
|
|
|
287
287
|
|
|
288
288
|
Why: "completed = frozen history" is a core Dflow guarantee. Accepting post-completion edits would erase the feature-lifecycle endpoint and make `_index.md`'s BR Snapshot unreliable. `/dflow:modify-existing` detects when the target is a completed feature and prompts the developer with three choices: A — follow-up; B — independent concern (**T1** goes to `/dflow:new-feature`; **T2 / T3** stay in `/dflow:modify-existing` and open a standalone minimal host); C (refused, re-directed to A).
|
|
289
289
|
|
|
290
|
+
**Advanced: wire the closeout check into a hook or CI.** The AI runs the closeout's consistency checks; when a step is skipped, an inconsistent archive (the directory in `completed/` while the `_index.md` inside it still says `in-progress`) can land without anyone noticing. To have git stop it mechanically on the closeout commit or the merge request, see [Checking the closeout commit](docs/closeout-check.en.md): `dflow check-closeout` plus pre-commit hook and GitLab / GitHub CI templates, which your team wires in itself.
|
|
291
|
+
|
|
290
292
|
## Files Created by Init
|
|
291
293
|
|
|
292
294
|
A typical initialized project receives a `dflow/` workspace:
|
|
@@ -423,15 +425,16 @@ Publish Checklist](docs/npm-publish-checklist.md).
|
|
|
423
425
|
## Status
|
|
424
426
|
|
|
425
427
|
Dflow is currently published as `dflow-sdd-ddd` on npm. The latest published
|
|
426
|
-
npm package is `0.
|
|
428
|
+
npm package is `0.16.0`, providing:
|
|
427
429
|
|
|
428
430
|
- Project scaffolding and upgrades: `dflow init` (initialization), `dflow configure-agents` (idempotent upgrade re-projection), `dflow doctor` (read-only health check with drift detection), `dflow render` (specs → human-readable HTML)
|
|
431
|
+
- A mechanical closeout check: `dflow check-closeout` (its exit code is the gate; your team wires it into a pre-commit hook or CI — see [`docs/closeout-check.en.md`](docs/closeout-check.en.md))
|
|
429
432
|
- Workflow documentation (the 11 `/dflow:*` flows) plus the project-vendored workflow bundle and multi-AI-tool setup (canonical guide, thin per-tool shims, project-level skill installed by default)
|
|
430
433
|
- Public evaluation material inside the package: evaluator guide and per-tool walkthroughs for Claude Code / Codex CLI / GitHub Copilot (all under `docs/`)
|
|
431
434
|
- Greenfield / Brownfield scenario tutorials and worked spec examples: **in the source repository, not in the npm package** (the tarball does not include `tutorial/`) — see [`tutorial/`](https://github.com/weilung/dflow-sdd-ddd/tree/main/tutorial)
|
|
432
|
-
- A verification-only CI workflow (it does not execute publish)
|
|
435
|
+
- A verification-only CI workflow (it does not execute publish): **in the source repository, not in the npm package**
|
|
433
436
|
|
|
434
|
-
The GitHub source may include post-`0.
|
|
437
|
+
The GitHub source may include post-`0.16.0` repository changes before the
|
|
435
438
|
next npm release is published. See [CHANGELOG.md](CHANGELOG.md) for full
|
|
436
439
|
release history.
|
|
437
440
|
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ AI 會判斷該走哪一條 workflow 並主動啟動,然後在每個決策點
|
|
|
102
102
|
|
|
103
103
|
想自己指定某條 flow(例如你已經確定這是一個新 feature),或想知道 Dflow 總共涵蓋哪些
|
|
104
104
|
情境,見[指令參考](docs/commands.md):11 條 workflow、各 AI 工具的輸入方式、以及 `dflow`
|
|
105
|
-
CLI
|
|
105
|
+
CLI 的五個指令。
|
|
106
106
|
|
|
107
107
|
第一次採用建議用 branch 或一次性試用專案,讓團隊先檢視產生的 `dflow/specs/` 工作區,再把流程引入正式程式碼。
|
|
108
108
|
|
|
@@ -217,6 +217,8 @@ Dflow 用 `_index.md` 解決這三點:Current BR Snapshot 每完成一個 phas
|
|
|
217
217
|
|
|
218
218
|
理由:「completed = 凍結歷史」是 Dflow 的核心保證;若接受 post-completion 修改,feature lifecycle 就失去明確終點、`_index.md` BR Snapshot 也無法可信。`/dflow:modify-existing` 偵測到目標是 completed feature 時會主動詢問 user 三個選項:A 走 follow-up、B 當獨立新需求(**T1** 走 `/dflow:new-feature`;**T2/T3** 留在 `/dflow:modify-existing`,開一個 standalone minimal host)、C(被拒絕,重新引導至 A)。
|
|
219
219
|
|
|
220
|
+
**進階:把收尾檢查接到 hook/CI。** 收尾的一致性檢查由 AI 執行;某一步被跳過時,不一致的歸檔(目錄已在 `completed/`,裡面的 `_index.md` 卻還是 `in-progress`)會沒人發現地落地。想讓 git 在收尾 commit 或 merge request 上機械地擋下它,見[收尾 commit 的機械檢查](docs/closeout-check.md):`dflow check-closeout` 加上 pre-commit hook 與 GitLab/GitHub CI 的接法範本,由你的團隊自己接。
|
|
221
|
+
|
|
220
222
|
## Init 產生的檔案
|
|
221
223
|
|
|
222
224
|
典型初始化專案會建立 `dflow/` workspace:
|
|
@@ -301,15 +303,16 @@ issue 與 pull request 指引見 [CONTRIBUTING.md](CONTRIBUTING.md)。Pull reque
|
|
|
301
303
|
|
|
302
304
|
## 狀態
|
|
303
305
|
|
|
304
|
-
Dflow 目前以 `dflow-sdd-ddd` 名稱發佈於 npm。最新發佈版本為 `0.
|
|
306
|
+
Dflow 目前以 `dflow-sdd-ddd` 名稱發佈於 npm。最新發佈版本為 `0.16.0`,提供:
|
|
305
307
|
|
|
306
308
|
- 專案 scaffolding 與升級:`dflow init`(初始化)、`dflow configure-agents`(idempotent 升級重投影)、`dflow doctor`(唯讀健康檢查與漂移偵測)、`dflow render`(specs → 人類可讀 HTML)
|
|
309
|
+
- 收尾歸檔的機械檢查:`dflow check-closeout`(以結束碼當閘,由團隊自己接到 pre-commit hook 或 CI;接法見 [`docs/closeout-check.md`](docs/closeout-check.md))
|
|
307
310
|
- Workflow 文件(11 個 `/dflow:*` 流程)+隨專案 vendored 的 workflow bundle+多 AI 工具設定(canonical 指南、各工具薄 shim、預設安裝的 project-level skill)
|
|
308
311
|
- 套件內的公開評估材料:evaluator 指南、Claude Code / Codex CLI / GitHub Copilot per-tool walkthrough(都在 `docs/`)
|
|
309
312
|
- Greenfield / Brownfield 劇情教學與規格範例:**在 source repository,不在 npm 套件裡**(tarball 不含 `tutorial/`)——見 [`tutorial/`](https://github.com/weilung/dflow-sdd-ddd/tree/main/tutorial)
|
|
310
|
-
- 僅驗證的 CI workflow(不執行 publish
|
|
313
|
+
- 僅驗證的 CI workflow(不執行 publish):**在 source repository,不在 npm 套件裡**
|
|
311
314
|
|
|
312
|
-
GitHub 上的 source 可能包含 `0.
|
|
315
|
+
GitHub 上的 source 可能包含 `0.16.0` 之後尚未發佈的 repo 變更。完整 release history 見 [CHANGELOG.md](CHANGELOG.md)。
|
|
313
316
|
|
|
314
317
|
## 授權
|
|
315
318
|
|
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
|
`);
|
|
@@ -130,6 +131,62 @@ Doctor never modifies files.
|
|
|
130
131
|
`);
|
|
131
132
|
}
|
|
132
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
|
+
|
|
133
190
|
async function main() {
|
|
134
191
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
135
192
|
printHelp();
|
|
@@ -217,6 +274,24 @@ async function main() {
|
|
|
217
274
|
});
|
|
218
275
|
}
|
|
219
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
|
+
|
|
220
295
|
process.stderr.write(`Unsupported subcommand: ${args[0]}\n\n`);
|
|
221
296
|
printHelp();
|
|
222
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.
|