@ryuenn3123/agentic-senior-core 2.0.25 → 2.0.26

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.
@@ -119,7 +119,17 @@ export async function runOptimizeCommand(targetDirectoryArgument, optimizeOption
119
119
  ? onboardingReport.selectedProfile
120
120
  : 'balanced';
121
121
  const selectedStackFileName = normalizeMarkdownFileName(onboardingReport.selectedStack, 'typescript.md');
122
+ const selectedAdditionalStackFileNames = Array.isArray(onboardingReport.selectedAdditionalStacks)
123
+ ? onboardingReport.selectedAdditionalStacks
124
+ .map((stackFileName) => normalizeMarkdownFileName(stackFileName, ''))
125
+ .filter((stackFileName) => stackFileName && stackFileName !== selectedStackFileName)
126
+ : [];
122
127
  const selectedBlueprintFileName = normalizeMarkdownFileName(onboardingReport.selectedBlueprint, 'api-nextjs.md');
128
+ const selectedAdditionalBlueprintFileNames = Array.isArray(onboardingReport.selectedAdditionalBlueprints)
129
+ ? onboardingReport.selectedAdditionalBlueprints
130
+ .map((blueprintFileName) => normalizeMarkdownFileName(blueprintFileName, ''))
131
+ .filter((blueprintFileName) => blueprintFileName && blueprintFileName !== selectedBlueprintFileName)
132
+ : [];
123
133
  const includeCiGuardrails = typeof onboardingReport.ciGuardrailsEnabled === 'boolean'
124
134
  ? onboardingReport.ciGuardrailsEnabled
125
135
  : true;
@@ -128,7 +138,9 @@ export async function runOptimizeCommand(targetDirectoryArgument, optimizeOption
128
138
  targetDirectoryPath: resolvedTargetDirectoryPath,
129
139
  selectedProfileName,
130
140
  selectedStackFileName,
141
+ selectedAdditionalStackFileNames,
131
142
  selectedBlueprintFileName,
143
+ selectedAdditionalBlueprintFileNames,
132
144
  includeCiGuardrails,
133
145
  });
134
146
 
@@ -119,17 +119,35 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
119
119
  ? existingOnboardingReport.selectedStack
120
120
  : projectDetection.recommendedStackFileName || 'typescript.md';
121
121
 
122
+ const selectedAdditionalStackFileNames = Array.isArray(existingOnboardingReport?.selectedAdditionalStacks)
123
+ ? Array.from(new Set(existingOnboardingReport.selectedAdditionalStacks.filter(
124
+ (stackFileName) => stackFileNames.includes(stackFileName) && stackFileName !== selectedStackFileName
125
+ )))
126
+ : Array.from(new Set((projectDetection.secondaryStackFileNames || []).filter(
127
+ (stackFileName) => stackFileNames.includes(stackFileName) && stackFileName !== selectedStackFileName
128
+ )));
129
+
122
130
  const selectedBlueprintFileName = blueprintFileNames.includes(existingOnboardingReport?.selectedBlueprint)
123
131
  ? existingOnboardingReport.selectedBlueprint
124
132
  : BLUEPRINT_RECOMMENDATIONS[selectedStackFileName] || 'api-nextjs.md';
125
133
 
134
+ const selectedAdditionalBlueprintFileNames = Array.isArray(existingOnboardingReport?.selectedAdditionalBlueprints)
135
+ ? Array.from(new Set(existingOnboardingReport.selectedAdditionalBlueprints.filter(
136
+ (blueprintFileName) => blueprintFileNames.includes(blueprintFileName) && blueprintFileName !== selectedBlueprintFileName
137
+ )))
138
+ : Array.from(new Set(selectedAdditionalStackFileNames
139
+ .map((stackFileName) => BLUEPRINT_RECOMMENDATIONS[stackFileName] || null)
140
+ .filter((blueprintFileName) => blueprintFileName && blueprintFileName !== selectedBlueprintFileName)));
141
+
126
142
  const includeCiGuardrails = typeof existingOnboardingReport?.ciGuardrailsEnabled === 'boolean'
