dflow-sdd-ddd 0.1.0 → 0.1.1

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.
Files changed (42) hide show
  1. package/README.md +136 -160
  2. package/bin/dflow.js +37 -8
  3. package/docs/why-ddd-for-ai.md +35 -0
  4. package/lib/init.js +347 -65
  5. package/package.json +9 -7
  6. package/templates/brownfield/scaffolding/AI-AGENT-GUIDE.md +64 -0
  7. package/templates/{webforms → brownfield}/scaffolding/CLAUDE-md-snippet.md +7 -9
  8. package/templates/{webforms → brownfield}/scaffolding/Git-principles-gitflow.md +1 -1
  9. package/templates/{webforms → brownfield}/scaffolding/Git-principles-trunk.md +1 -1
  10. package/templates/{webforms → brownfield}/scaffolding/_conventions.md +1 -1
  11. package/templates/{webforms → brownfield}/scaffolding/_overview.md +2 -2
  12. package/templates/{webforms → brownfield}/templates/context-map.md +1 -1
  13. package/templates/{webforms → brownfield}/templates/glossary.md +1 -1
  14. package/templates/{webforms → brownfield}/templates/models.md +1 -1
  15. package/templates/{webforms → brownfield}/templates/phase-spec.md +1 -1
  16. package/templates/{webforms → brownfield}/templates/rules.md +1 -1
  17. package/templates/{webforms → brownfield}/templates/tech-debt.md +1 -1
  18. package/templates/greenfield/scaffolding/AI-AGENT-GUIDE.md +64 -0
  19. package/templates/{core → greenfield}/scaffolding/CLAUDE-md-snippet.md +14 -14
  20. package/templates/{core → greenfield}/scaffolding/Git-principles-gitflow.md +1 -1
  21. package/templates/{core → greenfield}/scaffolding/Git-principles-trunk.md +1 -1
  22. package/templates/{core → greenfield}/scaffolding/_conventions.md +1 -1
  23. package/templates/{core → greenfield}/scaffolding/_overview.md +2 -2
  24. package/templates/{core → greenfield}/scaffolding/architecture-decisions-README.md +1 -1
  25. package/templates/{core → greenfield}/templates/context-map.md +1 -1
  26. package/templates/{core → greenfield}/templates/events.md +1 -1
  27. package/templates/{core → greenfield}/templates/glossary.md +1 -1
  28. package/templates/{core → greenfield}/templates/models.md +1 -1
  29. package/templates/{core → greenfield}/templates/phase-spec.md +1 -1
  30. package/templates/{core → greenfield}/templates/rules.md +1 -1
  31. package/templates/{core → greenfield}/templates/tech-debt.md +1 -1
  32. /package/templates/{webforms → brownfield}/templates/CLAUDE.md +0 -0
  33. /package/templates/{webforms → brownfield}/templates/_index.md +0 -0
  34. /package/templates/{webforms → brownfield}/templates/behavior.md +0 -0
  35. /package/templates/{webforms → brownfield}/templates/context-definition.md +0 -0
  36. /package/templates/{webforms → brownfield}/templates/lightweight-spec.md +0 -0
  37. /package/templates/{core → greenfield}/templates/CLAUDE.md +0 -0
  38. /package/templates/{core → greenfield}/templates/_index.md +0 -0
  39. /package/templates/{core → greenfield}/templates/aggregate-design.md +0 -0
  40. /package/templates/{core → greenfield}/templates/behavior.md +0 -0
  41. /package/templates/{core → greenfield}/templates/context-definition.md +0 -0
  42. /package/templates/{core → greenfield}/templates/lightweight-spec.md +0 -0
package/lib/init.js CHANGED
@@ -37,19 +37,6 @@ const PROJECT_TYPE_OPTIONS = [
37
37
  }
38
38
  ];
39
39
 
