@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.
- package/.agent-context/review-checklists/frontend-excellence-rubric.md +54 -0
- package/.agent-context/review-checklists/frontend-skill-parity.md +1 -0
- package/.agent-context/review-checklists/frontend-usability.md +1 -0
- package/.agent-context/rules/docker-runtime.md +29 -0
- package/.agent-context/skills/frontend/README.md +1 -0
- package/.agent-context/skills/frontend.md +4 -0
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/README.md +7 -0
- package/lib/cli/commands/init.mjs +358 -16
- package/lib/cli/commands/optimize.mjs +12 -0
- package/lib/cli/commands/upgrade.mjs +30 -1
- package/lib/cli/compiler.mjs +55 -1
- package/lib/cli/constants.mjs +83 -0
- package/lib/cli/detector.mjs +11 -1
- package/lib/cli/project-scaffolder.mjs +174 -1
- package/lib/cli/skill-selector.mjs +60 -38
- package/lib/cli/templates/architecture-decision-record.md.tmpl +39 -0
- package/lib/cli/templates/flow-overview.md.tmpl +12 -0
- package/lib/cli/templates/project-brief.md.id.tmpl +2 -0
- package/lib/cli/templates/project-brief.md.tmpl +26 -0
- package/lib/cli/utils.mjs +2 -1
- package/package.json +1 -1
- package/scripts/frontend-usability-audit.mjs +21 -0
- package/scripts/release-gate.mjs +30 -0
- package/scripts/validate.mjs +2 -0
|
@@ -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
|
|
package/lib/cli/compiler.mjs
CHANGED
|
@@ -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
|
|
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
|
|
package/lib/cli/constants.mjs
CHANGED
|
@@ -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',
|
package/lib/cli/detector.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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',
|