127
143
  ? existingOnboardingReport.ciGuardrailsEnabled
128
144
  : true;
129
145
 
130
146
  const selectedSkillDomainNames = inferSkillDomainNamesFromSelection(
131
147
  selectedStackFileName,
132
- selectedBlueprintFileName
148
+ selectedBlueprintFileName,
149
+ selectedAdditionalStackFileNames,
150
+ selectedAdditionalBlueprintFileNames
133
151
  );
134
152
  const compatibilityWarnings = await evaluateSkillDomainCompatibility(
135
153
  resolvedTargetDirectoryPath,
@@ -154,7 +172,9 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
154
172
  targetDirectoryPath: resolvedTargetDirectoryPath,
155
173
  selectedProfileName,
156
174
  selectedStackFileName,
175
+ selectedAdditionalStackFileNames,
157
176
  selectedBlueprintFileName,
177
+ selectedAdditionalBlueprintFileNames,
158
178
  includeCiGuardrails,
159
179
  });
160
180
 
@@ -166,7 +186,13 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
166
186
  console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
167
187
  console.log(`- Profile: ${toTitleCase(selectedProfileName)}`);
168
188
  console.log(`- Stack: ${toTitleCase(selectedStackFileName)}`);
189
+ if (selectedAdditionalStackFileNames.length > 0) {
190
+ console.log(`- Additional stacks: ${selectedAdditionalStackFileNames.map((stackFileName) => toTitleCase(stackFileName)).join(', ')}`);
191
+ }
169
192
  console.log(`- Blueprint: ${toTitleCase(selectedBlueprintFileName)}`);
193
+ if (selectedAdditionalBlueprintFileNames.length > 0) {
194
+ console.log(`- Additional blueprints: ${selectedAdditionalBlueprintFileNames.map((blueprintFileName) => toTitleCase(blueprintFileName)).join(', ')}`);
195
+ }
170
196
  console.log(`- CI/CD guardrails: ${includeCiGuardrails ? 'enabled' : 'disabled'}`);
171
197
  console.log(`- Existing rules lines: ${currentRuleLineCount}`);
172
198
  console.log(`- Planned rules lines: ${plannedRuleLineCount}`);
@@ -213,12 +239,15 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
213
239
  selectedProfileName,
214
240
  selectedProfilePack: existingOnboardingReport?.selectedProfilePack || null,
215
241
  selectedStackFileName,
242
+ selectedAdditionalStackFileNames,
216
243
  selectedBlueprintFileName,
244
+ selectedAdditionalBlueprintFileNames,
217
245
  includeCiGuardrails,
218
246
  setupDurationMs,
219
247
  projectDetection,
220
248
  selectedSkillDomains: selectedSkillDomainNames,
221
249
  compatibilityWarnings,
250
+ runtimeEnvironment: existingOnboardingReport?.runtimeEnvironment || null,
222
251
  operationMode: 'upgrade',
223
252
  });
224
253
 
@@ -9,6 +9,7 @@ import {
9
9
  CLI_VERSION,
10
10
  POLICY_FILE_NAME,
11
11
  SKILL_PLATFORM_INDEX_PATH,
12
+ BLUEPRINT_RECOMMENDATIONS,
12
13
  } from './constants.mjs';
13
14
 