40
- const EDITION_OPTIONS = [
41
- {
42
- key: 'core',
43
- label: 'ASP.NET Core - Clean Architecture + DDD',
44
- aliases: ['core', 'asp.net core', 'aspnet core']
45
- },
46
- {
47
- key: 'webforms',
48
- label: 'ASP.NET WebForms - progressive domain extraction',
49
- aliases: ['webforms', 'asp.net webforms', 'aspnet webforms']
50
- }
51
- ];
52
-
53
40
  const PROSE_LANGUAGE_OPTIONS = [
54
41
  {
55
42
  key: 'zh-TW',
@@ -88,11 +75,29 @@ const OPTIONAL_FILE_OPTIONS = [
88
75
  key: 'git-flow',
89
76
  label: 'Git principles - Git Flow',
90
77
  aliases: ['git principles - git flow', 'git flow', 'gitflow']
78
+ }
79
+ ];
80
+
81
+ const AI_AGENT_OPTIONS = [
82
+ {
83
+ key: 'agents',
84
+ label: 'AGENTS.md - Codex / Copilot coding agent',
85
+ aliases: ['agents', 'agents.md', 'codex', 'copilot agent', 'copilot coding agent']
91
86
  },
92
87
  {
93
88
  key: 'claude',
94
- label: 'CLAUDE.md snippet / project AI guide',
89
+ label: 'CLAUDE.md - Claude Code',
95
90
  aliases: ['claude', 'claude.md']
91
+ },
92
+ {
93
+ key: 'gemini',
94
+ label: 'GEMINI.md - Gemini CLI',
95
+ aliases: ['gemini', 'gemini.md']
96
+ },
97
+ {
98
+ key: 'copilot',
99
+ label: '.github/copilot-instructions.md - GitHub Copilot',
100
+ aliases: ['copilot', 'github copilot', 'copilot-instructions', 'copilot-instructions.md']
96
101
  }
97
102
  ];
98
103
 
@@ -211,6 +216,95 @@ async function runInit(options = {}) {
211
216
  }
212
217
  }
213
218
 
219
+ async function runConfigureAgents(options = {}) {
220
+ const cwd = path.resolve(options.cwd || process.cwd());
221
+ const stdin = options.stdin || process.stdin;
222
+ const stdout = options.stdout || process.stdout;
223
+ const stderr = options.stderr || process.stderr;
224
+
225
+ let rl;
226
+
227
+ try {
228
+ rl = readline.createInterface({
229
+ input: stdin,
230
+ output: stdout,
231
+ terminal: Boolean(stdin.isTTY && stdout.isTTY)
232
+ });
233
+ rl._dflowOutput = stdout;
234
+ getLinePrompter(rl);
235
+
236
+ if (compareVersions(process.versions.node, MIN_NODE_VERSION) < 0) {
237
+ throw new InitError(`Dflow configure-agents requires Node.js ${MIN_NODE_VERSION}+.`, 1);
238
+ }
239
+
240
+ await assertWritableProjectRoot(cwd);
241
+ await assertDflowInitialized(cwd);
242
+
243
+ const projectContext = await inferProjectContext(cwd, rl, stdout, stderr);
244
+ const aiAgents = await askAiAgents(rl, stdout, stderr);
245
+
246
+ if (aiAgents.length === 0) {
247
+ throw new UserAbort('No AI agents selected. Nothing changed.');
248
+ }
249
+
250
+ const plan = await buildConfigureAgentsPlan(cwd, {
251
+ ...projectContext,
252
+ aiAgents
253
+ });
254
+
255
+ renderPreview(stdout, plan, []);
256
+ const confirmed = await askConfirmation(rl, 'Create these files? (y/N) ');
257
+
258
+ if (!confirmed) {
259
+ throw new UserAbort();
260
+ }
261
+
262
+ rl.close();
263
+ rl = undefined;
264
+
265
+ const result = await writeFilePlan(cwd, plan);
266
+ result.warnings.push(...collectUnresolvedPlaceholderWarnings(plan, result.created));
267
+
268
+ printResultReport(stdout, result, plan.deferred);
269
+ printConfigureAgentsNextSteps(stdout);
270
+ return 0;
271
+ } catch (error) {
272
+ if (rl) {
273
+ rl.close();
274
+ }
275
+
276
+ if (error instanceof UserAbort) {
277
+ stdout.write(`${error.message}\n`);
278
+ return error.exitCode;
279
+ }
280
+
281
+ if (error instanceof WritePhaseError) {
282
+ stderr.write(`${error.message}\n`);
283
+ stderr.write('Files already created were kept; clean up partial output manually if needed.\n');
284
+ if (error.result) {
285
+ printResultReport(stdout, error.result, []);
286
+ }
287
+ return error.exitCode;
288
+ }
289
+
290
+ if (error instanceof InitError) {
291
+ stderr.write(`${error.message}\n`);
292
+ return error.exitCode;
293
+ }
294
+
295
+ stderr.write(`${error && error.message ? error.message : error}\n`);
296
+ return 1;
297
+ }
298
+ }
299
+
300
+ async function assertDflowInitialized(cwd) {
301
+ const dflowSpecsPath = path.join(cwd, 'dflow', 'specs');
302
+
303
+ if (!(await pathExists(dflowSpecsPath)) || !(await containsInitializedContent(dflowSpecsPath))) {
304
+ throw new InitError('Dflow is not initialized in this project. Run `dflow init` first.');
305
+ }
306
+ }
307
+
214
308
  async function runPreflight(cwd) {
215
309
  const warnings = [];
216
310
  const dflowSpecsPath = path.join(cwd, 'dflow', 'specs');
@@ -309,16 +403,16 @@ async function detectProjectSignals(cwd) {
309
403
  }
310
404
  }
