dflow-sdd-ddd 0.8.0 → 0.9.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 +47 -0
- package/LICENSE +679 -21
- package/README.en.md +5 -4
- package/README.md +3 -3
- package/bin/dflow.js +3 -2
- package/docs/using-with-codex.en.md +12 -8
- package/docs/using-with-codex.md +8 -6
- package/lib/init.js +217 -35
- package/package.json +2 -2
- package/templates/brownfield/references/dflow-feedback-flow.md +135 -63
- package/templates/brownfield/references/finish-feature-flow.md +56 -21
- package/templates/brownfield/references/git-integration.md +65 -6
- package/templates/brownfield/references/init-project-flow.md +36 -19
- package/templates/brownfield/references/modify-existing-flow.md +4 -0
- package/templates/brownfield/references/new-feature-flow.md +15 -0
- package/templates/brownfield/references/new-phase-flow.md +15 -0
- package/templates/brownfield/scaffolding/Git-principles-gitflow.md +13 -12
- package/templates/brownfield/scaffolding/Git-principles-trunk.md +13 -16
- package/templates/brownfield/templates/_index.md +20 -2
- package/templates/greenfield/references/dflow-feedback-flow.md +135 -63
- package/templates/greenfield/references/finish-feature-flow.md +55 -21
- package/templates/greenfield/references/git-integration.md +65 -6
- package/templates/greenfield/references/init-project-flow.md +36 -19
- package/templates/greenfield/references/modify-existing-flow.md +4 -0
- package/templates/greenfield/references/new-feature-flow.md +15 -0
- package/templates/greenfield/references/new-phase-flow.md +15 -0
- package/templates/greenfield/scaffolding/Git-principles-gitflow.md +13 -12
- package/templates/greenfield/scaffolding/Git-principles-trunk.md +13 -17
- package/templates/greenfield/templates/_index.md +20 -2
package/README.en.md
CHANGED
|
@@ -35,8 +35,9 @@ npm install -g dflow-sdd-ddd
|
|
|
35
35
|
dflow init
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
The init flow asks whether the project is greenfield or brownfield,
|
|
39
|
-
|
|
38
|
+
The init flow asks whether the project is greenfield or brownfield, which Git
|
|
39
|
+
policy the team follows (GitFlow / Trunk), and how AI-made commits should be
|
|
40
|
+
marked, then previews the files it will create. Existing files are not overwritten. Init
|
|
40
41
|
creates workflow documentation and AI instruction files; it does not inspect,
|
|
41
42
|
refactor, or migrate your application code.
|
|
42
43
|
|
|
@@ -317,7 +318,7 @@ Usable only inside an already-started active feature. Targets pointing at `compl
|
|
|
317
318
|
|---|---|---|
|
|
318
319
|
| `/dflow:verify` | Need to confirm docs, code, tests, and tech-debt records are still in sync | Drift report across spec, domain docs, implementation, tests, and debt records |
|
|
319
320
|
| `/dflow:pr-review` | A change is ready for review | SDD/DDD compliance review checklist with risks, gaps, and follow-up items |
|
|
320
|
-
| `/dflow:report-dflow-feedback` | You or the AI found a Dflow issue or improvement while using it | Sanitized local
|
|
321
|
+
| `/dflow:report-dflow-feedback` | You or the AI found a Dflow issue or improvement while using it | Sanitized local draft rendered field-by-field for the upstream issue form, ready to paste; nothing is submitted automatically |
|
|
321
322
|
|
|
322
323
|
### What should I run? (rule of thumb)
|
|
323
324
|
|
|
@@ -389,4 +390,4 @@ release history.
|
|
|
389
390
|
|
|
390
391
|
## License
|
|
391
392
|
|
|
392
|
-
|
|
393
|
+
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See [LICENSE](LICENSE).
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install -g dflow-sdd-ddd
|
|
|
33
33
|
dflow init
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
init 流程會詢問是 greenfield 或 brownfield
|
|
36
|
+
init 流程會詢問是 greenfield 或 brownfield、團隊採用的 Git policy(GitFlow / Trunk)、以及 AI commit 的標記方式,接著預覽即將建立的檔案。既有檔案不會被覆寫。Init 只建立 workflow 文件與 AI 指示檔,**不會**檢查、重構、或遷移你的應用程式碼。
|
|
37
37
|
|
|
38
38
|
若專案已初始化、之後又要加入另一個 AI 程式設計工具,執行:
|
|
39
39
|
|
|
@@ -263,7 +263,7 @@ Dflow 指令依角色分四類。「我要做的事」對應到指令的速查
|
|
|
263
263
|
|---|---|---|
|
|
264
264
|
| `/dflow:verify` | 需要確認文件、程式、測試、債務紀錄是否一致 | 跨規格、領域文件、實作、測試、債務的 drift report |
|
|
265
265
|
| `/dflow:pr-review` | 變更已準備接受審查 | SDD/DDD 合規 review 清單,含風險、缺口、後續項目 |
|
|
266
|
-
| `/dflow:report-dflow-feedback` | 你或 AI 在使用中發現 Dflow 本身的問題 | sanitized
|
|
266
|
+
| `/dflow:report-dflow-feedback` | 你或 AI 在使用中發現 Dflow 本身的問題 | sanitized 的本地草稿,逐欄對齊上游 issue 表單可直接貼上;不自動送出 |
|
|
267
267
|
|
|
268
268
|
### 該選哪個指令(rule of thumb)
|
|
269
269
|
|
|
@@ -328,4 +328,4 @@ GitHub 上的 source 可能包含 `0.2.0` 之後尚未發佈的 repo 變更。
|
|
|
328
328
|
|
|
329
329
|
## 授權
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
GNU Affero General Public License v3.0 或更新版本(AGPL-3.0-or-later),見 [LICENSE](LICENSE)。
|
package/bin/dflow.js
CHANGED
|
@@ -22,8 +22,9 @@ function printInitHelp() {
|
|
|
22
22
|
dflow init
|
|
23
23
|
|
|
24
24
|
Initializes Dflow project specs under dflow/specs/.
|
|
25
|
-
The command prompts for project type, tech stack, prose language,
|
|
26
|
-
optional starter files, and AI coding agents before showing a
|
|
25
|
+
The command prompts for project type, tech stack, prose language, Git policy,
|
|
26
|
+
AI commit marker, optional starter files, and AI coding agents before showing a
|
|
27
|
+
full file preview.
|
|
27
28
|
`);
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -162,20 +162,24 @@ triggers only. It does not create Codex command files and it does not add
|
|
|
162
162
|
equivalent to Claude `.claude/commands` or Copilot `.github/prompts`.
|
|
163
163
|
|
|
164
164
|
When you select `AGENTS.md - Codex / Copilot coding agent` in
|
|
165
|
-
`--command-adapters` mode
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
`--command-adapters` mode, Dflow writes a trigger list generated from the
|
|
166
|
+
canonical command registry into `AGENTS.md`. Those triggers are still plain
|
|
167
|
+
text prompts, for example:
|
|
168
168
|
|
|
169
169
|
```text
|
|
170
170
|
dflow:new-feature
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
As long as `AGENTS.md` is an unmodified Dflow-generated shim — which is exactly
|
|
174
|
+
the case after the standard `init` → `configure-agents --command-adapters`
|
|
175
|
+
flow — Dflow **injects** the trigger section directly into `AGENTS.md`, wrapped
|
|
176
|
+
in markers, with zero manual merge. Re-running re-projects that same section in
|
|
177
|
+
place instead of appending a duplicate.
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
If `AGENTS.md` was edited after Dflow generated it, or is your own custom file,
|
|
180
|
+
Dflow leaves it untouched and instead writes the trigger section to a merge
|
|
181
|
+
snippet for you to merge manually. In that case the Codex-target merge snippet
|
|
182
|
+
filename is `dflow/specs/shared/AGENTS-md-command-adapters-snippet.md`.
|
|
179
183
|
|
|
180
184
|
### Version-Control Policy for Generated Artifacts (Codex)
|
|
181
185
|
|
package/docs/using-with-codex.md
CHANGED
|
@@ -149,18 +149,20 @@ Claude `.claude/commands` 或 Copilot `.github/prompts` 對等的 Dflow command-
|
|
|
149
149
|
adapter。
|
|
150
150
|
|
|
151
151
|
當你在 `--command-adapters` 模式下選擇 `AGENTS.md - Codex / Copilot coding agent`
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
時,Dflow 會把從 canonical command registry 產生的 trigger 清單寫進 `AGENTS.md`。
|
|
153
|
+
這些 trigger 仍是文字提示,例如:
|
|
154
154
|
|
|
155
155
|
```text
|
|
156
156
|
dflow:new-feature
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
只要 `AGENTS.md` 是 Dflow 產生且未經改動的 shim(標準 `init` → `configure-agents
|
|
160
|
+
--command-adapters` 流程就是如此),Dflow 會把帶 marker 的 trigger 段**直接注入**
|
|
161
|
+
`AGENTS.md`,零手動合併;重複執行會就地重投影同一段、不會重複附加。
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
如果 `AGENTS.md` 在 Dflow 產生後被改過、或本來就是你自訂的檔案,Dflow 會保留既有
|
|
164
|
+
檔案不動,改把 trigger 段寫到 merge snippet 供你手動合併。此情況下 Codex 目標的
|
|
165
|
+
merge snippet 檔名是 `dflow/specs/shared/AGENTS-md-command-adapters-snippet.md`。
|
|
164
166
|
|
|
165
167
|
### 產生物的版控政策(Codex)
|
|
166
168
|
|
package/lib/init.js
CHANGED
|
@@ -13,6 +13,8 @@ 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
15
|
const WORKFLOW_BUNDLE_GENERATED_MARKER = '<!-- dflow-generated: workflow-bundle -->';
|
|
16
|
+
const CODEX_TRIGGER_SECTION_START = '<!-- dflow-generated: codex-command-triggers START -->';
|
|
17
|
+
const CODEX_TRIGGER_SECTION_END = '<!-- dflow-generated: codex-command-triggers END -->';
|
|
16
18
|
const WORKFLOW_BUNDLE_DEST = 'dflow/specs/shared/dflow-workflows';
|
|
17
19
|
const WORKFLOW_BUNDLE_MANIFEST_PATH = `${WORKFLOW_BUNDLE_DEST}/.dflow-bundle-manifest.json`;
|
|
18
20
|
const COMMON_SKILL_SOURCE_REL = 'common/skill/SKILL.md';
|
|
@@ -110,16 +112,42 @@ const OPTIONAL_FILE_OPTIONS = [
|
|
|
110
112
|
key: 'overview',
|
|
111
113
|
label: '_overview.md - system overview',
|
|
112
114
|
aliases: ['overview', '_overview.md']
|
|
115
|
+
}
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
// Git policy is a mandatory team choice (PROPOSAL-047): both options use feature
|
|
119
|
+
// branches; the choice selects the finish-stage merge guidance and drives the
|
|
120
|
+
// runtime branch gates / commit checkpoints.
|
|
121
|
+
const GIT_POLICY_OPTIONS = [
|
|
122
|
+
{
|
|
123
|
+
key: 'gitflow',
|
|
124
|
+
label: 'Git Flow - long-lived develop/release branches',
|
|
125
|
+
aliases: ['gitflow', 'git flow', 'flow']
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: 'trunk',
|
|
129
|
+
label: 'Trunk / GitHub Flow - short-lived feature branches (lightest)',
|
|
130
|
+
aliases: ['trunk', 'trunk-based', 'github flow', 'githubflow']
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// How AI-made commits are marked (PROPOSAL-047). Chosen once at init; the
|
|
135
|
+
// runtime does not re-ask. None is the default.
|
|
136
|
+
const AI_COMMIT_MARKER_OPTIONS = [
|
|
137
|
+
{
|
|
138
|
+
key: 'none',
|
|
139
|
+
label: 'None - AI commits look like any other commit',
|
|
140
|
+
aliases: ['none', 'off', 'no']
|
|
113
141
|
},
|
|
114
142
|
{
|
|
115
|
-
key: '
|
|
116
|
-
label: '
|
|
117
|
-
aliases: ['
|
|
143
|
+
key: 'co-authored-by',
|
|
144
|
+
label: 'Co-Authored-By trailer (dflow-ai) - filterable / auditable',
|
|
145
|
+
aliases: ['co-authored-by', 'co-author', 'trailer', 'coauthored']
|
|
118
146
|
},
|
|
119
147
|
{
|
|
120
|
-
key: '
|
|
121
|
-
label: '
|
|
122
|
-
aliases: ['
|
|
148
|
+
key: 'prefix',
|
|
149
|
+
label: '[ai-assisted] commit-message prefix - visible at a glance',
|
|
150
|
+
aliases: ['prefix', 'ai-assisted', '[ai-assisted]']
|
|
123
151
|
}
|
|
124
152
|
];
|
|
125
153
|
|
|
@@ -612,6 +640,20 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
|
|
|
612
640
|
proseLanguage = await askCustomProseLanguage(rl, stderr);
|
|
613
641
|
}
|
|
614
642
|
|
|
643
|
+
const gitPolicy = await askSelect(rl, stdout, stderr, {
|
|
644
|
+
id: 'Q5',
|
|
645
|
+
question: 'Which Git policy does the team follow? (drives branch gates and finish-stage merge guidance)',
|
|
646
|
+
options: GIT_POLICY_OPTIONS,
|
|
647
|
+
defaultKey: null
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
const aiCommitMarker = await askSelect(rl, stdout, stderr, {
|
|
651
|
+
id: 'Q6',
|
|
652
|
+
question: 'How should AI-made commits be marked? (the AI offers to commit at checkpoints; you can always decline)',
|
|
653
|
+
options: AI_COMMIT_MARKER_OPTIONS,
|
|
654
|
+
defaultKey: 'none'
|
|
655
|
+
});
|
|
656
|
+
|
|
615
657
|
const optionalFiles = await askOptionalFiles(rl, stdout, stderr);
|
|
616
658
|
const aiAgents = await askAiAgents(rl, stdout, stderr, detection.configuredAgents || []);
|
|
617
659
|
|
|
@@ -621,6 +663,8 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
|
|
|
621
663
|
techStackSummary,
|
|
622
664
|
migrationContext,
|
|
623
665
|
proseLanguage,
|
|
666
|
+
gitPolicy,
|
|
667
|
+
aiCommitMarker,
|
|
624
668
|
optionalFiles,
|
|
625
669
|
aiAgents
|
|
626
670
|
};
|
|
@@ -645,10 +689,26 @@ async function inferProjectContext(cwd, rl, stdout, stderr) {
|
|
|
645
689
|
techStackSummary: await inferTechStackSummary(cwd),
|
|
646
690
|
migrationContext: await inferMigrationContext(cwd),
|
|
647
691
|
proseLanguage: await inferProseLanguage(cwd),
|
|
692
|
+
gitPolicy: await inferGitPolicy(cwd),
|
|
693
|
+
aiCommitMarker: await inferAiCommitMarker(cwd),
|
|
648
694
|
optionalFiles: []
|
|
649
695
|
};
|
|
650
696
|
}
|
|
651
697
|
|
|
698
|
+
async function inferGitPolicy(cwd) {
|
|
699
|
+
const conventionsPath = path.join(cwd, 'dflow/specs/shared/_conventions.md');
|
|
700
|
+
const content = await fs.readFile(conventionsPath, 'utf8').catch(() => '');
|
|
701
|
+
const match = content.match(/Selected Git policy:\s*`([^`]+)`/);
|
|
702
|
+
return match ? match[1] : null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function inferAiCommitMarker(cwd) {
|
|
706
|
+
const conventionsPath = path.join(cwd, 'dflow/specs/shared/_conventions.md');
|
|
707
|
+
const content = await fs.readFile(conventionsPath, 'utf8').catch(() => '');
|
|
708
|
+
const match = content.match(/AI commit marker:\s*`([^`]+)`/);
|
|
709
|
+
return match ? match[1] : null;
|
|
710
|
+
}
|
|
711
|
+
|
|
652
712
|
async function inferExistingEdition(cwd) {
|
|
653
713
|
if (await pathExists(path.join(cwd, 'dflow/specs/architecture/tech-debt.md'))) {
|
|
654
714
|
return 'greenfield';
|
|
@@ -767,33 +827,22 @@ async function askOptionalFiles(rl, stdout, stderr) {
|
|
|
767
827
|
while (true) {
|
|
768
828
|
stdout.write('\nWhich optional starter files should Dflow seed?\n');
|
|
769
829
|
OPTIONAL_FILE_OPTIONS.forEach((option, index) => {
|
|
770
|
-
const defaultMarker = option.key === 'overview'
|
|
830
|
+
const defaultMarker = option.key === 'overview' ? ' (recommended)' : '';
|
|
771
831
|
stdout.write(` ${index + 1}. ${option.label}${defaultMarker}\n`);
|
|
772
832
|
});
|
|
773
833
|
|
|
774
|
-
const answer = await askLine(rl, 'Enter comma-separated choices, "none", or press Enter for recommended [1
|
|
775
|
-
const parsed = parseMultiselectAnswer(answer, OPTIONAL_FILE_OPTIONS, ['overview'
|
|
834
|
+
const answer = await askLine(rl, 'Enter comma-separated choices, "none", or press Enter for recommended [1]: ');
|
|
835
|
+
const parsed = parseMultiselectAnswer(answer, OPTIONAL_FILE_OPTIONS, ['overview']);
|
|
776
836
|
|
|
777
837
|
if (!parsed.valid) {
|
|
778
838
|
failedAttempts += 1;
|
|
779
839
|
if (failedAttempts >= 3) {
|
|
780
|
-
throw new InitError('Too many invalid attempts for
|
|
840
|
+
throw new InitError('Too many invalid attempts for Q7. Dflow init aborted.');
|
|
781
841
|
}
|
|
782
842
|
stderr.write(`${parsed.message} (${3 - failedAttempts} attempts left)\n`);
|
|
783
843
|
continue;
|
|
784
844
|
}
|
|
785
845
|
|
|
786
|
-
if (parsed.values.includes('git-trunk') && parsed.values.includes('git-flow')) {
|
|
787
|
-
const keepBoth = await askConfirmation(
|
|
788
|
-
rl,
|
|
789
|
-
'You selected both Git principles templates. Most projects choose one. Keep both? (y/N) '
|
|
790
|
-
);
|
|
791
|
-
if (!keepBoth) {
|
|
792
|
-
failedAttempts = 0;
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
846
|
return parsed.values;
|
|
798
847
|
}
|
|
799
848
|
}
|
|
@@ -997,6 +1046,7 @@ async function buildFilePlan(cwd, answers) {
|
|
|
997
1046
|
content = substitutePlaceholders(content, substitution);
|
|
998
1047
|
if (options.injectProseLanguage) {
|
|
999
1048
|
content = ensureProseLanguageSection(content, answers.proseLanguage);
|
|
1049
|
+
content = ensureConventionPolicySections(content, answers);
|
|
1000
1050
|
}
|
|
1001
1051
|
items.push({
|
|
1002
1052
|
relativePath,
|
|
@@ -1030,11 +1080,13 @@ async function buildFilePlan(cwd, answers) {
|
|
|
1030
1080
|
if (answers.optionalFiles.includes('overview')) {
|
|
1031
1081
|
await addTemplate('dflow/specs/shared/_overview.md', 'scaffolding/_overview.md', 'selected');
|
|
1032
1082
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (answers.
|
|
1037
|
-
await addTemplate('dflow/specs/shared/Git-principles-gitflow.md', 'scaffolding/Git-principles-gitflow.md', 'selected');
|
|
1083
|
+
|
|
1084
|
+
// PROPOSAL-047: the selected Git policy is mandatory — always project exactly
|
|
1085
|
+
// the matching Git-principles file so the runtime branch gates have a policy.
|
|
1086
|
+
if (answers.gitPolicy === 'gitflow') {
|
|
1087
|
+
await addTemplate('dflow/specs/shared/Git-principles-gitflow.md', 'scaffolding/Git-principles-gitflow.md', 'mandatory, selected Git policy');
|
|
1088
|
+
} else {
|
|
1089
|
+
await addTemplate('dflow/specs/shared/Git-principles-trunk.md', 'scaffolding/Git-principles-trunk.md', 'mandatory, selected Git policy');
|
|
1038
1090
|
}
|
|
1039
1091
|
|
|
1040
1092
|
if (answers.aiAgents.length > 0) {
|
|
@@ -1080,7 +1132,7 @@ async function buildConfigureAgentsPlan(cwd, answers) {
|
|
|
1080
1132
|
const commandRegistry = answers.commandAdapters ? parseDflowCommandRegistry(content) : [];
|
|
1081
1133
|
|
|
1082
1134
|
for (const agent of answers.aiAgents) {
|
|
1083
|
-
await addAiAgentShim(cwd, items, agent, substitution, { commandRegistry });
|
|
1135
|
+
await addAiAgentShim(cwd, items, agent, substitution, { commandRegistry, warnings });
|
|
1084
1136
|
}
|
|
1085
1137
|
|
|
1086
1138
|
if (answers.commandAdapters) {
|
|
@@ -1308,15 +1360,48 @@ async function addAiAgentShim(cwd, items, agent, substitution, options = {}) {
|
|
|
1308
1360
|
const targetExists = await pathExists(targetPath);
|
|
1309
1361
|
const targetConfigured = targetExists && await fileReferencesAiAgentGuide(targetPath);
|
|
1310
1362
|
const commandRegistry = options.commandRegistry || [];
|
|
1363
|
+
const warnings = options.warnings;
|
|
1311
1364
|
const codexCommandAdapterSnippet = target.relativePath === 'AGENTS.md' && commandRegistry.length > 0 && targetExists;
|
|
1365
|
+
const content = substitutePlaceholders(buildAiAgentShim(target.relativePath, options.commandRegistry), substitution);
|
|
1366
|
+
const source = `generated:${agent}-shim`;
|
|
1367
|
+
|
|
1368
|
+
// PROPOSAL-046: when adding Codex command triggers and the existing AGENTS.md
|
|
1369
|
+
// is an unmodified Dflow shim, inject the marked trigger section directly
|
|
1370
|
+
// (zero manual merge) instead of parking a side snippet. A user-modified shim
|
|
1371
|
+
// still degrades safely to the snippet + warning.
|
|
1372
|
+
if (codexCommandAdapterSnippet && targetConfigured) {
|
|
1373
|
+
const existingContent = await fs.readFile(targetPath, 'utf8');
|
|
1374
|
+
const baseShim = substitutePlaceholders(buildAiAgentShim(target.relativePath), substitution);
|
|
1375
|
+
if (isPristineDflowAgentsShim(existingContent, baseShim)) {
|
|
1376
|
+
items.push({
|
|
1377
|
+
relativePath: target.relativePath,
|
|
1378
|
+
source,
|
|
1379
|
+
notes: `selected, injected command trigger section into Dflow-generated ${target.relativePath}`,
|
|
1380
|
+
content,
|
|
1381
|
+
overwrite: true
|
|
1382
|
+
});
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
if (warnings) {
|
|
1386
|
+
warnings.push(
|
|
1387
|
+
`Existing ${target.relativePath} was modified after Dflow generated it; wrote the command trigger section to dflow/specs/shared/AGENTS-md-command-adapters-snippet.md for manual merge.`
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
items.push({
|
|
1391
|
+
relativePath: 'dflow/specs/shared/AGENTS-md-command-adapters-snippet.md',
|
|
1392
|
+
source,
|
|
1393
|
+
notes: `selected, ${target.relativePath} was modified after Dflow generated it; merge this command trigger snippet manually`,
|
|
1394
|
+
content,
|
|
1395
|
+
overwrite: true
|
|
1396
|
+
});
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1312
1400
|
const relativePath = codexCommandAdapterSnippet
|
|
1313
|
-
?
|
|
1401
|
+
? target.snippetPath
|
|
1314
1402
|
: (targetExists && !targetConfigured ? target.snippetPath : target.relativePath);
|
|
1315
|
-
const content = substitutePlaceholders(buildAiAgentShim(target.relativePath, options.commandRegistry), substitution);
|
|
1316
1403
|
let notes = 'selected, tool-specific shim';
|
|
1317
|
-
if (codexCommandAdapterSnippet
|
|
1318
|
-
notes = `selected, ${target.relativePath} already points to AI-AGENT-GUIDE.md; merge this command trigger snippet manually`;
|
|
1319
|
-
} else if (codexCommandAdapterSnippet) {
|
|
1404
|
+
if (codexCommandAdapterSnippet) {
|
|
1320
1405
|
notes = `selected, ${target.relativePath} already exists; merge this command trigger snippet manually`;
|
|
1321
1406
|
} else if (targetConfigured) {
|
|
1322
1407
|
notes = `selected, ${target.relativePath} already points to AI-AGENT-GUIDE.md`;
|
|
@@ -1326,7 +1411,7 @@ async function addAiAgentShim(cwd, items, agent, substitution, options = {}) {
|
|
|
1326
1411
|
|
|
1327
1412
|
items.push({
|
|
1328
1413
|
relativePath,
|
|
1329
|
-
source
|
|
1414
|
+
source,
|
|
1330
1415
|
notes,
|
|
1331
1416
|
content,
|
|
1332
1417
|
overwrite: codexCommandAdapterSnippet
|
|
@@ -1544,6 +1629,36 @@ function normalizeCommandAdapterFingerprint(content) {
|
|
|
1544
1629
|
return String(content).replace(/\r\n/g, '\n');
|
|
1545
1630
|
}
|
|
1546
1631
|
|
|
1632
|
+
function normalizeShimForMatch(content) {
|
|
1633
|
+
return String(content)
|
|
1634
|
+
.replace(/\r\n/g, '\n')
|
|
1635
|
+
.split('\n')
|
|
1636
|
+
.map((line) => line.replace(/[ \t]+$/, ''))
|
|
1637
|
+
.join('\n')
|
|
1638
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1639
|
+
.trim();
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function stripCodexTriggerBlock(content) {
|
|
1643
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1644
|
+
const re = new RegExp(
|
|
1645
|
+
`\\n*${escape(CODEX_TRIGGER_SECTION_START)}[\\s\\S]*?${escape(CODEX_TRIGGER_SECTION_END)}\\n*`
|
|
1646
|
+
);
|
|
1647
|
+
return content.replace(re, '\n');
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// An AGENTS.md is a safely-injectable Dflow shim when, after removing any
|
|
1651
|
+
// previously-injected trigger block, it matches the shim Dflow itself
|
|
1652
|
+
// generates. This covers both a pristine 0.8.0/0.9.0 shim (no marker, normalized
|
|
1653
|
+
// exact-template match) and a shim Dflow already injected into (idempotent
|
|
1654
|
+
// re-projection). A user-edited shim fails the match and degrades to a snippet.
|
|
1655
|
+
// (A future shim could carry its own generated-marker for a cheaper check, but
|
|
1656
|
+
// that would require freezing this pre-marker template for back-compat matching;
|
|
1657
|
+
// the normalized template match works for both eras without that.)
|
|
1658
|
+
function isPristineDflowAgentsShim(existingContent, baseShim) {
|
|
1659
|
+
return normalizeShimForMatch(stripCodexTriggerBlock(existingContent)) === normalizeShimForMatch(baseShim);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1547
1662
|
function buildCodexCommandTriggerSection(commandRegistry) {
|
|
1548
1663
|
const triggers = commandRegistry
|
|
1549
1664
|
.map((command) => {
|
|
@@ -1554,6 +1669,8 @@ function buildCodexCommandTriggerSection(commandRegistry) {
|
|
|
1554
1669
|
|
|
1555
1670
|
return `
|
|
1556
1671
|
|
|
1672
|
+
${CODEX_TRIGGER_SECTION_START}
|
|
1673
|
+
|
|
1557
1674
|
## Dflow Text Triggers
|
|
1558
1675
|
|
|
1559
1676
|
Codex does not install Dflow command files. When the developer asks for a
|
|
@@ -1568,6 +1685,8 @@ and execute it.
|
|
|
1568
1685
|
Recognized canonical triggers:
|
|
1569
1686
|
|
|
1570
1687
|
${triggers}
|
|
1688
|
+
|
|
1689
|
+
${CODEX_TRIGGER_SECTION_END}
|
|
1571
1690
|
`;
|
|
1572
1691
|
}
|
|
1573
1692
|
|
|
@@ -1757,8 +1876,7 @@ const PLACEHOLDER_ALIASES = {
|
|
|
1757
1876
|
|
|
1758
1877
|
function buildSubstitutionMap(cwd, answers) {
|
|
1759
1878
|
const extracted = extractTechStackPlaceholders(answers.techStackSummary);
|
|
1760
|
-
const
|
|
1761
|
-
const gitStyle = gitSelection.length === 1 ? (gitSelection[0] === 'git-trunk' ? 'trunk' : 'gitflow') : null;
|
|
1879
|
+
const gitStyle = answers.gitPolicy === 'gitflow' ? 'gitflow' : (answers.gitPolicy === 'trunk' ? 'trunk' : null);
|
|
1762
1880
|
const systemName = path.basename(cwd);
|
|
1763
1881
|
|
|
1764
1882
|
const map = new Map([
|
|
@@ -1990,6 +2108,70 @@ function stripProseLanguageSections(content) {
|
|
|
1990
2108
|
return kept.join('\n');
|
|
1991
2109
|
}
|
|
1992
2110
|
|
|
2111
|
+
function stripNamedSections(content, headings) {
|
|
2112
|
+
const set = new Set(headings.map((heading) => `## ${heading}`));
|
|
2113
|
+
const kept = [];
|
|
2114
|
+
let skipping = false;
|
|
2115
|
+
|
|
2116
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2117
|
+
if (set.has(line.trim())) {
|
|
2118
|
+
skipping = true;
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
if (skipping && /^## /.test(line)) {
|
|
2122
|
+
skipping = false;
|
|
2123
|
+
}
|
|
2124
|
+
if (!skipping) {
|
|
2125
|
+
kept.push(line);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
return kept.join('\n');
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function buildGitPolicySection(gitPolicy) {
|
|
2133
|
+
const policy = gitPolicy === 'gitflow' ? 'gitflow' : 'trunk';
|
|
2134
|
+
return `## Git Policy
|
|
2135
|
+
|
|
2136
|
+
Selected Git policy: \`${policy}\`
|
|
2137
|
+
|
|
2138
|
+
Dflow runtime branch gates and finish-feature guidance follow this policy. Both
|
|
2139
|
+
policies use feature branches; the policy selects the finish-stage merge
|
|
2140
|
+
guidance — \`gitflow\` introduces merge-commit / release+develop flow, while
|
|
2141
|
+
\`trunk\` (GitHub Flow) favors squash or fast-forward back to the main branch
|
|
2142
|
+
with small, frequent merges.`;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function buildAiCommitPolicySection(aiCommitMarker) {
|
|
2146
|
+
const marker = ['none', 'co-authored-by', 'prefix'].includes(aiCommitMarker) ? aiCommitMarker : 'none';
|
|
2147
|
+
return `## AI Commit Policy
|
|
2148
|
+
|
|
2149
|
+
AI commit marker: \`${marker}\`
|
|
2150
|
+
|
|
2151
|
+
At lifecycle checkpoints the AI may offer to commit using your Git identity; you
|
|
2152
|
+
can always decline (Y / N). Completed and skipped checkpoints are recorded in
|
|
2153
|
+
each feature's Checkpoint Log. Marker modes:
|
|
2154
|
+
|
|
2155
|
+
- \`none\`: AI-made commits carry no extra marker.
|
|
2156
|
+
- \`co-authored-by\`: append a \`Co-Authored-By: dflow-ai <noreply@dflow.local>\`
|
|
2157
|
+
trailer (teams may customize the name/email).
|
|
2158
|
+
- \`prefix\`: prefix the commit subject with \`[ai-assisted]\`.`;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function ensureConventionPolicySections(content, answers) {
|
|
2162
|
+
const stripped = stripNamedSections(content, ['Git Policy', 'AI Commit Policy']).replace(/\n{3,}/g, '\n\n');
|
|
2163
|
+
const sections = `${buildGitPolicySection(answers.gitPolicy)}\n\n${buildAiCommitPolicySection(answers.aiCommitMarker)}`;
|
|
2164
|
+
const markerMatch = stripped.match(/^## Filling the Templates/m);
|
|
2165
|
+
|
|
2166
|
+
if (markerMatch && typeof markerMatch.index === 'number') {
|
|
2167
|
+
const before = stripped.slice(0, markerMatch.index).replace(/\s*$/, '\n\n');
|
|
2168
|
+
const after = stripped.slice(markerMatch.index).replace(/^\s*/, '');
|
|
2169
|
+
return `${before}${sections}\n\n${after}`;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
return `${stripped.replace(/\s*$/, '\n\n')}${sections}\n`;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
1993
2175
|
function buildProseLanguageSection(proseLanguage) {
|
|
1994
2176
|
return `## Prose Language
|
|
1995
2177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dflow-sdd-ddd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Spec-first SDD/DDD workflow kit for AI-assisted development",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"test": "node test/smoke.mjs"
|
|
43
43
|
},
|
|
44
|
-
"license": "
|
|
44
|
+
"license": "AGPL-3.0-or-later",
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
}
|