dflow-sdd-ddd 0.7.0 → 0.8.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 +26 -0
- package/docs/evaluating-dflow.en.md +14 -5
- package/docs/evaluating-dflow.md +14 -5
- package/docs/using-with-claude-code.en.md +17 -9
- package/docs/using-with-claude-code.md +15 -8
- package/lib/init.js +263 -52
- package/package.json +1 -1
- package/templates/brownfield/references/dflow-feedback-flow.md +179 -0
- package/templates/brownfield/references/drift-verification.md +183 -0
- package/templates/brownfield/references/finish-feature-flow.md +259 -0
- package/templates/brownfield/references/git-integration.md +312 -0
- package/templates/brownfield/references/init-project-flow.md +413 -0
- package/templates/brownfield/references/modify-existing-flow.md +444 -0
- package/templates/brownfield/references/new-feature-flow.md +367 -0
- package/templates/brownfield/references/new-phase-flow.md +259 -0
- package/templates/brownfield/references/pr-review-checklist.md +179 -0
- package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +31 -4
- package/templates/brownfield/scaffolding/CLAUDE-md-snippet.md +12 -8
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +1 -1
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +1 -1
- package/templates/brownfield/scaffolding/_conventions.md +1 -1
- package/templates/brownfield/scaffolding/_overview.md +3 -3
- package/templates/brownfield/templates/context-map.md +1 -1
- package/templates/brownfield/templates/glossary.md +1 -1
- package/templates/brownfield/templates/models.md +1 -1
- package/templates/brownfield/templates/rules.md +1 -1
- package/templates/brownfield/templates/tech-debt.md +1 -1
- package/templates/common/skill/SKILL.md +35 -0
- package/templates/greenfield/references/ddd-modeling-guide.md +351 -0
- package/templates/greenfield/references/dflow-feedback-flow.md +179 -0
- package/templates/greenfield/references/drift-verification.md +195 -0
- package/templates/greenfield/references/finish-feature-flow.md +280 -0
- package/templates/greenfield/references/git-integration.md +285 -0
- package/templates/greenfield/references/init-project-flow.md +447 -0
- package/templates/greenfield/references/modify-existing-flow.md +362 -0
- package/templates/greenfield/references/new-feature-flow.md +397 -0
- package/templates/greenfield/references/new-phase-flow.md +273 -0
- package/templates/greenfield/references/pr-review-checklist.md +130 -0
- package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +31 -4
- package/templates/greenfield/scaffolding/CLAUDE-md-snippet.md +15 -13
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +1 -1
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +1 -1
- package/templates/greenfield/scaffolding/_conventions.md +1 -1
- package/templates/greenfield/scaffolding/_overview.md +5 -3
- package/templates/greenfield/scaffolding/architecture-decisions-README.md +1 -1
- package/templates/greenfield/templates/context-map.md +1 -1
- package/templates/greenfield/templates/events.md +1 -1
- package/templates/greenfield/templates/glossary.md +1 -1
- package/templates/greenfield/templates/models.md +1 -1
- package/templates/greenfield/templates/rules.md +1 -1
- package/templates/greenfield/templates/tech-debt.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.8.0 — 2026-05-24 — DDD set-based invariant 引導擴充、雙軌 skill 殼合一、user-projected bundle 內容 polish
|
|
10
|
+
|
|
11
|
+
**Proposals**:PROPOSAL-040(CLI papercuts + 文件補洞)、PROPOSAL-041(C0+C1:configure-agents idempotent bundle re-projection + 雙軌 skill 殼合一)、PROPOSAL-042(ddd-modeling-guide 補 set-based invariants 與 Phase-1 event dispatch 段)、PROPOSAL-044(user-projected bundle 內 maintainer-source path 清除)、PROPOSAL-045(user-projected bundle 內 maintainer-only provenance refs 清除)
|
|
12
|
+
|
|
13
|
+
**新功能 / 新引導內容**:
|
|
14
|
+
|
|
15
|
+
- **ddd-modeling-guide 補「Set-Based / Uniqueness Invariants」段**(PROPOSAL-042):明確說明 set-based invariants(「同 X 只能一筆 active」「email 跨 User 唯一」這類)**無論選哪種 aggregate 邊界**,in-memory check 在並發下都不夠保護,需 DB unique / partial index 或 optimistic concurrency token 配合。涵蓋兩種常見 aggregate 切法(拆獨立 + folded 為 child entity)的對策;指出 child entity 寫入需 bump root version;建議將並發衝突 translate 為業務語意 conflict(HTTP 409)。同 update 補 Common Mistakes #6 與 Phase-1 event dispatch lifecycle 段落。
|
|
16
|
+
|
|
17
|
+
**行為改善**:
|
|
18
|
+
|
|
19
|
+
- **`configure-agents` 自動補回 bundle**(PROPOSAL-041 C0):對 pre-039 既有專案再次跑 `configure-agents` 時,自動 re-project 缺失的 `dflow/specs/shared/dflow-workflows/` bundle(idempotent;第二次跑為 no-op)。順手關閉 PROPOSAL-039 follow-up (i)。
|
|
20
|
+
- **雙軌 skill 殼合一**(PROPOSAL-041 C1):兩份 `sdd-ddd-{green,brown}field-skill/SKILL.md` 統一為 `templates/common/skill/SKILL.md` thin shell;`lib/init.js` 從該檔讀。對 npm 使用者只是裝起來行為一致,無破壞性變更;對 maintainer 是 source-of-truth 收斂。
|
|
21
|
+
- **CLI papercuts 批次修**(PROPOSAL-040 group A):`init` / `configure-agents` 數個 CLI 摩擦點修補(issues #2/#11/#12)。
|
|
22
|
+
- **`init` 後續 step 文件補洞**(PROPOSAL-040 group B):slug 命名加 CI 相容性 caveat、`behavior.md` skeleton 註解明確(避免 user 以為 finish-feature 前就該填)、`configure-agents` 自動偵測既有 agent 配置(#13/#7)、`aggregate-design.md` 在 feature 目錄內的存放位置明文釘住。
|
|
23
|
+
|
|
24
|
+
**文件 / user-facing 內容 polish**:
|
|
25
|
+
|
|
26
|
+
- **user-projected bundle 內 `sdd-ddd-*-skill/` maintainer-source path 清除**(PROPOSAL-044):原本 `npm install` 後 user project 內的 flow / template 文件含 `sdd-ddd-greenfield-skill/scaffolding/...` 這類在 user 端不存在的路徑引用——user 讀到會誤判「我裝錯了?」。本 release 把該類引用全部清為 user 端可解析路徑或自足語句。涵蓋 12 個 source + 12 個 mirror。
|
|
27
|
+
- **user-projected bundle 內 maintainer-only provenance refs 清除**(PROPOSAL-045):原本 user 端會看到的 `<!-- ... PROPOSAL-013 ... -->` HTML 註解、`init-project-flow.md` 末段 `archive/reviews/...` 引用、references body 內 bare `PROPOSAL-NNN` 引用、brownfield `CLAUDE-md-snippet.md` 內 `(established in PROPOSAL-007c)` 標註等——這些是 maintainer-only 歷史佔位、user 看到只會困惑。本 release 全部清除或改寫為自足語句;HTML 註解保留為 `<!-- Seeded by Dflow. -->`(語意精準:user 取到檔後可自行客製化、dflow 並不持續維護該副本)。涵蓋 66 個 unique files。
|
|
28
|
+
|
|
29
|
+
**維護者工具**(不影響套件使用者):
|
|
30
|
+
|
|
31
|
+
- **`scripts/check-repo-consistency.sh` 新增 045 guard**:scoped git grep 對 `sdd-ddd-*-skill/{templates,scaffolding,references}/*.md` + `templates/{green,brown}field/**/*.md` 偵測 `(archive/|planning/|reviews/|proposals/|PROPOSAL-[0-9]{3})` 任一命中即 fail——防未來再引入 maintainer-only provenance refs。Pre-commit hook 不加。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
9
35
|
## 0.7.0 — 2026-05-22 — Opt-in Claude skill adapter、自動清理舊 command adapter、發布前 lifecycle 機檢
|
|
10
36
|
|
|
11
37
|
**Proposals**:PROPOSAL-037(generated adapter commit 政策與升級流程)、PROPOSAL-038(選配 skill adapter)、PROPOSAL-035(lifecycle drift forcing-functions)
|
|
@@ -35,10 +35,16 @@ in your project's `dflow/specs/` directory and AI instruction files.
|
|
|
35
35
|
map, architecture/tech-debt, features active/completed). See
|
|
36
36
|
[`README.md` "Files Created by Init"](../README.en.md#files-created-by-init)
|
|
37
37
|
for the full tree.
|
|
38
|
-
- A canonical project guide at `dflow/specs/shared/AI-AGENT-GUIDE.md
|
|
38
|
+
- A canonical project guide at `dflow/specs/shared/AI-AGENT-GUIDE.md` —
|
|
39
|
+
the command registry, routing rules, and project context.
|
|
40
|
+
- A vendored workflow bundle at `dflow/specs/shared/dflow-workflows/` —
|
|
41
|
+
the executable step definitions for each `/dflow:*` workflow (step gates,
|
|
42
|
+
completion checklists, templates). This bundle is projected from the npm
|
|
43
|
+
package at init time so workflows are reachable from any clone without
|
|
44
|
+
needing the Dflow source or package installed locally.
|
|
39
45
|
- Mergeable AI agent instruction files for the tools you select (e.g.,
|
|
40
46
|
`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`). Each is a
|
|
41
|
-
thin pointer to the canonical guide.
|
|
47
|
+
thin pointer to the canonical guide and workflow bundle.
|
|
42
48
|
|
|
43
49
|
`init` does **not**:
|
|
44
50
|
|
|
@@ -60,7 +66,9 @@ or more tools and Dflow writes the corresponding shim:
|
|
|
60
66
|
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
61
67
|
|
|
62
68
|
Each shim points back to the canonical
|
|
63
|
-
`dflow/specs/shared/AI-AGENT-GUIDE.md`.
|
|
69
|
+
`dflow/specs/shared/AI-AGENT-GUIDE.md`. The guide acts as the command registry
|
|
70
|
+
and router; executable workflow steps live in the vendored bundle at
|
|
71
|
+
`dflow/specs/shared/dflow-workflows/`. Practical implications:
|
|
64
72
|
|
|
65
73
|
- Multiple tools can be active in the same project without diverging
|
|
66
74
|
workflow rules.
|
|
@@ -68,8 +76,9 @@ Each shim points back to the canonical
|
|
|
68
76
|
`dflow configure-agents` to add another shim, or
|
|
69
77
|
`dflow configure-agents --command-adapters` to opt in to tool-native command
|
|
70
78
|
entries.
|
|
71
|
-
- The
|
|
72
|
-
behavior
|
|
79
|
+
- The guide and workflow bundle together are the authoritative sources for
|
|
80
|
+
Dflow workflow behavior; both are plain Markdown committed in your repo and
|
|
81
|
+
accessible to any clone.
|
|
73
82
|
|
|
74
83
|
`/dflow:*` is the canonical shared vocabulary, but each tool's `/` parser
|
|
75
84
|
behaves differently: Claude Code command adapters use `/dflow:<id>`, the
|
package/docs/evaluating-dflow.md
CHANGED
|
@@ -25,8 +25,14 @@ Dflow 是 Markdown-based 的 workflow 材料加上一個 scaffolding CLI。它
|
|
|
25
25
|
|
|
26
26
|
- `dflow/specs/` workspace(概覽、慣例、領域詞彙表、context map、架構 / 技術債、功能 active/completed)。完整目錄樹見
|
|
27
27
|
[`README.md` "Init 產生的檔案"](../README.md#init-產生的檔案)。
|
|
28
|
-
- 位於 `dflow/specs/shared/AI-AGENT-GUIDE.md` 的 canonical
|
|
29
|
-
|
|
28
|
+
- 位於 `dflow/specs/shared/AI-AGENT-GUIDE.md` 的 canonical 專案指南 ——
|
|
29
|
+
命令登錄表、路由規則與專案上下文。
|
|
30
|
+
- 位於 `dflow/specs/shared/dflow-workflows/` 的 vendored workflow bundle ——
|
|
31
|
+
各 `/dflow:*` workflow 的可執行步驟定義(step gates、completion checklists、模板)。
|
|
32
|
+
這個 bundle 在 init 時從 npm 套件投影進專案,因此 workflow 步驟是 self-contained
|
|
33
|
+
且可達的,任何 clone 都不需要 Dflow source 或 package 在本機安裝。
|
|
34
|
+
- 你所選工具的可合併 AI 指示檔(例如 `CLAUDE.md`、`AGENTS.md`、`.github/copilot-instructions.md`)。
|
|
35
|
+
每個都是指向 canonical 指南與 workflow bundle 的薄 shim。
|
|
30
36
|
|
|
31
37
|
`init` **不會**:
|
|
32
38
|
|
|
@@ -45,11 +51,14 @@ Dflow 支援多種 AI 程式設計助理。跑完 `init` 後,你選取一個
|
|
|
45
51
|
| Claude Code | `CLAUDE.md` |
|
|
46
52
|
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
47
53
|
|
|
48
|
-
每個 shim 都指向 canonical 的 `dflow/specs/shared/AI-AGENT-GUIDE.md
|
|
54
|
+
每個 shim 都指向 canonical 的 `dflow/specs/shared/AI-AGENT-GUIDE.md`(命令登錄表)
|
|
55
|
+
與 `dflow/specs/shared/dflow-workflows/`(可執行步驟定義)。實際意義:
|
|
49
56
|
|
|
50
57
|
- 同一個專案可以同時啟用多個工具,而不會有 workflow 規則分歧。
|
|
51
|
-
- 之後切換或新增工具不需要重跑 `init`;執行 `dflow configure-agents` 即可新增 shim
|
|
52
|
-
|
|
58
|
+
- 之後切換或新增工具不需要重跑 `init`;執行 `dflow configure-agents` 即可新增 shim,
|
|
59
|
+
或用 `dflow configure-agents --command-adapters` opt in 工具原生命令入口。
|
|
60
|
+
- 指南與 workflow bundle 共同成為 Dflow workflow 行為的權威來源;兩者都是純 Markdown
|
|
61
|
+
已 commit 進 repo,任何 clone 均可直接讀取。
|
|
53
62
|
|
|
54
63
|
`/dflow:*` 是 canonical 共同詞彙,但各工具的 `/` parser 行為不同:Claude Code
|
|
55
64
|
command adapters 使用 `/dflow:<id>`,GitHub Copilot prompt 選單使用
|
|
@@ -68,11 +68,15 @@ Two things happen when Claude Code starts in this project:
|
|
|
68
68
|
Code's Markdown import syntax to inline the canonical Dflow guide. So
|
|
69
69
|
Claude Code effectively reads both files as one set of instructions.
|
|
70
70
|
|
|
71
|
-
The canonical guide (`dflow/specs/shared/AI-AGENT-GUIDE.md`) is
|
|
72
|
-
|
|
71
|
+
The canonical guide (`dflow/specs/shared/AI-AGENT-GUIDE.md`) is the
|
|
72
|
+
**command registry and router**: project context (track, tech stack, prose
|
|
73
73
|
language), the `/dflow:*` workflow table, source-of-truth file paths, and
|
|
74
|
-
core SDD/DDD rules. The
|
|
75
|
-
|
|
74
|
+
core SDD/DDD rules. The executable workflow steps (Step 1→N, step gates,
|
|
75
|
+
completion checklists) live in the **vendored workflow bundle** that `init`
|
|
76
|
+
projects into `dflow/specs/shared/dflow-workflows/`. Both are plain Markdown
|
|
77
|
+
checked into your repo, so they travel with the project when cloned. The
|
|
78
|
+
`CLAUDE.md` shim stays small precisely so the canonical guide can evolve
|
|
79
|
+
without Claude-Code-specific edits.
|
|
76
80
|
|
|
77
81
|
If a `CLAUDE.md` already existed in the project, `init` does not overwrite
|
|
78
82
|
it. Instead it writes a merge snippet under `dflow/specs/shared/` that you
|
|
@@ -112,9 +116,11 @@ dflow/specs/features/active/. Before I do, I need a short answer on:
|
|
|
112
116
|
The workflow then walks you through spec drafting, behavior examples,
|
|
113
117
|
implementation planning, and finish-feature drift checks. The exact
|
|
114
118
|
sequence depends on which workflow you entered (`/dflow:new-feature`,
|
|
115
|
-
`/dflow:modify-existing`, `/dflow:bug-fix`, etc.).
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
`/dflow:modify-existing`, `/dflow:bug-fix`, etc.). The executable step
|
|
120
|
+
definitions (Step 1→N, step gates, completion checklists) are in the
|
|
121
|
+
vendored workflow bundle at `dflow/specs/shared/dflow-workflows/`; `init`
|
|
122
|
+
projects this bundle into every initialized project so the workflow steps
|
|
123
|
+
are self-contained and reachable without any external source dependency.
|
|
118
124
|
|
|
119
125
|
Available workflow entry points:
|
|
120
126
|
|
|
@@ -215,8 +221,10 @@ After you select Claude Code, Dflow generates a thin skill:
|
|
|
215
221
|
|
|
216
222
|
- `.claude/skills/dflow/SKILL.md`
|
|
217
223
|
|
|
218
|
-
This skill does not copy workflow steps; its body
|
|
219
|
-
`dflow/specs/shared/AI-AGENT-GUIDE.md
|
|
224
|
+
This skill does not copy workflow steps; its body points to the canonical
|
|
225
|
+
`dflow/specs/shared/AI-AGENT-GUIDE.md` (command registry and routing rules) and
|
|
226
|
+
`dflow/specs/shared/dflow-workflows/` (vendored bundle with executable step
|
|
227
|
+
definitions).
|
|
220
228
|
Its behavior:
|
|
221
229
|
|
|
222
230
|
- **Auto-triggers on** feature / bug-fix workflows, product/domain behavior
|
|
@@ -62,10 +62,14 @@ Claude Code 在這個專案中啟動時,會發生兩件事:
|
|
|
62
62
|
Markdown import 語法,將 canonical Dflow 指南 inline 嵌入。因此 Claude Code
|
|
63
63
|
等效於把兩個檔案當成一組指示來讀取。
|
|
64
64
|
|
|
65
|
-
canonical 指南(`dflow/specs/shared/AI-AGENT-GUIDE.md
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
canonical 指南(`dflow/specs/shared/AI-AGENT-GUIDE.md`)是**命令登錄表與路由器**:
|
|
66
|
+
專案上下文(track、技術棧、文章語言)、`/dflow:*` workflow 表、source-of-truth
|
|
67
|
+
檔案路徑,以及核心 SDD/DDD 規則。可執行的 workflow 步驟(Step 1→N、step gates、
|
|
68
|
+
completion checklists)則放在 `init` 投影進專案的 **vendored workflow bundle**
|
|
69
|
+
(`dflow/specs/shared/dflow-workflows/`)。兩者都是純 Markdown,隨 repo
|
|
70
|
+
一起 commit,任何 clone 都能直接讀取,不需要 Dflow source 或 package 在本機安裝。
|
|
71
|
+
`CLAUDE.md` shim 刻意保持精簡,這樣 canonical 指南就能在不需要 Claude Code
|
|
72
|
+
專屬修改的情況下持續演進。
|
|
69
73
|
|
|
70
74
|
如果專案中已有 `CLAUDE.md`,`init` 不會覆蓋它。它改為在
|
|
71
75
|
`dflow/specs/shared/` 下寫入 merge snippet,讓你手動貼入現有的 `CLAUDE.md`。
|
|
@@ -103,8 +107,10 @@ dflow/specs/features/active/. Before I do, I need a short answer on:
|
|
|
103
107
|
接著這個 workflow 會引導你完成 spec 起草、行為範例、實作計畫,以及
|
|
104
108
|
finish-feature 漂移(drift)檢查。確切的流程取決於你進入的是哪個 workflow
|
|
105
109
|
(`/dflow:new-feature`、`/dflow:modify-existing`、`/dflow:bug-fix` 等)。
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
可執行的步驟定義(Step 1→N、step gates、completion checklists)存放在
|
|
111
|
+
`dflow/specs/shared/dflow-workflows/` 的 vendored bundle 中;`init` 會把這個
|
|
112
|
+
bundle 投影進每個初始化的專案,因此 workflow 步驟是 self-contained 且可達的,
|
|
113
|
+
不需要任何外部 source dependency。
|
|
108
114
|
|
|
109
115
|
可用的 workflow 入口:
|
|
110
116
|
|
|
@@ -190,8 +196,9 @@ dflow configure-agents --skills
|
|
|
190
196
|
|
|
191
197
|
- `.claude/skills/dflow/SKILL.md`
|
|
192
198
|
|
|
193
|
-
這份 skill 不複製 workflow 步驟,body
|
|
194
|
-
`dflow/specs/shared/AI-AGENT-GUIDE.md
|
|
199
|
+
這份 skill 不複製 workflow 步驟,body 指向 canonical
|
|
200
|
+
`dflow/specs/shared/AI-AGENT-GUIDE.md`(命令登錄表與路由規則)以及
|
|
201
|
+
`dflow/specs/shared/dflow-workflows/`(含可執行步驟定義的 vendored bundle)。
|
|
195
202
|
它的行為:
|
|
196
203
|
|
|
197
204
|
- **自動觸發於** feature / bug-fix workflow、product/domain behavior 變更、新需求、
|
package/lib/init.js
CHANGED
|
@@ -12,6 +12,10 @@ const COMMAND_REGISTRY_START = '<!-- dflow-command-registry:start -->';
|
|
|
12
12
|
const COMMAND_REGISTRY_END = '<!-- dflow-command-registry:end -->';
|
|
13
13
|
const COMMAND_ADAPTER_GENERATED_MARKER = '<!-- dflow-generated: command-adapter -->';
|
|
14
14
|
const SKILL_ADAPTER_GENERATED_MARKER = '<!-- dflow-generated: skill-adapter -->';
|
|
15
|
+
const WORKFLOW_BUNDLE_GENERATED_MARKER = '<!-- dflow-generated: workflow-bundle -->';
|
|
16
|
+
const WORKFLOW_BUNDLE_DEST = 'dflow/specs/shared/dflow-workflows';
|
|
17
|
+
const WORKFLOW_BUNDLE_MANIFEST_PATH = `${WORKFLOW_BUNDLE_DEST}/.dflow-bundle-manifest.json`;
|
|
18
|
+
const COMMON_SKILL_SOURCE_REL = 'common/skill/SKILL.md';
|
|
15
19
|
const EXPECTED_COMMAND_IDS = [
|
|
16
20
|
'new-feature',
|
|
17
21
|
'modify-existing',
|
|
@@ -203,8 +207,8 @@ async function runInit(options = {}) {
|
|
|
203
207
|
|
|
204
208
|
const detection = await detectProjectSignals(cwd);
|
|
205
209
|
const answers = await promptForAnswers(rl, stdout, stderr, detection);
|
|
206
|
-
const warnings = [...preflight.warnings, ...buildDetectionWarnings(answers, detection)];
|
|
207
210
|
const plan = await buildFilePlan(cwd, answers);
|
|
211
|
+
const warnings = [...preflight.warnings, ...buildDetectionWarnings(answers, detection), ...(plan.bundleWarnings || [])];
|
|
208
212
|
|
|
209
213
|
renderPreview(stdout, plan, warnings);
|
|
210
214
|
const confirmed = await askConfirmation(rl, 'Create these files? (y/N) ');
|
|
@@ -277,7 +281,8 @@ async function runConfigureAgents(options = {}) {
|
|
|
277
281
|
await assertDflowInitialized(cwd);
|
|
278
282
|
|
|
279
283
|
const projectContext = await inferProjectContext(cwd, rl, stdout, stderr);
|
|
280
|
-
const
|
|
284
|
+
const detectedAgents = await detectConfiguredAgents(cwd);
|
|
285
|
+
const aiAgents = await askAiAgents(rl, stdout, stderr, detectedAgents);
|
|
281
286
|
|
|
282
287
|
if (aiAgents.length === 0) {
|
|
283
288
|
throw new UserAbort('No AI agents selected. Nothing changed.');
|
|
@@ -488,10 +493,32 @@ async function detectProjectSignals(cwd) {
|
|
|
488
493
|
return {
|
|
489
494
|
hasSourceTree: hasSourceTree || relNames.has('src'),
|
|
490
495
|
trackHint,
|
|
491
|
-
stackHints
|
|
496
|
+
stackHints,
|
|
497
|
+
configuredAgents: await detectConfiguredAgents(cwd)
|
|
492
498
|
};
|
|
493
499
|
}
|
|
494
500
|
|
|
501
|
+
async function detectConfiguredAgents(cwd) {
|
|
502
|
+
// Surface agents this project already has configured so init / configure-agents
|
|
503
|
+
// can default to them instead of re-asking from scratch on every invocation.
|
|
504
|
+
// Order matches AI_AGENT_OPTIONS so the prompt numbering lines up.
|
|
505
|
+
const detected = [];
|
|
506
|
+
if (await pathExists(path.join(cwd, 'AGENTS.md'))) {
|
|
507
|
+
detected.push('agents');
|
|
508
|
+
}
|
|
509
|
+
if (
|
|
510
|
+
(await pathExists(path.join(cwd, 'CLAUDE.md'))) ||
|
|
511
|
+
(await pathExists(path.join(cwd, '.claude/commands/dflow'))) ||
|
|
512
|
+
(await pathExists(path.join(cwd, '.claude/skills/dflow')))
|
|
513
|
+
) {
|
|
514
|
+
detected.push('claude');
|
|
515
|
+
}
|
|
516
|
+
if (await pathExists(path.join(cwd, '.github/copilot-instructions.md'))) {
|
|
517
|
+
detected.push('copilot');
|
|
518
|
+
}
|
|
519
|
+
return detected;
|
|
520
|
+
}
|
|
521
|
+
|
|
495
522
|
async function collectProjectFiles(cwd, maxDepth, maxFiles) {
|
|
496
523
|
const results = [];
|
|
497
524
|
const ignored = new Set(['.git', 'node_modules', 'bin', 'obj', 'dflow']);
|
|
@@ -535,8 +562,8 @@ async function collectProjectFiles(cwd, maxDepth, maxFiles) {
|
|
|
535
562
|
function buildDetectionWarnings(answers, detection) {
|
|
536
563
|
const warnings = [];
|
|
537
564
|
|
|
538
|
-
if (answers.projectType === 'greenfield' && detection.hasSourceTree) {
|
|
539
|
-
warnings.push('
|
|
565
|
+
if (answers.projectType === 'greenfield' && detection.hasSourceTree && !detection.trackHint) {
|
|
566
|
+
warnings.push('Note: existing source files were detected (e.g. a src/ directory or a build manifest). This is expected if you just scaffolded a fresh project — Dflow will continue as Greenfield. If this is actually an existing codebase, consider re-running and selecting Brownfield.');
|
|
540
567
|
}
|
|
541
568
|
|
|
542
569
|
if (detection.trackHint && answers.projectType !== detection.trackHint) {
|
|
@@ -586,7 +613,7 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
|
|
|
586
613
|
}
|
|
587
614
|
|
|
588
615
|
const optionalFiles = await askOptionalFiles(rl, stdout, stderr);
|
|
589
|
-
const aiAgents = await askAiAgents(rl, stdout, stderr);
|
|
616
|
+
const aiAgents = await askAiAgents(rl, stdout, stderr, detection.configuredAgents || []);
|
|
590
617
|
|
|
591
618
|
return {
|
|
592
619
|
projectType,
|
|
@@ -771,17 +798,24 @@ async function askOptionalFiles(rl, stdout, stderr) {
|
|
|
771
798
|
}
|
|
772
799
|
}
|
|
773
800
|
|
|
774
|
-
async function askAiAgents(rl, stdout, stderr) {
|
|
801
|
+
async function askAiAgents(rl, stdout, stderr, defaultKeys = []) {
|
|
775
802
|
let failedAttempts = 0;
|
|
803
|
+
const validDefaults = AI_AGENT_OPTIONS
|
|
804
|
+
.filter((option) => defaultKeys.includes(option.key))
|
|
805
|
+
.map((option) => option.key);
|
|
776
806
|
|
|
777
807
|
while (true) {
|
|
778
808
|
stdout.write('\nWhich AI coding agents should Dflow configure?\n');
|
|
779
809
|
AI_AGENT_OPTIONS.forEach((option, index) => {
|
|
780
|
-
|
|
810
|
+
const marker = validDefaults.includes(option.key) ? ' (currently configured)' : '';
|
|
811
|
+
stdout.write(` ${index + 1}. ${option.label}${marker}\n`);
|
|
781
812
|
});
|
|
782
813
|
|
|
783
|
-
const
|
|
784
|
-
|
|
814
|
+
const defaultHint = validDefaults.length > 0
|
|
815
|
+
? validDefaults.map((key) => AI_AGENT_OPTIONS.findIndex((option) => option.key === key) + 1).join(',')
|
|
816
|
+
: 'none';
|
|
817
|
+
const answer = await askLine(rl, `Enter comma-separated choices or "none" (default: ${defaultHint}): `);
|
|
818
|
+
const parsed = parseMultiselectAnswer(answer, AI_AGENT_OPTIONS, validDefaults);
|
|
785
819
|
|
|
786
820
|
if (!parsed.valid) {
|
|
787
821
|
failedAttempts += 1;
|
|
@@ -1012,9 +1046,14 @@ async function buildFilePlan(cwd, answers) {
|
|
|
1012
1046
|
|
|
1013
1047
|
await finalizePlanItems(cwd, items);
|
|
1014
1048
|
|
|
1049
|
+
// Always project the workflow bundle (required for /dflow:* workflows to be reachable).
|
|
1050
|
+
const bundleWarnings = [];
|
|
1051
|
+
await addWorkflowBundleItems(cwd, items, bundleWarnings, answers.edition);
|
|
1052
|
+
|
|
1015
1053
|
return {
|
|
1016
1054
|
items,
|
|
1017
1055
|
deferred: buildDeferredItems(answers.edition),
|
|
1056
|
+
bundleWarnings,
|
|
1018
1057
|
unresolvedInitPlaceholders: Array.from(substitution.entries())
|
|
1019
1058
|
.filter(([placeholder, value]) => placeholder === value)
|
|
1020
1059
|
.map(([placeholder]) => placeholder)
|
|
@@ -1056,6 +1095,14 @@ async function buildConfigureAgentsPlan(cwd, answers) {
|
|
|
1056
1095
|
|
|
1057
1096
|
await addSkillAdapterItems(cwd, items, answers.aiAgents, answers.skills, warnings);
|
|
1058
1097
|
|
|
1098
|
+
// Project the workflow bundle on configure-agents too, so pre-039 projects (no bundle)
|
|
1099
|
+
// and edition-switch repairs get the runtime references/templates reachable. The function
|
|
1100
|
+
// is idempotent: it skips fresh bundle files, updates Dflow-generated ones, and warns
|
|
1101
|
+
// (without overwriting) on user-modified bundle files.
|
|
1102
|
+
const bundleWarnings = [];
|
|
1103
|
+
await addWorkflowBundleItems(cwd, items, bundleWarnings, answers.edition);
|
|
1104
|
+
warnings.push(...bundleWarnings);
|
|
1105
|
+
|
|
1059
1106
|
return {
|
|
1060
1107
|
items,
|
|
1061
1108
|
deferred: [],
|
|
@@ -1078,6 +1125,183 @@ async function finalizePlanItems(cwd, items) {
|
|
|
1078
1125
|
}
|
|
1079
1126
|
}
|
|
1080
1127
|
|
|
1128
|
+
async function listBundleSourceFiles(edition) {
|
|
1129
|
+
const bundleDirs = ['references', 'templates'];
|
|
1130
|
+
const files = [];
|
|
1131
|
+
|
|
1132
|
+
for (const dir of bundleDirs) {
|
|
1133
|
+
const sourceDir = path.join(TEMPLATE_ROOT, edition, dir);
|
|
1134
|
+
let entries;
|
|
1135
|
+
try {
|
|
1136
|
+
entries = await fs.readdir(sourceDir);
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
if (error.code === 'ENOENT') {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
throw error;
|
|
1142
|
+
}
|
|
1143
|
+
for (const entry of entries) {
|
|
1144
|
+
const sourceRel = `${dir}/${entry}`;
|
|
1145
|
+
const sourcePath = path.join(sourceDir, entry);
|
|
1146
|
+
const stat = await fs.stat(sourcePath);
|
|
1147
|
+
if (stat.isFile()) {
|
|
1148
|
+
files.push({ sourceRel, dir, name: entry });
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return files;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
async function readCurrentBundleManifest(cwd) {
|
|
1157
|
+
const manifestPath = path.join(cwd, WORKFLOW_BUNDLE_MANIFEST_PATH);
|
|
1158
|
+
try {
|
|
1159
|
+
const raw = await fs.readFile(manifestPath, 'utf8');
|
|
1160
|
+
return JSON.parse(raw);
|
|
1161
|
+
} catch {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function buildBundleManifest(edition, version, files) {
|
|
1167
|
+
return {
|
|
1168
|
+
edition,
|
|
1169
|
+
version,
|
|
1170
|
+
generatedBy: 'dflow-sdd-ddd',
|
|
1171
|
+
files: files.map((f) => `${WORKFLOW_BUNDLE_DEST}/${f.sourceRel}`)
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function injectBundleMarker(content) {
|
|
1176
|
+
return `${WORKFLOW_BUNDLE_GENERATED_MARKER}\n\n${content}`;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async function addWorkflowBundleItems(cwd, items, warnings, edition) {
|
|
1180
|
+
const bundleFiles = await listBundleSourceFiles(edition);
|
|
1181
|
+
|
|
1182
|
+
// Detect any previous manifest to handle edition-switch stale cleanup.
|
|
1183
|
+
const existingManifest = await readCurrentBundleManifest(cwd);
|
|
1184
|
+
const previousEdition = existingManifest ? existingManifest.edition : null;
|
|
1185
|
+
|
|
1186
|
+
// If the edition changed, schedule removal of stale generated files from prior edition.
|
|
1187
|
+
if (previousEdition && previousEdition !== edition) {
|
|
1188
|
+
const staleFiles = existingManifest.files || [];
|
|
1189
|
+
for (const staleRelPath of staleFiles) {
|
|
1190
|
+
const staleAbsPath = path.join(cwd, staleRelPath);
|
|
1191
|
+
let staleExists = false;
|
|
1192
|
+
try {
|
|
1193
|
+
await fs.stat(staleAbsPath);
|
|
1194
|
+
staleExists = true;
|
|
1195
|
+
} catch {
|
|
1196
|
+
staleExists = false;
|
|
1197
|
+
}
|
|
1198
|
+
if (!staleExists) {
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
const staleContent = await fs.readFile(staleAbsPath, 'utf8');
|
|
1202
|
+
if (!staleContent.includes(WORKFLOW_BUNDLE_GENERATED_MARKER)) {
|
|
1203
|
+
warnings.push(
|
|
1204
|
+
`Edition changed from ${previousEdition} to ${edition}; skipped removal of user-modified bundle file: ${staleRelPath}`
|
|
1205
|
+
);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
// Check if this path is also in the new edition bundle — if so, it will be overwritten, not removed.
|
|
1209
|
+
const newRelPaths = new Set(bundleFiles.map((f) => `${WORKFLOW_BUNDLE_DEST}/${f.sourceRel}`));
|
|
1210
|
+
if (!newRelPaths.has(staleRelPath)) {
|
|
1211
|
+
items.push({
|
|
1212
|
+
relativePath: staleRelPath,
|
|
1213
|
+
source: `stale-bundle:${previousEdition}`,
|
|
1214
|
+
notes: `stale workflow bundle file from ${previousEdition} edition`,
|
|
1215
|
+
action: 'remove',
|
|
1216
|
+
size: Buffer.byteLength(staleContent, 'utf8'),
|
|
1217
|
+
expectedContent: staleContent
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Build items for current edition bundle files.
|
|
1224
|
+
for (const { sourceRel } of bundleFiles) {
|
|
1225
|
+
const relativePath = `${WORKFLOW_BUNDLE_DEST}/${sourceRel}`;
|
|
1226
|
+
const absolutePath = path.join(cwd, relativePath);
|
|
1227
|
+
const sourceContent = await readPackagedBundleFile(edition, sourceRel);
|
|
1228
|
+
const content = injectBundleMarker(sourceContent);
|
|
1229
|
+
|
|
1230
|
+
let action;
|
|
1231
|
+
let notes = 'workflow bundle';
|
|
1232
|
+
const targetExists = await pathExists(absolutePath);
|
|
1233
|
+
|
|
1234
|
+
if (targetExists) {
|
|
1235
|
+
const existingContent = await fs.readFile(absolutePath, 'utf8');
|
|
1236
|
+
if (existingContent.includes(WORKFLOW_BUNDLE_GENERATED_MARKER)) {
|
|
1237
|
+
action = 'update';
|
|
1238
|
+
} else {
|
|
1239
|
+
action = 'skip';
|
|
1240
|
+
notes = 'workflow bundle, user-modified — skipped; remove or rename to let Dflow manage';
|
|
1241
|
+
warnings.push(
|
|
1242
|
+
`Existing ${relativePath} is not a Dflow-generated bundle file; left unchanged. Remove or rename it to let Dflow manage this file.`
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
action = 'create';
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
items.push({
|
|
1250
|
+
relativePath,
|
|
1251
|
+
source: `packaged-bundle:${edition}/${sourceRel}`,
|
|
1252
|
+
notes,
|
|
1253
|
+
content,
|
|
1254
|
+
action,
|
|
1255
|
+
overwrite: action === 'update',
|
|
1256
|
+
size: Buffer.byteLength(content, 'utf8')
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Add the manifest file.
|
|
1261
|
+
const manifestContent = JSON.stringify(
|
|
1262
|
+
buildBundleManifest(edition, pkg.version, bundleFiles),
|
|
1263
|
+
null,
|
|
1264
|
+
2
|
|
1265
|
+
) + '\n';
|
|
1266
|
+
const manifestExists = await pathExists(path.join(cwd, WORKFLOW_BUNDLE_MANIFEST_PATH));
|
|
1267
|
+
|
|
1268
|
+
items.push({
|
|
1269
|
+
relativePath: WORKFLOW_BUNDLE_MANIFEST_PATH,
|
|
1270
|
+
source: `generated:workflow-bundle-manifest`,
|
|
1271
|
+
notes: 'workflow bundle manifest',
|
|
1272
|
+
content: manifestContent,
|
|
1273
|
+
action: manifestExists ? 'update' : 'create',
|
|
1274
|
+
overwrite: true,
|
|
1275
|
+
size: Buffer.byteLength(manifestContent, 'utf8')
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
async function readPackagedBundleFile(edition, sourceRel) {
|
|
1280
|
+
const filePath = path.join(TEMPLATE_ROOT, edition, sourceRel);
|
|
1281
|
+
const normalizedRoot = path.resolve(TEMPLATE_ROOT, edition);
|
|
1282
|
+
const normalizedFilePath = path.resolve(filePath);
|
|
1283
|
+
|
|
1284
|
+
if (!normalizedFilePath.startsWith(`${normalizedRoot}${path.sep}`)) {
|
|
1285
|
+
throw new InitError(`Internal error: packaged bundle file not found: templates/${edition}/${sourceRel}`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
let buffer;
|
|
1289
|
+
try {
|
|
1290
|
+
buffer = await fs.readFile(normalizedFilePath);
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
if (error.code === 'ENOENT') {
|
|
1293
|
+
throw new InitError(`Internal error: packaged bundle file not found: templates/${edition}/${sourceRel}`);
|
|
1294
|
+
}
|
|
1295
|
+
throw new InitError(`Internal error: cannot read packaged bundle file: templates/${edition}/${sourceRel}`);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
try {
|
|
1299
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(buffer);
|
|
1300
|
+
} catch {
|
|
1301
|
+
throw new InitError(`Internal error: invalid UTF-8 packaged bundle file: templates/${edition}/${sourceRel}`);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1081
1305
|
async function addAiAgentShim(cwd, items, agent, substitution, options = {}) {
|
|
1082
1306
|
const target = getAiAgentTarget(agent);
|
|
1083
1307
|
const targetPath = path.join(cwd, target.relativePath);
|
|
@@ -1157,10 +1381,11 @@ This project uses Dflow for spec-first AI-assisted development.
|
|
|
1157
1381
|
|
|
1158
1382
|
Before planning or editing code, read and follow:
|
|
1159
1383
|
|
|
1160
|
-
- \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
|
|
1384
|
+
- \`dflow/specs/shared/AI-AGENT-GUIDE.md\` — command registry, routing rules, and project context.
|
|
1385
|
+
- \`dflow/specs/shared/dflow-workflows/\` — vendored workflow bundle with executable step definitions.
|
|
1161
1386
|
|
|
1162
|
-
Keep tool-specific instruction files small. The
|
|
1163
|
-
|
|
1387
|
+
Keep tool-specific instruction files small. The guide and workflow bundle are
|
|
1388
|
+
the authoritative sources for Dflow workflow rules, slash-command behavior,
|
|
1164
1389
|
spec locations, and SDD/DDD constraints.${commandTriggerHint}${importHint}
|
|
1165
1390
|
`;
|
|
1166
1391
|
}
|
|
@@ -1235,39 +1460,13 @@ async function addLegacyCommandAdapterCleanupItems(cwd, items, aiAgents, warning
|
|
|
1235
1460
|
}
|
|
1236
1461
|
}
|
|
1237
1462
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
/dflow:report-dflow-feedback, /dflow:status, /dflow:next, /dflow:cancel).
|
|
1246
|
-
SECONDARY (auto-trigger safety net) — engage ONLY for: adding or changing
|
|
1247
|
-
product/domain behavior, new requirements, a feature or bug-fix workflow, or
|
|
1248
|
-
spec-impacting architecture/domain-model decisions. Do NOT engage for pure
|
|
1249
|
-
refactors, infrastructure chores, formatting, or general code questions.
|
|
1250
|
-
When engaged by natural language, DO NOT auto-enter a workflow: judge the
|
|
1251
|
-
intent, suggest the matching /dflow: command, and wait for confirmation.
|
|
1252
|
-
---
|
|
1253
|
-
|
|
1254
|
-
${SKILL_ADAPTER_GENERATED_MARKER}
|
|
1255
|
-
|
|
1256
|
-
# Dflow SDD/DDD Workflow Guardian
|
|
1257
|
-
|
|
1258
|
-
This project uses Dflow for spec-first AI-assisted development. The canonical
|
|
1259
|
-
workflow contract, command registry, spec locations, and SDD/DDD rules live in:
|
|
1260
|
-
|
|
1261
|
-
- \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
|
|
1262
|
-
|
|
1263
|
-
When this skill engages, read that guide and follow the matching \`/dflow:\`
|
|
1264
|
-
workflow or control command defined there. Do not duplicate or invent workflow
|
|
1265
|
-
steps here — the guide is the single source of truth.
|
|
1266
|
-
|
|
1267
|
-
If engaged by natural language (not an explicit \`/dflow:\` command): identify
|
|
1268
|
-
which \`/dflow:\` command fits, suggest it, and wait for the developer to confirm
|
|
1269
|
-
before entering any workflow.
|
|
1270
|
-
`;
|
|
1463
|
+
// Edition-agnostic thin shell: a single canonical source at
|
|
1464
|
+
// templates/common/skill/SKILL.md (PROPOSAL-041 C1). Returns the file
|
|
1465
|
+
// verbatim — frontmatter, marker, and body all live in the source file so
|
|
1466
|
+
// the skill content can be edited without touching JS.
|
|
1467
|
+
async function buildDflowSkillAdapter() {
|
|
1468
|
+
const sourcePath = path.join(TEMPLATE_ROOT, COMMON_SKILL_SOURCE_REL);
|
|
1469
|
+
return fs.readFile(sourcePath, 'utf8');
|
|
1271
1470
|
}
|
|
1272
1471
|
|
|
1273
1472
|
async function addSkillAdapterItems(cwd, items, aiAgents, skills, warnings) {
|
|
@@ -1307,11 +1506,13 @@ async function addSkillAdapterItems(cwd, items, aiAgents, skills, warnings) {
|
|
|
1307
1506
|
return;
|
|
1308
1507
|
}
|
|
1309
1508
|
|
|
1509
|
+
const skillContent = await buildDflowSkillAdapter();
|
|
1310
1510
|
items.push({
|
|
1311
1511
|
relativePath,
|
|
1312
1512
|
source: 'generated:claude-skill-adapter',
|
|
1313
1513
|
notes: 'skill adapter, thin skill pointing to AI-AGENT-GUIDE.md',
|
|
1314
|
-
content:
|
|
1514
|
+
content: skillContent,
|
|
1515
|
+
size: Buffer.byteLength(skillContent, 'utf8'),
|
|
1315
1516
|
overwrite: true
|
|
1316
1517
|
});
|
|
1317
1518
|
}
|
|
@@ -1381,7 +1582,8 @@ ${COMMAND_ADAPTER_GENERATED_MARKER}
|
|
|
1381
1582
|
|
|
1382
1583
|
Execute the canonical \`${command.label}\` Dflow workflow or control command.
|
|
1383
1584
|
|
|
1384
|
-
|
|
1585
|
+
Registry and rules: \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
|
|
1586
|
+
Workflow steps: \`dflow/specs/shared/dflow-workflows/\`
|
|
1385
1587
|
|
|
1386
1588
|
${argHint}
|
|
1387
1589
|
`;
|
|
@@ -1952,14 +2154,17 @@ function collectUnresolvedPlaceholderWarnings(plan, createdPaths) {
|
|
|
1952
2154
|
return [];
|
|
1953
2155
|
}
|
|
1954
2156
|
|
|
2157
|
+
const placeholderFiles = new Set();
|
|
1955
2158
|
for (const item of plan.items) {
|
|
1956
2159
|
if (!createdSet.has(item.relativePath)) {
|
|
1957
2160
|
continue;
|
|
1958
2161
|
}
|
|
1959
2162
|
const matches = item.content.match(/{[^{}\n]+}/g) || [];
|
|
1960
|
-
matches
|
|
1961
|
-
|
|
1962
|
-
|
|
2163
|
+
const hits = matches.filter((match) => unresolvedInitPlaceholders.has(match));
|
|
2164
|
+
hits.forEach((match) => placeholders.add(match));
|
|
2165
|
+
if (hits.length > 0) {
|
|
2166
|
+
placeholderFiles.add(item.relativePath);
|
|
2167
|
+
}
|
|
1963
2168
|
}
|
|
1964
2169
|
|
|
1965
2170
|
if (placeholders.size === 0) {
|
|
@@ -1969,7 +2174,10 @@ function collectUnresolvedPlaceholderWarnings(plan, createdPaths) {
|
|
|
1969
2174
|
const sorted = Array.from(placeholders).sort();
|
|
1970
2175
|
const shown = sorted.slice(0, 25).join(', ');
|
|
1971
2176
|
const suffix = sorted.length > 25 ? `, and ${sorted.length - 25} more` : '';
|
|
1972
|
-
|
|
2177
|
+
const files = Array.from(placeholderFiles).sort();
|
|
2178
|
+
const filesShown = files.slice(0, 10).join(', ');
|
|
2179
|
+
const filesSuffix = files.length > 10 ? `, and ${files.length - 10} more` : '';
|
|
2180
|
+
return [`Unresolved placeholders remain for later SDD workflows: ${shown}${suffix}. Fill them in (or leave for the workflow to resolve) in: ${filesShown}${filesSuffix}.`];
|
|
1973
2181
|
}
|
|
1974
2182
|
|
|
1975
2183
|
function printResultReport(stdout, result, deferred) {
|
|
@@ -2086,6 +2294,9 @@ function normalizePath(value) {
|
|
|
2086
2294
|
}
|
|
2087
2295
|
|
|
2088
2296
|
function formatBytes(bytes) {
|
|
2297
|
+
if (!Number.isFinite(bytes)) {
|
|
2298
|
+
return '—';
|
|
2299
|
+
}
|
|
2089
2300
|
if (bytes < 1024) {
|
|
2090
2301
|
return `${bytes} B`;
|
|
2091
2302
|
}
|