311
405
 
312
- let editionHint = null;
406
+ let trackHint = null;
313
407
  if (coreSignal && !webFormsSignal) {
314
- editionHint = 'core';
408
+ trackHint = 'greenfield';
315
409
  } else if (webFormsSignal && !coreSignal) {
316
- editionHint = 'webforms';
410
+ trackHint = 'brownfield';
317
411
  }
318
412
 
319
413
  return {
320
414
  hasSourceTree: hasSourceTree || relNames.has('src'),
321
- editionHint
415
+ trackHint
322
416
  };
323
417
  }
324
418
 
@@ -369,9 +463,9 @@ function buildDetectionWarnings(answers, detection) {
369
463
  warnings.push('Warning: source-tree signals already exist, but project type is Greenfield. Continuing with your selected project type.');
370
464
  }
371
465
 
372
- if (detection.editionHint && answers.edition !== detection.editionHint) {
466
+ if (detection.trackHint && answers.projectType !== detection.trackHint) {
373
467
  warnings.push(
374
- `Warning: project signals look like ${formatEdition(detection.editionHint)}, but selected edition is ${formatEdition(answers.edition)}. Continuing with your selected edition.`
468
+ `Warning: project signals look like ${formatTrack(detection.trackHint)}, but selected track is ${formatTrack(answers.projectType)}. Continuing with your selected track.`
375
469
  );
376
470
  }
377
471
 
@@ -387,15 +481,8 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
387
481
  defaultKey: projectTypeDefault
388
482
  });
389
483
 
390
- const edition = await askSelect(rl, stdout, stderr, {
391
- id: 'Q2',
392
- question: 'Which Dflow edition should initialize this project?',
393
- options: EDITION_OPTIONS,
394
- defaultKey: detection.editionHint
395
- });
396
-
397
484
  const techStackSummary = await askText(rl, stderr, {
398
- id: 'Q3',
485
+ id: 'Q2',
399
486
  question: 'Confirm the main tech stack details for placeholders.',
400
487
  required: true,
401
488
  maxLength: 1000,
@@ -403,7 +490,7 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
403
490
  });
404
491
 
405
492
  const migrationContext = await askText(rl, stderr, {
406
- id: 'Q4',
493
+ id: 'Q3',
407
494
  question: 'Is there migration or legacy context Dflow should note?',
408
495
  required: false,
409
496
  maxLength: 1000,
@@ -411,7 +498,7 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
411
498
  });
412
499
 