14
15
  import {
@@ -38,12 +39,15 @@ export async function writeOnboardingReport({
38
39
  selectedProfilePack,
39
40
  selectedPreset,
40
41
  selectedStackFileName,
42
+ selectedAdditionalStackFileNames = [],
41
43
  selectedBlueprintFileName,
44
+ selectedAdditionalBlueprintFileNames = [],
42
45
  includeCiGuardrails,
43
46
  setupDurationMs,
44
47
  projectDetection,
45
48
  selectedSkillDomains = [],
46
49
  compatibilityWarnings = [],
50
+ runtimeEnvironment = null,
47
51
  operationMode = 'init',
48
52
  }) {
49
53
  const onboardingReportPath = path.join(targetDirectoryPath, '.agent-context', 'state', 'onboarding-report.json');
@@ -60,14 +64,23 @@ export async function writeOnboardingReport({
60
64
  : null,
61
65
  selectedPreset,
62
66
  selectedStack: selectedStackFileName,
67
+ selectedAdditionalStacks: selectedAdditionalStackFileNames,
63
68
  selectedBlueprint: selectedBlueprintFileName,
69
+ selectedAdditionalBlueprints: selectedAdditionalBlueprintFileNames,
64
70
  ciGuardrailsEnabled: includeCiGuardrails,
65
71
  setupDurationMs,
66
72
  selectedSkillDomains,
67
73
  compatibilityWarnings,
74
+ runtimeEnvironment,
68
75
  autoDetection: {
69
76
  recommendedStack: projectDetection.recommendedStackFileName,
77
+ recommendedAdditionalStacks: projectDetection.secondaryStackFileNames || [],
70
78
  recommendedBlueprint: projectDetection.recommendedBlueprintFileName,
79
+ recommendedAdditionalBlueprints: Array.isArray(projectDetection.secondaryStackFileNames)
80
+ ? projectDetection.secondaryStackFileNames
81
+ .map((secondaryStackFileName) => BLUEPRINT_RECOMMENDATIONS[secondaryStackFileName] || null)
82
+ .filter(Boolean)
83
+ : [],
71
84
  confidenceLabel: projectDetection.confidenceLabel,
72
85
  confidenceScore: projectDetection.confidenceScore,
73
86
  confidenceGap: projectDetection.confidenceGap,
@@ -94,7 +107,9 @@ export async function buildCompiledRulesContent({
94
107
  targetDirectoryPath,
95
108
  selectedProfileName,
96
109
  selectedStackFileName,
110
+ selectedAdditionalStackFileNames = [],
97
111
  selectedBlueprintFileName,
112
+ selectedAdditionalBlueprintFileNames = [],
98
113
  includeCiGuardrails,
99
114
  }) {
100
115
  const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
@@ -102,7 +117,20 @@ export async function buildCompiledRulesContent({
102
117
  const selectedStacksDirectoryPath = path.join(resolvedTargetDirectoryPath, '.agent-context', 'stacks');
103
118
  const selectedBlueprintsDirectoryPath = path.join(resolvedTargetDirectoryPath, '.agent-context', 'blueprints');
104
119
  const skillPlatformIndex = JSON.parse(await fs.readFile(SKILL_PLATFORM_INDEX_PATH, 'utf8'));
105
- const selectedSkillDomainNames = inferSkillDomainNamesFromSelection(selectedStackFileName, selectedBlueprintFileName);
120
+ const normalizedAdditionalStackFileNames = Array.isArray(selectedAdditionalStackFileNames)
121
+ ? Array.from(new Set(selectedAdditionalStackFileNames.filter((stackFileName) => stackFileName && stackFileName !== selectedStackFileName)))
122
+ : [];
123
+ const normalizedAdditionalBlueprintFileNames = Array.isArray(selectedAdditionalBlueprintFileNames)
124
+ ? Array.from(new Set(selectedAdditionalBlueprintFileNames.filter(
125
+ (blueprintFileName) => blueprintFileName && blueprintFileName !== selectedBlueprintFileName
126
+ )))
127
+ : [];
128
+ const selectedSkillDomainNames = inferSkillDomainNamesFromSelection(
129
+ selectedStackFileName,
130
+ selectedBlueprintFileName,
131
+ normalizedAdditionalStackFileNames,
132
+ normalizedAdditionalBlueprintFileNames
133
+ );
106
134
 
107
135
  const universalRuleFileNames = await collectFileNames(selectedRulesDirectoryPath);
108
136
  const contextBlocks = [];
@@ -166,6 +194,16 @@ export async function buildCompiledRulesContent({
166
194
  ].join('\n')
167
195
  );
168
196
 
197
+ if (normalizedAdditionalStackFileNames.length > 0) {
198
+ contextBlocks.push(
199
+ [
200
+ '## LAYER 2B: ADDITIONAL STACK PROFILES',
201
+ 'This project uses multiple stacks. Load all additional stack profiles below:',
202
+ ...normalizedAdditionalStackFileNames.map((stackFileName, stackIndex) => `${stackIndex + 1}. .agent-context/stacks/${stackFileName}`),
203
+ ].join('\n')
204
+ );
205
+ }
206
+
169
207
  const blueprintFilePath = path.join(selectedBlueprintsDirectoryPath, selectedBlueprintFileName);
170
208
  const blueprintContent = await fs.readFile(blueprintFilePath, 'utf8');
171
209
  const blueprintSummary = firstMarkdownHeading(blueprintContent, selectedBlueprintFileName);
@@ -178,6 +216,18 @@ export async function buildCompiledRulesContent({
178
216
  ].join('\n')
179
217
  );
180
218
 
219
+ if (normalizedAdditionalBlueprintFileNames.length > 0) {
220
+ contextBlocks.push(
221
+ [
222
+ '## LAYER 3A: ADDITIONAL BLUEPRINT PROFILES',
223
+ 'This project uses multiple architecture blueprints. Load all additional blueprint profiles below:',
224
+ ...normalizedAdditionalBlueprintFileNames.map(
225
+ (blueprintFileName, blueprintIndex) => `${blueprintIndex + 1}. .agent-context/blueprints/${blueprintFileName}`
226
+ ),
227
+ ].join('\n')
228
+ );
229
+ }
230
+
181
231
  if (includeCiGuardrails) {
182
232
  contextBlocks.push(
183
233
  [
@@ -300,7 +350,9 @@ export async function compileDynamicContext({
300
350
  targetDirectoryPath,
301
351
  selectedProfileName,
302
352
  selectedStackFileName,
353
+ selectedAdditionalStackFileNames = [],
303
354
  selectedBlueprintFileName,
355
+ selectedAdditionalBlueprintFileNames = [],
304
356
  includeCiGuardrails,
305
357
  }) {
306
358
  const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
@@ -308,7 +360,9 @@ export async function compileDynamicContext({
308
360
  targetDirectoryPath: resolvedTargetDirectoryPath,
309
361
  selectedProfileName,
310
362
  selectedStackFileName,
363
+ selectedAdditionalStackFileNames,
311
364
  selectedBlueprintFileName,
365
+ selectedAdditionalBlueprintFileNames,
312
366
  includeCiGuardrails,
313
367
  });
314
368
 
@@ -160,6 +160,89 @@ export const PROFILE_PRESETS = {
160
160
  },
161
161
  };
162
162
 
163
+ export const PROJECT_SCOPE_CHOICES = [
164
+ {
165
+ key: 'api-service',
166
+ label: 'API service',
167
+ },
168
+ {
169
+ key: 'web-application',
170
+ label: 'Web application',
171
+ },
172
+ {
173
+ key: 'mobile-app',
174
+ label: 'Mobile app',
175
+ },
176
+ {
177
+ key: 'cli-tool',
178
+ label: 'CLI tool',
179
+ },
180
+ {
181
+ key: 'library-sdk',
182
+ label: 'Library / SDK',
183
+ },
184
+ {
185
+ key: 'other',
186
+ label: 'Other',
187
+ },
188
+ ];
189
+
190
+ export const PROJECT_SCOPE_STACK_FILTERS = {
191
+ 'api-service': ['typescript.md', 'python.md', 'go.md', 'java.md', 'php.md', 'csharp.md', 'ruby.md', 'rust.md'],
192
+ 'web-application': ['typescript.md', 'python.md', 'go.md', 'java.md', 'php.md', 'csharp.md', 'ruby.md', 'rust.md'],
193
+ 'mobile-app': ['react-native.md', 'flutter.md'],
194
+ 'cli-tool': ['typescript.md', 'python.md', 'go.md', 'rust.md', 'ruby.md', 'java.md', 'csharp.md'],
195
+ 'library-sdk': ['typescript.md', 'python.md', 'go.md', 'rust.md', 'java.md', 'csharp.md', 'php.md', 'ruby.md'],
196
+ other: null,
197
+ };
198
+
199
+ export const WEB_FRONTEND_STACK_CANDIDATES = ['typescript.md'];
200
+
201
+ export const WEB_BACKEND_STACK_CANDIDATES = [
202
+ 'typescript.md',
203
+ 'python.md',
204
+ 'go.md',
205
+ 'java.md',
206
+ 'php.md',
207
+ 'csharp.md',
208
+ 'ruby.md',
209
+ 'rust.md',
210
+ ];
211
+
212
+ export const WEB_FRONTEND_BLUEPRINT_CANDIDATES = [
213
+ 'api-nextjs.md',
214
+ ];
215
+
216
+ export const WEB_BACKEND_BLUEPRINT_CANDIDATES = [
217
+ 'nestjs-logic.md',
218
+ 'fastapi-service.md',
219
+ 'go-service.md',
220
+ 'spring-boot-api.md',
221
+ 'laravel-api.md',
222
+ 'aspnet-api.md',
223
+ 'graphql-grpc-api.md',
224
+ 'api-nextjs.md',
225
+ ];
226
+
227
+ export const RUNTIME_ENVIRONMENT_CHOICES = [
228
+ {
229
+ key: 'linux-wsl',
230
+ label: 'Linux / WSL',
231
+ },
232
+ {
233
+ key: 'windows',
234
+ label: 'Windows',
235
+ },
236
+ {
237
+ key: 'linux',
238
+ label: 'Linux',
239
+ },
240
+ {
241
+ key: 'macos',
242
+ label: 'macOS',
243
+ },
244
+ ];
245
+
163
246
  export const entryPointFiles = [
164
247
  '.cursorrules',
165
248
  '.windsurfrules',
@@ -133,6 +133,7 @@ export async function detectProjectContext(targetDirectoryPath) {
133
133
  return {
134
134
  hasExistingProjectFiles,
135
135
  recommendedStackFileName: null,
136
+ secondaryStackFileNames: [],
136
137
  recommendedBlueprintFileName: null,
137
138
  confidenceLabel: null,
138
139
  confidenceScore: 0,
@@ -164,6 +165,10 @@ export async function detectProjectContext(targetDirectoryPath) {
164
165
  confidenceScore: Number(detectionCandidate.confidenceScore.toFixed(2)),
165
166
  evidence: detectionCandidate.evidence,
166
167
  }));
168
+ const secondaryStackFileNames = rankedCandidates
169
+ .slice(1)
170
+ .filter((rankedCandidate) => (strongestCandidate.confidenceScore - rankedCandidate.confidenceScore) < 0.08)
171
+ .map((rankedCandidate) => rankedCandidate.stackFileName);
167
172
  const detectionReasoning = isAmbiguous
168
173
  ? `Top signal ${toTitleCase(strongestCandidate.stackFileName)} is close to ${toTitleCase(secondStrongestCandidate.stackFileName)} (confidence gap ${confidenceGap}).`
169
174
  : `Top signal ${toTitleCase(strongestCandidate.stackFileName)} won with confidence ${strongestCandidate.confidenceScore.toFixed(2)} from markers: ${strongestCandidate.evidence.join(', ') || 'none'}.`;
@@ -171,6 +176,7 @@ export async function detectProjectContext(targetDirectoryPath) {
171
176
  return {
172
177
  hasExistingProjectFiles,
173
178
  recommendedStackFileName: strongestCandidate.stackFileName,
179
+ secondaryStackFileNames,
174
180
  recommendedBlueprintFileName: BLUEPRINT_RECOMMENDATIONS[strongestCandidate.stackFileName] || null,
175
181
  confidenceLabel,
176
182
  confidenceScore: strongestCandidate.confidenceScore,
@@ -194,7 +200,11 @@ export function buildDetectionSummary(projectDetection) {
194
200
  ? ` Confidence gap: ${projectDetection.confidenceGap}.`
195
201
  : '';
196
202
 
197
- return `This folder looks like ${toTitleCase(projectDetection.recommendedStackFileName)} with ${projectDetection.confidenceLabel} confidence based on ${readableEvidence}.${confidenceGapSummary}`;
203
+ const secondaryStacksSummary = projectDetection.secondaryStackFileNames?.length
204
+ ? ` Secondary stack signals: ${projectDetection.secondaryStackFileNames.map((stackFileName) => toTitleCase(stackFileName)).join(', ')}.`
205
+ : '';
206
+
207
+ return `This folder looks like ${toTitleCase(projectDetection.recommendedStackFileName)} with ${projectDetection.confidenceLabel} confidence based on ${readableEvidence}.${confidenceGapSummary}${secondaryStacksSummary}`;
198
208
  }
199
209
 
200
210
  export function formatDetectionCandidates(rankedCandidates) {
@@ -22,7 +22,7 @@ const PROJECT_DOC_FILE_NAMES = [
22
22
  'flow-overview.md',
23
23
  ];
24
24
 
25
- export const PROJECT_DOC_TEMPLATE_VERSION = '1.1.0';
25
+ export const PROJECT_DOC_TEMPLATE_VERSION = '1.2.0';
26
26
 
27
27
  const DOMAIN_CHOICES = [
28
28
  'API service',
@@ -50,6 +50,13 @@ const AUTH_CHOICES = [
50
50
  'Other',
51
51
  ];
52
52
 
53
+ const DOCKER_STRATEGY_CHOICES = [
54
+ 'No Docker (run services directly)',
55
+ 'Docker for development only',
56
+ 'Docker for production only',
57
+ 'Docker for both development and production',
58
+ ];
59
+
53
60
  const DISCOVERY_MODE_CHOICES = [
54
61
  'Quick mode (mostly choices, fastest)',
55
62
  'Detailed mode (type your own answers)',
@@ -101,6 +108,100 @@ const FEATURE_PRESET_MAP = {
101
108
  ],
102
109
  };
103
110
 
111
+ function parseBooleanLikeValue(rawValue) {
112
+ const normalizedValue = String(rawValue || '').trim().toLowerCase();
113
+ if (['true', 'yes', 'y', '1'].includes(normalizedValue)) {
114
+ return true;
115
+ }
116
+
117
+ if (['false', 'no', 'n', '0'].includes(normalizedValue)) {
118
+ return false;
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ function resolveDockerStrategy({ dockerStrategy, useDocker, useDockerDevelopment, useDockerProduction }) {
125
+ if (typeof dockerStrategy === 'string' && dockerStrategy.trim().length > 0) {
126
+ const normalizedDockerStrategy = dockerStrategy.trim().toLowerCase();
127
+ const directMatch = DOCKER_STRATEGY_CHOICES.find(
128
+ (dockerStrategyChoice) => dockerStrategyChoice.toLowerCase() === normalizedDockerStrategy
129
+ );
130
+
131
+ if (directMatch) {
132
+ return directMatch;
133
+ }
134
+ }
135
+
136
+ const normalizedUseDocker = typeof useDocker === 'boolean' ? useDocker : parseBooleanLikeValue(useDocker);
137
+ const normalizedUseDockerDevelopment = typeof useDockerDevelopment === 'boolean'
138
+ ? useDockerDevelopment
139
+ : parseBooleanLikeValue(useDockerDevelopment);
140
+ const normalizedUseDockerProduction = typeof useDockerProduction === 'boolean'
141
+ ? useDockerProduction
142
+ : parseBooleanLikeValue(useDockerProduction);
143
+
144
+ if (normalizedUseDocker === false) {
145
+ return DOCKER_STRATEGY_CHOICES[0];
146
+ }
147
+
148
+ if (normalizedUseDockerDevelopment === true && normalizedUseDockerProduction === true) {
149
+ return DOCKER_STRATEGY_CHOICES[3];
150
+ }
151
+
152
+ if (normalizedUseDockerDevelopment === true && normalizedUseDockerProduction !== true) {
153
+ return DOCKER_STRATEGY_CHOICES[1];
154
+ }
155
+
156
+ if (normalizedUseDockerProduction === true && normalizedUseDockerDevelopment !== true) {
157
+ return DOCKER_STRATEGY_CHOICES[2];
158
+ }
159
+
160
+ if (normalizedUseDocker === true) {
161
+ return DOCKER_STRATEGY_CHOICES[3];
162
+ }
163
+
164
+ return DOCKER_STRATEGY_CHOICES[0];
165
+ }
166
+
167
+ function parseDockerStrategy(dockerStrategy) {
168
+ const normalizedDockerStrategy = String(dockerStrategy || '').toLowerCase();
169
+
170
+ if (normalizedDockerStrategy.includes('both development and production')) {
171
+ return {
172
+ dockerStrategy: DOCKER_STRATEGY_CHOICES[3],
173
+ hasDocker: true,
174
+ useDockerDevelopment: true,
175
+ useDockerProduction: true,
176
+ };
177
+ }
178
+
179
+ if (normalizedDockerStrategy.includes('development only')) {
180
+ return {
181
+ dockerStrategy: DOCKER_STRATEGY_CHOICES[1],
182
+ hasDocker: true,
183
+ useDockerDevelopment: true,
184
+ useDockerProduction: false,
185
+ };
186
+ }
187
+
188
+ if (normalizedDockerStrategy.includes('production only')) {
189
+ return {
190
+ dockerStrategy: DOCKER_STRATEGY_CHOICES[2],
191
+ hasDocker: true,
192
+ useDockerDevelopment: false,
193
+ useDockerProduction: true,
194
+ };
195
+ }
196
+
197
+ return {
198
+ dockerStrategy: DOCKER_STRATEGY_CHOICES[0],
199
+ hasDocker: false,
200
+ useDockerDevelopment: false,
201
+ useDockerProduction: false,
202
+ };
203
+ }
204
+
104
205
  async function askFeatureList(userInterface) {
105
206
  console.log('\nList your key features (one per line, press Enter to finish):');
106
207
 
@@ -253,6 +354,42 @@ export async function runProjectDiscovery(userInterface, options = {}) {
253
354
  authStrategy = (await userInterface.question('Describe your auth setup: ')).trim() || 'Custom auth';
254
355
  }
255
356
 
357
+ let dockerStrategy = DOCKER_STRATEGY_CHOICES[0];
358
+ if (isQuickMode) {
359
+ dockerStrategy = await askChoice(
360
+ 'Containerization strategy:',
361
+ DOCKER_STRATEGY_CHOICES,
362
+ userInterface
363
+ );
364
+ } else {
365
+ const useDocker = await askYesNo(
366
+ 'Use Docker for this project (development and/or production)?',
367
+ userInterface,
368
+ false
369
+ );
370
+
371
+ if (useDocker) {
372
+ const useDockerDevelopment = await askYesNo(
373
+ 'Use Docker for development workflow?',
374
+ userInterface,
375
+ true
376
+ );
377
+ const useDockerProduction = await askYesNo(
378
+ 'Use Docker for production runtime/build?',
379
+ userInterface,
380
+ true
381
+ );
382
+
383
+ dockerStrategy = resolveDockerStrategy({
384
+ useDocker,
385
+ useDockerDevelopment,
386
+ useDockerProduction,
387
+ });
388
+ }
389
+ }
390
+
391
+ const parsedDockerStrategy = parseDockerStrategy(dockerStrategy);
392
+
256
393
  let features = [];
257
394
  if (isQuickMode) {
258
395
  const selectedFeaturePreset = await askChoice(
@@ -297,6 +434,9 @@ export async function runProjectDiscovery(userInterface, options = {}) {
297
434
  primaryDomain,
298
435
  databaseChoice,
299
436
  authStrategy,
437
+ dockerStrategy: parsedDockerStrategy.dockerStrategy,
438
+ useDockerDevelopment: parsedDockerStrategy.useDockerDevelopment,
439
+ useDockerProduction: parsedDockerStrategy.useDockerProduction,
300
440
  features,
301
441
  additionalContext,
302
442
  };
@@ -343,6 +483,13 @@ export function resolveDocumentManifest(discoveryAnswers) {
343
483
  export function buildTemplateContext(discoveryAnswers, initContext) {
344
484
  const hasDatabase = !discoveryAnswers.databaseChoice.toLowerCase().startsWith('none');
345
485
  const hasAuth = !discoveryAnswers.authStrategy.toLowerCase().startsWith('none');
486
+ const additionalStackFileNames = Array.isArray(initContext.additionalStackFileNames)
487
+ ? initContext.additionalStackFileNames
488
+ : [];
489
+ const additionalBlueprintFileNames = Array.isArray(initContext.additionalBlueprintFileNames)
490
+ ? initContext.additionalBlueprintFileNames
491
+ : [];
492
+ const parsedDockerStrategy = parseDockerStrategy(discoveryAnswers.dockerStrategy);
346
493
 
347
494
  const baseUrlMap = {
348
495
  'API service': 'http://localhost:3000',
@@ -362,14 +509,34 @@ export function buildTemplateContext(discoveryAnswers, initContext) {
362
509
  additionalContext: discoveryAnswers.additionalContext,
363
510
  stackFileName: initContext.stackFileName,
364
511
  stackDisplayName: toTitleCase(initContext.stackFileName),
512
+ additionalStackFileNames,
513
+ additionalStackDisplayNames: additionalStackFileNames.length > 0
514
+ ? additionalStackFileNames.map((stackFileName) => toTitleCase(stackFileName))
515
+ : null,
365
516
  blueprintFileName: initContext.blueprintFileName,
366
517
  blueprintDisplayName: toTitleCase(initContext.blueprintFileName),
518
+ additionalBlueprintFileNames,
519
+ additionalBlueprintDisplayNames: additionalBlueprintFileNames.length > 0
520
+ ? additionalBlueprintFileNames.map((blueprintFileName) => toTitleCase(blueprintFileName))
521
+ : null,
522
+ runtimeEnvironmentKey: initContext.runtimeEnvironmentKey || 'linux',
523
+ runtimeEnvironmentLabel: initContext.runtimeEnvironmentLabel || 'Linux',
367
524
  cliVersion: CLI_VERSION,
368
525
  templateVersion: PROJECT_DOC_TEMPLATE_VERSION,
369
526
  generatedAt: new Date().toISOString(),
370
527
  generatedDate: new Date().toISOString().split('T')[0],
371
528
  hasDatabase,
372
529
  hasAuth,
530
+ hasDocker: parsedDockerStrategy.hasDocker,
531
+ dockerStrategy: parsedDockerStrategy.dockerStrategy,
532
+ useDockerDevelopment: parsedDockerStrategy.useDockerDevelopment,
533
+ useDockerProduction: parsedDockerStrategy.useDockerProduction,
534
+ dockerDevelopmentGuidance: parsedDockerStrategy.useDockerDevelopment
535
+ ? '- Development containers are required: optimize for fast rebuilds, bind mounts, and debug-friendly startup.'
536
+ : '',
537
+ dockerProductionGuidance: parsedDockerStrategy.useDockerProduction
538
+ ? '- Production containers are required: use multi-stage builds, non-root runtime, and minimal image footprint.'
539
+ : '',
373
540
  baseUrl: baseUrlMap[discoveryAnswers.primaryDomain] || 'http://localhost:3000',
374
541
  };
375
542
  }
@@ -595,6 +762,12 @@ export async function loadProjectConfig(configFilePath) {
595
762
  primaryDomain: configEntries.primaryDomain || configEntries.domain || 'API service',
596
763
  databaseChoice: configEntries.databaseChoice || configEntries.database || 'None (stateless service)',
597
764
  authStrategy: configEntries.authStrategy || configEntries.auth || 'None (public service)',
765
+ dockerStrategy: resolveDockerStrategy({
766
+ dockerStrategy: configEntries.dockerStrategy || configEntries.containerStrategy,
767
+ useDocker: configEntries.useDocker,
768
+ useDockerDevelopment: configEntries.useDockerDevelopment || configEntries.dockerDevelopment,
769
+ useDockerProduction: configEntries.useDockerProduction || configEntries.dockerProduction,
770
+ }),
598
771
  features: Array.isArray(configEntries.features) ? configEntries.features : [],
599
772
  additionalContext: configEntries.additionalContext || configEntries.context || 'No additional context provided.',
600
773
  docsLang: configEntries.docsLang || configEntries.docsLanguage || 'en',