413
500
  const proseLanguageSelection = await askSelect(rl, stdout, stderr, {
414
- id: 'Q5',
501
+ id: 'Q4',
415
502
  question: 'Project prose language for generated spec content?',
416
503
  options: PROSE_LANGUAGE_OPTIONS,
417
504
  defaultKey: null
@@ -423,17 +510,76 @@ async function promptForAnswers(rl, stdout, stderr, detection) {
423
510
  }
424
511
 
425
512
  const optionalFiles = await askOptionalFiles(rl, stdout, stderr);
513
+ const aiAgents = await askAiAgents(rl, stdout, stderr);
426
514
 
427
515
  return {
428
516
  projectType,
429
- edition,
517
+ edition: projectType,
430
518
  techStackSummary,
431
519
  migrationContext,
432
520
  proseLanguage,
433
- optionalFiles
521
+ optionalFiles,
522
+ aiAgents
434
523
  };
435
524
  }
436
525
 
526
+ async function inferProjectContext(cwd, rl, stdout, stderr) {
527
+ let edition = await inferExistingEdition(cwd);
528
+
529
+ if (!edition) {
530
+ stderr.write('Could not infer the Dflow track from dflow/specs/. Please choose it explicitly.\n');
531
+ edition = await askSelect(rl, stdout, stderr, {
532
+ id: 'track',
533
+ question: 'Which Dflow track is this project using?',
534
+ options: PROJECT_TYPE_OPTIONS,
535
+ defaultKey: null
536
+ });
537
+ }
538
+
539
+ return {
540
+ projectType: edition,
541
+ edition,
542
+ techStackSummary: await inferTechStackSummary(cwd),
543
+ migrationContext: await inferMigrationContext(cwd),
544
+ proseLanguage: await inferProseLanguage(cwd),
545
+ optionalFiles: []
546
+ };
547
+ }
548
+
549
+ async function inferExistingEdition(cwd) {
550
+ if (await pathExists(path.join(cwd, 'dflow/specs/architecture/tech-debt.md'))) {
551
+ return 'greenfield';
552
+ }
553
+ if (await pathExists(path.join(cwd, 'dflow/specs/migration/tech-debt.md'))) {
554
+ return 'brownfield';
555
+ }
556
+ if (await pathExists(path.join(cwd, 'dflow/specs/domain/context-map.md'))) {
557
+ return 'greenfield';
558
+ }
559
+ return null;
560
+ }
561
+
562
+ async function inferProseLanguage(cwd) {
563
+ const conventionsPath = path.join(cwd, 'dflow/specs/shared/_conventions.md');
564
+ const content = await fs.readFile(conventionsPath, 'utf8').catch(() => '');
565
+ const match = content.match(/Project prose language:\s*`([^`]+)`/);
566
+ return match ? match[1] : 'unknown';
567
+ }
568
+
569
+ async function inferTechStackSummary(cwd) {
570
+ const overviewPath = path.join(cwd, 'dflow/specs/shared/_overview.md');
571
+ const content = await fs.readFile(overviewPath, 'utf8').catch(() => '');
572
+ const match = content.match(/\|\s*Tech stack\s*\|\s*([^|\n]+?)\s*\|/i);
573
+ return match ? match[1].trim() : 'unknown';
574
+ }
575
+
576
+ async function inferMigrationContext(cwd) {
577
+ const overviewPath = path.join(cwd, 'dflow/specs/shared/_overview.md');
578
+ const content = await fs.readFile(overviewPath, 'utf8').catch(() => '');
579
+ const match = content.match(/\|\s*Migration \/ legacy context\s*\|\s*([^|\n]+?)\s*\|/i);
580
+ return match ? match[1].trim() : 'none';
581
+ }
582
+
437
583
  async function askSelect(rl, stdout, stderr, config) {
438
584
  let failedAttempts = 0;
439
585
 
@@ -506,7 +652,7 @@ async function askCustomProseLanguage(rl, stderr) {
506
652
 
507
653
  failedAttempts += 1;
508
654
  if (failedAttempts >= 3) {
509
- throw new InitError('Too many invalid attempts for Q5a. Dflow init aborted.');
655
+ throw new InitError('Too many invalid attempts for Q4a. Dflow init aborted.');
510
656
  }
511
657
  stderr.write(`${validation.message} (${3 - failedAttempts} attempts left)\n`);
512
658
  }
@@ -528,7 +674,7 @@ async function askOptionalFiles(rl, stdout, stderr) {
528
674
  if (!parsed.valid) {
529
675
  failedAttempts += 1;
530
676
  if (failedAttempts >= 3) {
531
- throw new InitError('Too many invalid attempts for Q6. Dflow init aborted.');
677
+ throw new InitError('Too many invalid attempts for Q5. Dflow init aborted.');
532
678
  }
533
679
  stderr.write(`${parsed.message} (${3 - failedAttempts} attempts left)\n`);
534
680
  continue;
@@ -549,6 +695,31 @@ async function askOptionalFiles(rl, stdout, stderr) {
549
695
  }
550
696
  }
551
697
 
698
+ async function askAiAgents(rl, stdout, stderr) {
699
+ let failedAttempts = 0;
700
+
701
+ while (true) {
702
+ stdout.write('\nWhich AI coding agents should Dflow configure?\n');
703
+ AI_AGENT_OPTIONS.forEach((option, index) => {
704
+ stdout.write(` ${index + 1}. ${option.label}\n`);
705
+ });
706
+
707
+ const answer = await askLine(rl, 'Enter comma-separated choices or "none" (default: none): ');
708
+ const parsed = parseMultiselectAnswer(answer || 'none', AI_AGENT_OPTIONS, []);
709
+
710
+ if (!parsed.valid) {
711
+ failedAttempts += 1;
712
+ if (failedAttempts >= 3) {
713
+ throw new InitError('Too many invalid attempts for Q6. Dflow init aborted.');
714
+ }
715
+ stderr.write(`${parsed.message} (${3 - failedAttempts} attempts left)\n`);
716
+ continue;
717
+ }
718
+
719
+ return parsed.values;
720
+ }
721
+ }
722
+
552
723
  function askLine(rl, prompt) {
553
724
  return getLinePrompter(rl).ask(prompt);
554
725
  }
@@ -669,7 +840,7 @@ function parseMultiselectAnswer(answer, options, defaultKeys) {
669
840
  for (const token of tokens) {
670
841
  const selectedKey = parseSelectAnswer(token, options, null);
671
842
  if (!selectedKey) {
672
- return { valid: false, message: `Invalid optional file selection: ${token}` };
843
+ return { valid: false, message: `Invalid selection: ${token}` };
673
844
  }
674
845
  if (!selected.includes(selectedKey)) {
675
846
  selected.push(selectedKey);
@@ -713,9 +884,6 @@ async function buildFilePlan(cwd, answers) {
713
884
 
714
885
  const addTemplate = async (relativePath, sourceRel, notes, options = {}) => {
715
886
  let content = await readPackagedTemplate(answers.edition, sourceRel);
716
- if (options.extractClaudeSnippetBody) {
717
- content = extractClaudeSnippetBody(content, sourceRel);
718
- }
719
887
  content = substitutePlaceholders(content, substitution);
720
888
  if (options.injectProseLanguage) {
721
889
  content = ensureProseLanguageSection(content, answers.proseLanguage);
@@ -737,7 +905,7 @@ async function buildFilePlan(cwd, answers) {
737
905
  });
738
906
  await addTemplate('dflow/specs/domain/glossary.md', 'templates/glossary.md', 'mandatory');
739
907
 
740
- if (answers.edition === 'core') {
908
+ if (answers.edition === 'greenfield') {
741
909
  await addTemplate('dflow/specs/domain/context-map.md', 'templates/context-map.md', 'mandatory');
742
910
  await addTemplate('dflow/specs/architecture/tech-debt.md', 'templates/tech-debt.md', 'mandatory');
743
911
  await addTemplate(
@@ -758,17 +926,57 @@ async function buildFilePlan(cwd, answers) {
758
926
  if (answers.optionalFiles.includes('git-flow')) {
759
927
  await addTemplate('dflow/specs/shared/Git-principles-gitflow.md', 'scaffolding/Git-principles-gitflow.md', 'selected');
760
928
  }
761
- if (answers.optionalFiles.includes('claude')) {
762
- const rootClaudePath = path.join(cwd, 'CLAUDE.md');
763
- if (await pathExists(rootClaudePath)) {
764
- await addTemplate('dflow/specs/shared/CLAUDE-md-snippet.md', 'scaffolding/CLAUDE-md-snippet.md', 'selected, root CLAUDE.md already exists');
765
- } else {
766
- await addTemplate('CLAUDE.md', 'scaffolding/CLAUDE-md-snippet.md', 'selected, snippet body only', {
767
- extractClaudeSnippetBody: true
768
- });
929
+
930
+ if (answers.aiAgents.length > 0) {
931
+ await addTemplate('dflow/specs/shared/AI-AGENT-GUIDE.md', 'scaffolding/AI-AGENT-GUIDE.md', 'selected, canonical AI agent guide');
932
+ for (const agent of answers.aiAgents) {
933
+ await addAiAgentShim(cwd, items, agent, substitution);
769
934
  }
770
935
  }
771
936
 
937
+ await finalizePlanItems(cwd, items);
938
+
939
+ return {
940
+ items,
941
+ deferred: buildDeferredItems(answers.edition),
942
+ unresolvedInitPlaceholders: Array.from(substitution.entries())
943
+ .filter(([placeholder, value]) => placeholder === value)
944
+ .map(([placeholder]) => placeholder)
945
+ };
946
+ }
947
+
948
+ async function buildConfigureAgentsPlan(cwd, answers) {
949
+ const substitution = buildSubstitutionMap(cwd, {
950
+ ...answers,
951
+ optionalFiles: answers.optionalFiles || []
952
+ });
953
+ const items = [];
954
+
955
+ let content = await readPackagedTemplate(answers.edition, 'scaffolding/AI-AGENT-GUIDE.md');
956
+ content = substitutePlaceholders(content, substitution);
957
+ items.push({
958
+ relativePath: 'dflow/specs/shared/AI-AGENT-GUIDE.md',
959
+ source: `packaged:${answers.edition}/scaffolding/AI-AGENT-GUIDE.md`,
960
+ notes: 'canonical AI agent guide',
961
+ content
962
+ });
963
+
964
+ for (const agent of answers.aiAgents) {
965
+ await addAiAgentShim(cwd, items, agent, substitution);
966
+ }
967
+
968
+ await finalizePlanItems(cwd, items);
969
+
970
+ return {
971
+ items,
972
+ deferred: [],
973
+ unresolvedInitPlaceholders: Array.from(substitution.entries())
974
+ .filter(([placeholder, value]) => placeholder === value)
975
+ .map(([placeholder]) => placeholder)
976
+ };
977
+ }
978
+
979
+ async function finalizePlanItems(cwd, items) {
772
980
  for (const item of items) {
773
981
  const absolute = path.join(cwd, item.relativePath);
774
982
  item.action = (await pathExists(absolute)) ? 'skip' : 'create';
@@ -777,22 +985,92 @@ async function buildFilePlan(cwd, answers) {
777
985
  }
778
986
  item.size = Buffer.byteLength(item.content, 'utf8');
779
987
  }
988
+ }
780
989
 
781
- return {
782
- items,
783
- deferred: buildDeferredItems(answers.edition),
784
- unresolvedInitPlaceholders: Array.from(substitution.entries())
785
- .filter(([placeholder, value]) => placeholder === value)
786
- .map(([placeholder]) => placeholder)
990
+ async function addAiAgentShim(cwd, items, agent, substitution) {
991
+ const target = getAiAgentTarget(agent);
992
+ const targetPath = path.join(cwd, target.relativePath);
993
+ const targetExists = await pathExists(targetPath);
994
+ const targetConfigured = targetExists && await fileReferencesAiAgentGuide(targetPath);
995
+ const relativePath = targetExists && !targetConfigured ? target.snippetPath : target.relativePath;
996
+ const content = substitutePlaceholders(buildAiAgentShim(target.relativePath), substitution);
997
+ let notes = 'selected, tool-specific shim';
998
+ if (targetConfigured) {
999
+ notes = `selected, ${target.relativePath} already points to AI-AGENT-GUIDE.md`;
1000
+ } else if (targetExists) {
1001
+ notes = `selected, ${target.relativePath} already exists; merge this snippet manually`;
1002
+ }
1003
+
1004
+ items.push({
1005
+ relativePath,
1006
+ source: `generated:${agent}-shim`,
1007
+ notes,
1008
+ content
1009
+ });
1010
+ }
1011
+
1012
+ async function fileReferencesAiAgentGuide(targetPath) {
1013
+ try {
1014
+ const content = await fs.readFile(targetPath, 'utf8');
1015
+ return content.includes('dflow/specs/shared/AI-AGENT-GUIDE.md') ||
1016
+ content.includes('dflow\\specs\\shared\\AI-AGENT-GUIDE.md');
1017
+ } catch {
1018
+ return false;
1019
+ }
1020
+ }
1021
+
1022
+ function getAiAgentTarget(agent) {
1023
+ const targets = {
1024
+ agents: {
1025
+ relativePath: 'AGENTS.md',
1026
+ snippetPath: 'dflow/specs/shared/AGENTS-md-snippet.md'
1027
+ },
1028
+ claude: {
1029
+ relativePath: 'CLAUDE.md',
1030
+ snippetPath: 'dflow/specs/shared/CLAUDE-md-snippet.md'
1031
+ },
1032
+ gemini: {
1033
+ relativePath: 'GEMINI.md',
1034
+ snippetPath: 'dflow/specs/shared/GEMINI-md-snippet.md'
1035
+ },
1036
+ copilot: {
1037
+ relativePath: '.github/copilot-instructions.md',
1038
+ snippetPath: 'dflow/specs/shared/copilot-instructions-snippet.md'
1039
+ }
787
1040
  };
1041
+
1042
+ return targets[agent];
1043
+ }
1044
+
1045
+ function buildAiAgentShim(targetPath) {
1046
+ const title = targetPath === '.github/copilot-instructions.md'
1047
+ ? 'GitHub Copilot Repository Instructions'
1048
+ : `${targetPath} - Dflow Project Instructions`;
1049
+
1050
+ const importHint = targetPath === 'CLAUDE.md' || targetPath === 'GEMINI.md'
1051
+ ? '\nIf your tool supports Markdown imports, the canonical guide is imported below:\n\n@dflow/specs/shared/AI-AGENT-GUIDE.md\n'
1052
+ : '';
1053
+
1054
+ return `# ${title}
1055
+
1056
+ This project uses Dflow for spec-first AI-assisted development.
1057
+
1058
+ Before planning or editing code, read and follow:
1059
+
1060
+ - \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
1061
+
1062
+ Keep tool-specific instruction files small. The Dflow guide above is the
1063
+ single source of truth for project workflow rules, slash-command behavior,
1064
+ spec locations, and SDD/DDD constraints.${importHint}
1065
+ `;
788
1066
  }
789
1067
 
790
1068
  function buildDeferredItems(edition) {
791
1069
  const deferred = [...DEFERRED_COMMON];
792
- if (edition === 'core') {
1070
+ if (edition === 'greenfield') {
793
1071
  deferred.splice(3, 0, {
794
1072
  relativePath: 'dflow/specs/domain/{context}/events.md',
795
- reason: 'Core only, but still needs a real bounded context.'
1073
+ reason: 'Greenfield only, but still needs a real bounded context.'
796
1074
  });
797
1075
  }
798
1076
  return deferred;
@@ -892,14 +1170,6 @@ function extractTestFramework(text) {
892
1170
  return null;
893
1171
  }
894
1172
 
895
- function extractClaudeSnippetBody(content, sourceRel) {
896
- const match = content.match(/## Snippet to merge into `CLAUDE\.md`[\s\S]*?```markdown\r?\n([\s\S]*?)\r?\n```/);
897
- if (!match) {
898
- throw new InitError(`Internal error: CLAUDE.md snippet body not found in packaged template: ${sourceRel}`);
899
- }
900
- return `${match[1].trimEnd()}\n`;
901
- }
902
-
903
1173
  function ensureProseLanguageSection(content, proseLanguage) {
904
1174
  const section = buildProseLanguageSection(proseLanguage);
905
1175
  let stripped = stripProseLanguageSections(content);
@@ -1116,6 +1386,17 @@ Recommended next steps:
1116
1386
  `);
1117
1387
  }
1118
1388
 
1389
+ function printConfigureAgentsNextSteps(stdout) {
1390
+ stdout.write(`
1391
+ Dflow AI agent configuration complete.
1392
+
1393
+ Recommended next steps:
1394
+ - Keep AI-agent-specific root files small.
1395
+ - Put durable workflow changes in dflow/specs/shared/AI-AGENT-GUIDE.md.
1396
+ - If a merge snippet was created, review it and merge the pointer into the existing tool instruction file.
1397
+ `);
1398
+ }
1399
+
1119
1400
  function printList(stdout, values) {
1120
1401
  if (!values || values.length === 0) {
1121
1402
  stdout.write('- (none)\n');
@@ -1186,8 +1467,8 @@ function formatBytes(bytes) {
1186
1467
  return `${(bytes / 1024).toFixed(1)} KB`;
1187
1468
  }
1188
1469
 
1189
- function formatEdition(edition) {
1190
- return edition === 'core' ? 'ASP.NET Core' : 'ASP.NET WebForms';
1470
+ function formatTrack(track) {
1471
+ return track === 'greenfield' ? 'Greenfield' : 'Brownfield';
1191
1472
  }
1192
1473
 
1193
1474
  function escapeTableCell(value) {
@@ -1199,6 +1480,7 @@ function dedupe(values) {
1199
1480
  }
1200
1481
 
1201
1482
  module.exports = {
1483
+ runConfigureAgents,
1202
1484
  runInit,
1203
1485
  validateProseLanguage,
1204
1486
  ensureProseLanguageSection,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dflow-sdd-ddd",
3
- "version": "0.1.0",
4
- "description": "Dflow SDD/DDD project scaffolding CLI",
3
+ "version": "0.1.1",
4
+ "description": "Spec-first SDD/DDD workflow kit for AI-assisted development",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "dflow": "bin/dflow.js",
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "files": [
14
14
  "bin/",
15
+ "docs/",
15
16
  "lib/",
16
17
  "templates/",
17
18
  "README.md"
@@ -21,17 +22,18 @@
21
22
  "ddd",
22
23
  "specification-driven-development",
23
24
  "domain-driven-design",
24
- "codex",
25
- "claude-code"
25
+ "spec-first",
26
+ "ai-assisted-development",
27
+ "ai-coding-agent"
26
28
  ],
27
29
  "repository": {
28
30
  "type": "git",
29
- "url": "git+ssh://git@github.com/weilung/AI-Guided-SDD-DDD-Development-Skills.git"
31
+ "url": "git+ssh://git@github.com/weilung/dflow-sdd-ddd.git"
30
32
  },
31
33
  "bugs": {
32
- "url": "https://github.com/weilung/AI-Guided-SDD-DDD-Development-Skills/issues"
34
+ "url": "https://github.com/weilung/dflow-sdd-ddd/issues"
33
35
  },
34
- "homepage": "https://github.com/weilung/AI-Guided-SDD-DDD-Development-Skills#readme",
36
+ "homepage": "https://github.com/weilung/dflow-sdd-ddd#readme",
35
37
  "scripts": {
36
38
  "test": "node test/smoke.mjs"
37
39
  },
@@ -0,0 +1,64 @@
1
+ # Dflow AI Agent Guide
2
+
3
+ This project uses Dflow for spec-first AI-assisted development.
4
+
5
+ ## Project Context
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | Project | {System Name} |
10
+ | Dflow track | {edition} |
11
+ | Project type | {project-type} |
12
+ | Tech stack | {tech-stack-summary} |
13
+ | Migration / legacy context | {migration-context} |
14
+ | Prose language | {prose-language} |
15
+
16
+ ## Before Editing Code
17
+
18
+ Do not jump from a request directly to code. First identify the matching
19
+ Dflow workflow and confirm the intended path with the developer.
20
+
21
+ Use these workflow entry points as plain chat instructions if slash commands
22
+ are not available in the current AI tool:
23
+
24
+ | Workflow | Use when |
25
+ |---|---|
26
+ | `/dflow:new-feature` | A new user-visible capability or business behavior is requested. |
27
+ | `/dflow:modify-existing` | Existing behavior needs to change. |
28
+ | `/dflow:bug-fix` | A defect can be described with expected vs actual behavior. |
29
+ | `/dflow:new-phase` | An active feature needs another implementation slice. |
30
+ | `/dflow:finish-feature` | Implementation is complete and needs drift closure. |
31
+ | `/dflow:verify` | Specs, domain docs, implementation, and tests need consistency checks. |
32
+ | `/dflow:pr-review` | A change is ready for SDD/DDD review. |
33
+
34
+ ## Source of Truth
35
+
36
+ Dflow-owned project documents live under `dflow/specs/`.
37
+
38
+ | Area | Path |
39
+ |---|---|
40
+ | Shared conventions | `dflow/specs/shared/_conventions.md` |
41
+ | System overview | `dflow/specs/shared/_overview.md` |
42
+ | Domain glossary | `dflow/specs/domain/glossary.md` |
43
+ | Context map | `dflow/specs/domain/context-map.md` |
44
+ | Active feature specs | `dflow/specs/features/active/` |
45
+ | Completed feature snapshots | `dflow/specs/features/completed/` |
46
+ | Technical debt | `dflow/specs/architecture/tech-debt.md` or `dflow/specs/migration/tech-debt.md` |
47
+
48
+ ## Core Rules
49
+
50
+ 1. Spec before code: meaningful behavior changes need a spec or lightweight bug spec before implementation.
51
+ 2. Keep domain language explicit: update glossary, rules, models, and behavior snapshots when domain meaning changes.
52
+ 3. Keep phase delta, feature snapshot, and system state separate.
53
+ 4. Check drift before calling work complete.
54
+ 5. Follow `dflow/specs/shared/_conventions.md`, especially `## Prose Language`.
55
+
56
+ ## Tool-Specific Notes
57
+
58
+ This file is the canonical Dflow guide. Root-level files such as
59
+ `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and `.github/copilot-instructions.md`
60
+ should stay thin and point back here.
61
+
62
+ If a tool does not support Dflow slash commands, treat the command names as
63
+ plain workflow names and execute the matching process from the Dflow skill
64
+ source in this repository.