@ryuenn3123/agentic-senior-core 3.0.11 → 3.0.13
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/prompts/bootstrap-design.md +7 -1
- package/.agent-context/rules/architecture.md +7 -0
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/lib/cli/commands/init.mjs +215 -154
- package/lib/cli/commands/upgrade.mjs +5 -0
- package/lib/cli/compiler.mjs +20 -0
- package/lib/cli/constants.mjs +1 -0
- package/lib/cli/detector.mjs +444 -90
- package/lib/cli/project-scaffolder.mjs +161 -35
- package/package.json +1 -1
- package/scripts/frontend-usability-audit.mjs +2 -0
- package/scripts/mcp-server.mjs +200 -118
- package/scripts/ui-design-judge.mjs +3 -2
- package/scripts/validate.mjs +4 -0
package/lib/cli/detector.mjs
CHANGED
|
@@ -13,6 +13,149 @@ const FRONTEND_SCAN_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.v
|
|
|
13
13
|
const FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES = new Set(['.git', 'node_modules', '.next', 'dist', 'build', 'coverage']);
|
|
14
14
|
const FRONTEND_FILE_SCAN_LIMIT = 200;
|
|
15
15
|
const FRONTEND_FILE_SIZE_LIMIT_BYTES = 200_000;
|
|
16
|
+
const WORKSPACE_SCAN_MAX_DEPTH = 3;
|
|
17
|
+
const WORKSPACE_SCAN_MAX_DIRECTORIES = 120;
|
|
18
|
+
const WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES = new Set([
|
|
19
|
+
...FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES,
|
|
20
|
+
'.agent-context',
|
|
21
|
+
'.agents',
|
|
22
|
+
'.cursor',
|
|
23
|
+
'.gemini',
|
|
24
|
+
'.github',
|
|
25
|
+
'.idea',
|
|
26
|
+
'.vscode',
|
|
27
|
+
'.zed',
|
|
28
|
+
]);
|
|
29
|
+
const WORKSPACE_CONTAINER_DIRECTORY_NAMES = new Set([
|
|
30
|
+
'admin',
|
|
31
|
+
'admins',
|
|
32
|
+
'api',
|
|
33
|
+
'apis',
|
|
34
|
+
'app',
|
|
35
|
+
'apps',
|
|
36
|
+
'backend',
|
|
37
|
+
'backends',
|
|
38
|
+
'client',
|
|
39
|
+
'clients',
|
|
40
|
+
'dashboard',
|
|
41
|
+
'dashboards',
|
|
42
|
+
'frontend',
|
|
43
|
+
'frontends',
|
|
44
|
+
'mobile',
|
|
45
|
+
'mobiles',
|
|
46
|
+
'package',
|
|
47
|
+
'packages',
|
|
48
|
+
'pkg',
|
|
49
|
+
'server',
|
|
50
|
+
'servers',
|
|
51
|
+
'service',
|
|
52
|
+
'services',
|
|
53
|
+
'site',
|
|
54
|
+
'sites',
|
|
55
|
+
'ui',
|
|
56
|
+
'web',
|
|
57
|
+
'worker',
|
|
58
|
+
'workers',
|
|
59
|
+
]);
|
|
60
|
+
const WORKSPACE_ROOT_MARKER_FILE_NAMES = new Set([
|
|
61
|
+
'lerna.json',
|
|
62
|
+
'nx.json',
|
|
63
|
+
'pnpm-workspace.yaml',
|
|
64
|
+
'turbo.json',
|
|
65
|
+
]);
|
|
66
|
+
const DIRECT_UI_MARKER_NAMES = [
|
|
67
|
+
'src',
|
|
68
|
+
'next.config.js',
|
|
69
|
+
'next.config.mjs',
|
|
70
|
+
'next.config.ts',
|
|
71
|
+
'tailwind.config.js',
|
|
72
|
+
'tailwind.config.mjs',
|
|
73
|
+
'tailwind.config.ts',
|
|
74
|
+
'vite.config.js',
|
|
75
|
+
'vite.config.mjs',
|
|
76
|
+
'vite.config.ts',
|
|
77
|
+
'react-native.config.js',
|
|
78
|
+
'app',
|
|
79
|
+
'pages',
|
|
80
|
+
'components',
|
|
81
|
+
'public',
|
|
82
|
+
'styles',
|
|
83
|
+
'android',
|
|
84
|
+
'ios',
|
|
85
|
+
'index.html',
|
|
86
|
+
];
|
|
87
|
+
const PROJECT_MARKER_FILE_NAMES = new Set([
|
|
88
|
+
'Cargo.toml',
|
|
89
|
+
'Gemfile',
|
|
90
|
+
'build.gradle',
|
|
91
|
+
'build.gradle.kts',
|
|
92
|
+
'composer.json',
|
|
93
|
+
'go.mod',
|
|
94
|
+
'package.json',
|
|
95
|
+
'pom.xml',
|
|
96
|
+
'pubspec.yaml',
|
|
97
|
+
'pyproject.toml',
|
|
98
|
+
'react-native.config.js',
|
|
99
|
+
'requirements.txt',
|
|
100
|
+
'tsconfig.json',
|
|
101
|
+
...DIRECT_UI_MARKER_NAMES,
|
|
102
|
+
]);
|
|
103
|
+
const INTERNAL_GOVERNANCE_SURFACE_NAMES = new Set([
|
|
104
|
+
'.agent-context',
|
|
105
|
+
'.agent-instructions.md',
|
|
106
|
+
'.agent-override.md',
|
|
107
|
+
'.agentic-backup',
|
|
108
|
+
'.agents',
|
|
109
|
+
'.clauderc',
|
|
110
|
+
'.cursorrules',
|
|
111
|
+
'.cursor',
|
|
112
|
+
'.gemini',
|
|
113
|
+
'.github',
|
|
114
|
+
'.instructions.md',
|
|
115
|
+
'.vscode',
|
|
116
|
+
'.windsurfrules',
|
|
117
|
+
'.zed',
|
|
118
|
+
'AGENTS.md',
|
|
119
|
+
'mcp.json',
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
function looksLikeWorkspaceSearchCandidate(directoryName) {
|
|
123
|
+
const normalizedDirectoryName = String(directoryName || '').trim().toLowerCase();
|
|
124
|
+
|
|
125
|
+
if (!normalizedDirectoryName) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(normalizedDirectoryName)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return [
|
|
134
|
+
'admin',
|
|
135
|
+
'api',
|
|
136
|
+
'app',
|
|
137
|
+
'backend',
|
|
138
|
+
'client',
|
|
139
|
+
'dashboard',
|
|
140
|
+
'frontend',
|
|
141
|
+
'mobile',
|
|
142
|
+
'package',
|
|
143
|
+
'server',
|
|
144
|
+
'service',
|
|
145
|
+
'site',
|
|
146
|
+
'ui',
|
|
147
|
+
'web',
|
|
148
|
+
'worker',
|
|
149
|
+
].some((keyword) => normalizedDirectoryName.includes(keyword));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasProjectMarkers(markerNames) {
|
|
153
|
+
return Array.from(markerNames).some((markerName) => (
|
|
154
|
+
PROJECT_MARKER_FILE_NAMES.has(markerName)
|
|
155
|
+
|| markerName.endsWith('.csproj')
|
|
156
|
+
|| markerName.endsWith('.sln')
|
|
157
|
+
));
|
|
158
|
+
}
|
|
16
159
|
|
|
17
160
|
export async function collectProjectMarkers(targetDirectoryPath) {
|
|
18
161
|
const markerNames = new Set();
|
|
@@ -23,6 +166,10 @@ export async function collectProjectMarkers(targetDirectoryPath) {
|
|
|
23
166
|
continue;
|
|
24
167
|
}
|
|
25
168
|
|
|
169
|
+
if (INTERNAL_GOVERNANCE_SURFACE_NAMES.has(directoryEntry.name)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
26
173
|
markerNames.add(directoryEntry.name);
|
|
27
174
|
}
|
|
28
175
|
|
|
@@ -40,6 +187,133 @@ async function readPackageJsonIfExists(targetDirectoryPath) {
|
|
|
40
187
|
}
|
|
41
188
|
}
|
|
42
189
|
|
|
190
|
+
async function readDirectoryEntries(directoryPath) {
|
|
191
|
+
try {
|
|
192
|
+
return await fs.readdir(directoryPath, { withFileTypes: true });
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function collectNestedWorkspaceProjects(targetDirectoryPath) {
|
|
199
|
+
const rootDirectoryEntries = await readDirectoryEntries(targetDirectoryPath);
|
|
200
|
+
const rootMarkerNames = new Set(rootDirectoryEntries.map((directoryEntry) => directoryEntry.name));
|
|
201
|
+
const rootLooksLikeWorkspace = Array.from(rootMarkerNames).some((markerName) => (
|
|
202
|
+
WORKSPACE_ROOT_MARKER_FILE_NAMES.has(markerName)
|
|
203
|
+
|| looksLikeWorkspaceSearchCandidate(markerName)
|
|
204
|
+
));
|
|
205
|
+
const nestedWorkspaceProjects = [];
|
|
206
|
+
const queuedWorkspacePaths = new Set();
|
|
207
|
+
const workspaceQueue = [];
|
|
208
|
+
let scannedDirectoryCount = 0;
|
|
209
|
+
|
|
210
|
+
for (const rootDirectoryEntry of rootDirectoryEntries) {
|
|
211
|
+
if (!rootDirectoryEntry.isDirectory()) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES.has(rootDirectoryEntry.name)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const shouldInspectRootChild = rootLooksLikeWorkspace
|
|
220
|
+
|| looksLikeWorkspaceSearchCandidate(rootDirectoryEntry.name);
|
|
221
|
+
|
|
222
|
+
if (!shouldInspectRootChild) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const rootChildDirectoryPath = path.join(targetDirectoryPath, rootDirectoryEntry.name);
|
|
227
|
+
const rootChildEntries = await readDirectoryEntries(rootChildDirectoryPath);
|
|
228
|
+
const rootChildMarkerNames = new Set(rootChildEntries.map((directoryEntry) => directoryEntry.name));
|
|
229
|
+
const rootChildRelativePath = rootDirectoryEntry.name.replace(/\\/g, '/');
|
|
230
|
+
|
|
231
|
+
workspaceQueue.push({
|
|
232
|
+
directoryPath: rootChildDirectoryPath,
|
|
233
|
+
relativePath: rootChildRelativePath,
|
|
234
|
+
markerNames: rootChildMarkerNames,
|
|
235
|
+
depth: 1,
|
|
236
|
+
underWorkspaceContainer: WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(rootDirectoryEntry.name.toLowerCase()),
|
|
237
|
+
});
|
|
238
|
+
queuedWorkspacePaths.add(rootChildRelativePath);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
while (workspaceQueue.length > 0 && scannedDirectoryCount < WORKSPACE_SCAN_MAX_DIRECTORIES) {
|
|
242
|
+
const currentWorkspaceEntry = workspaceQueue.shift();
|
|
243
|
+
scannedDirectoryCount += 1;
|
|
244
|
+
|
|
245
|
+
const isProjectCandidate = hasProjectMarkers(currentWorkspaceEntry.markerNames);
|
|
246
|
+
const currentDirectoryName = path.basename(currentWorkspaceEntry.directoryPath).toLowerCase();
|
|
247
|
+
const isWorkspaceContainer = WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(currentDirectoryName);
|
|
248
|
+
|
|
249
|
+
if (isProjectCandidate) {
|
|
250
|
+
nestedWorkspaceProjects.push({
|
|
251
|
+
directoryPath: currentWorkspaceEntry.directoryPath,
|
|
252
|
+
relativePath: currentWorkspaceEntry.relativePath,
|
|
253
|
+
markerNames: currentWorkspaceEntry.markerNames,
|
|
254
|
+
packageManifest: currentWorkspaceEntry.markerNames.has('package.json')
|
|
255
|
+
? await readPackageJsonIfExists(currentWorkspaceEntry.directoryPath)
|
|
256
|
+
: null,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (currentWorkspaceEntry.depth >= WORKSPACE_SCAN_MAX_DEPTH) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const shouldTraverseChildren = currentWorkspaceEntry.underWorkspaceContainer
|
|
265
|
+
|| isWorkspaceContainer
|
|
266
|
+
|| !isProjectCandidate;
|
|
267
|
+
|
|
268
|
+
if (!shouldTraverseChildren) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const childEntries = await readDirectoryEntries(currentWorkspaceEntry.directoryPath);
|
|
273
|
+
for (const childEntry of childEntries) {
|
|
274
|
+
if (!childEntry.isDirectory()) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES.has(childEntry.name)) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const childLooksRelevant = looksLikeWorkspaceSearchCandidate(childEntry.name);
|
|
283
|
+
if (!childLooksRelevant && !currentWorkspaceEntry.underWorkspaceContainer && !isWorkspaceContainer) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const childDirectoryPath = path.join(currentWorkspaceEntry.directoryPath, childEntry.name);
|
|
288
|
+
const childRelativePath = path.join(currentWorkspaceEntry.relativePath, childEntry.name).replace(/\\/g, '/');
|
|
289
|
+
|
|
290
|
+
if (queuedWorkspacePaths.has(childRelativePath)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const childDirectoryEntries = await readDirectoryEntries(childDirectoryPath);
|
|
295
|
+
const childMarkerNames = new Set(childDirectoryEntries.map((directoryEntry) => directoryEntry.name));
|
|
296
|
+
const childIsProjectCandidate = hasProjectMarkers(childMarkerNames);
|
|
297
|
+
const childIsWorkspaceContainer = WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(childEntry.name.toLowerCase());
|
|
298
|
+
|
|
299
|
+
if (!childIsProjectCandidate && !childIsWorkspaceContainer && !childLooksRelevant) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
workspaceQueue.push({
|
|
304
|
+
directoryPath: childDirectoryPath,
|
|
305
|
+
relativePath: childRelativePath,
|
|
306
|
+
markerNames: childMarkerNames,
|
|
307
|
+
depth: currentWorkspaceEntry.depth + 1,
|
|
308
|
+
underWorkspaceContainer: currentWorkspaceEntry.underWorkspaceContainer || isWorkspaceContainer || childIsWorkspaceContainer,
|
|
309
|
+
});
|
|
310
|
+
queuedWorkspacePaths.add(childRelativePath);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return nestedWorkspaceProjects;
|
|
315
|
+
}
|
|
316
|
+
|
|
43
317
|
async function collectFrontendSourceFilePaths(directoryPath, collectedFilePaths = []) {
|
|
44
318
|
if (collectedFilePaths.length >= FRONTEND_FILE_SCAN_LIMIT) {
|
|
45
319
|
return collectedFilePaths;
|
|
@@ -79,13 +353,18 @@ function countPatternMatches(sourceText, pattern) {
|
|
|
79
353
|
return Array.from(sourceText.matchAll(pattern)).length;
|
|
80
354
|
}
|
|
81
355
|
|
|
82
|
-
async function collectFrontendEvidenceMetrics(targetDirectoryPath, markerNames) {
|
|
356
|
+
async function collectFrontendEvidenceMetrics(targetDirectoryPath, markerNames, scanRootDirectoryPaths = []) {
|
|
83
357
|
const candidateDirectoryPaths = FRONTEND_SCAN_DIRECTORY_NAMES
|
|
84
358
|
.filter((directoryName) => markerNames.has(directoryName))
|
|
85
359
|
.map((directoryName) => path.join(targetDirectoryPath, directoryName));
|
|
86
|
-
const
|
|
87
|
-
?
|
|
88
|
-
: [
|
|
360
|
+
const explicitScanRootDirectoryPaths = Array.isArray(scanRootDirectoryPaths)
|
|
361
|
+
? scanRootDirectoryPaths.filter((scanRootDirectoryPath) => typeof scanRootDirectoryPath === 'string' && scanRootDirectoryPath.trim().length > 0)
|
|
362
|
+
: [];
|
|
363
|
+
const resolvedCandidateDirectoryPaths = explicitScanRootDirectoryPaths.length > 0
|
|
364
|
+
? Array.from(new Set(explicitScanRootDirectoryPaths))
|
|
365
|
+
: candidateDirectoryPaths.length > 0
|
|
366
|
+
? candidateDirectoryPaths
|
|
367
|
+
: [targetDirectoryPath];
|
|
89
368
|
const scannedFilePaths = [];
|
|
90
369
|
|
|
91
370
|
for (const candidateDirectoryPath of resolvedCandidateDirectoryPaths) {
|
|
@@ -144,63 +423,11 @@ async function collectFrontendEvidenceMetrics(targetDirectoryPath, markerNames)
|
|
|
144
423
|
};
|
|
145
424
|
}
|
|
146
425
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
selectedStackFileName,
|
|
150
|
-
selectedBlueprintFileName,
|
|
151
|
-
packageManifest = null,
|
|
152
|
-
projectScopeKey = null,
|
|
153
|
-
projectScopeSourceLabel = 'project scope',
|
|
154
|
-
}) {
|
|
155
|
-
const signalReasons = [];
|
|
156
|
-
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
157
|
-
const resolvedPackageManifest = packageManifest || await readPackageJsonIfExists(targetDirectoryPath);
|
|
158
|
-
|
|
159
|
-
const normalizedProjectScopeKey = String(projectScopeKey || '').trim().toLowerCase();
|
|
160
|
-
if (normalizedProjectScopeKey === 'frontend-only' || normalizedProjectScopeKey === 'both') {
|
|
161
|
-
signalReasons.push(`${projectScopeSourceLabel}: ${normalizedProjectScopeKey}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const selectedStackKey = String(selectedStackFileName || '').trim().toLowerCase();
|
|
165
|
-
if (selectedStackKey === 'react-native.md' || selectedStackKey === 'flutter.md') {
|
|
166
|
-
signalReasons.push(`selected stack implies UI runtime: ${selectedStackKey}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const selectedBlueprintKey = String(selectedBlueprintFileName || '').trim().toLowerCase();
|
|
170
|
-
if (selectedBlueprintKey.includes('frontend') || selectedBlueprintKey.includes('landing') || selectedBlueprintKey.includes('mobile-app')) {
|
|
171
|
-
signalReasons.push(`selected blueprint implies UI scope: ${selectedBlueprintKey}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const directUiMarkerNames = [
|
|
175
|
-
'src',
|
|
176
|
-
'next.config.js',
|
|
177
|
-
'next.config.mjs',
|
|
178
|
-
'next.config.ts',
|
|
179
|
-
'tailwind.config.js',
|
|
180
|
-
'tailwind.config.mjs',
|
|
181
|
-
'tailwind.config.ts',
|
|
182
|
-
'vite.config.js',
|
|
183
|
-
'vite.config.mjs',
|
|
184
|
-
'vite.config.ts',
|
|
185
|
-
'react-native.config.js',
|
|
186
|
-
'app',
|
|
187
|
-
'pages',
|
|
188
|
-
'components',
|
|
189
|
-
'public',
|
|
190
|
-
'styles',
|
|
191
|
-
'android',
|
|
192
|
-
'ios',
|
|
193
|
-
'index.html',
|
|
194
|
-
];
|
|
195
|
-
|
|
196
|
-
const detectedUiMarkers = directUiMarkerNames.filter((markerName) => markerNames.has(markerName));
|
|
197
|
-
if (detectedUiMarkers.length > 0) {
|
|
198
|
-
signalReasons.push(`ui markers: ${detectedUiMarkers.join(', ')}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
426
|
+
function analyzeUiSignalsForMarkerSet(markerNames, packageManifest, sourceLabel = null) {
|
|
427
|
+
const detectedUiMarkers = DIRECT_UI_MARKER_NAMES.filter((markerName) => markerNames.has(markerName));
|
|
201
428
|
const dependencySource = {
|
|
202
|
-
...(
|
|
203
|
-
...(
|
|
429
|
+
...(packageManifest?.dependencies || {}),
|
|
430
|
+
...(packageManifest?.devDependencies || {}),
|
|
204
431
|
};
|
|
205
432
|
const detectableUiDependencies = [
|
|
206
433
|
'next',
|
|
@@ -211,10 +438,6 @@ export async function detectUiScopeSignals({
|
|
|
211
438
|
'tailwindcss',
|
|
212
439
|
];
|
|
213
440
|
const detectedUiDependencies = detectableUiDependencies.filter((dependencyName) => dependencySource[dependencyName]);
|
|
214
|
-
if (detectedUiDependencies.length > 0) {
|
|
215
|
-
signalReasons.push(`ui dependencies: ${detectedUiDependencies.join(', ')}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
441
|
const hasStrongUiMarker = detectedUiMarkers.some((markerName) => (
|
|
219
442
|
markerName.startsWith('next.config')
|
|
220
443
|
|| markerName === 'react-native.config.js'
|
|
@@ -223,46 +446,63 @@ export async function detectUiScopeSignals({
|
|
|
223
446
|
));
|
|
224
447
|
const hasUiDependencies = detectedUiDependencies.length > 0;
|
|
225
448
|
const hasStructuralUiMarkers = detectedUiMarkers.length >= 2;
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
:
|
|
449
|
+
const signalReasons = [];
|
|
450
|
+
const sourcePrefix = sourceLabel ? `${sourceLabel}: ` : '';
|
|
451
|
+
|
|
452
|
+
if (detectedUiMarkers.length > 0) {
|
|
453
|
+
signalReasons.push(`${sourcePrefix}ui markers: ${detectedUiMarkers.join(', ')}`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (detectedUiDependencies.length > 0) {
|
|
457
|
+
signalReasons.push(`${sourcePrefix}ui dependencies: ${detectedUiDependencies.join(', ')}`);
|
|
458
|
+
}
|
|
231
459
|
|
|
232
460
|
return {
|
|
233
|
-
isUiScopeLikely,
|
|
234
461
|
signalReasons,
|
|
235
462
|
detectedUiMarkers,
|
|
236
463
|
detectedUiDependencies,
|
|
237
|
-
|
|
238
|
-
|
|
464
|
+
hasStrongUiMarker,
|
|
465
|
+
hasUiDependencies,
|
|
466
|
+
hasStructuralUiMarkers,
|
|
239
467
|
};
|
|
240
468
|
}
|
|
241
469
|
|
|
242
|
-
|
|
243
|
-
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
470
|
+
function collectStackDetectionCandidates(markerNames, evidencePrefix = null) {
|
|
244
471
|
const detectionCandidates = [];
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
if (
|
|
472
|
+
const withEvidencePrefix = (evidenceItem) => evidencePrefix ? `${evidencePrefix}: ${evidenceItem}` : evidenceItem;
|
|
473
|
+
|
|
474
|
+
if (
|
|
475
|
+
markerNames.has('package.json')
|
|
476
|
+
|| markerNames.has('tsconfig.json')
|
|
477
|
+
|| markerNames.has('next.config.js')
|
|
478
|
+
|| markerNames.has('next.config.mjs')
|
|
479
|
+
|| markerNames.has('vite.config.js')
|
|
480
|
+
|| markerNames.has('vite.config.mjs')
|
|
481
|
+
|| markerNames.has('vite.config.ts')
|
|
482
|
+
) {
|
|
248
483
|
const evidence = [];
|
|
249
484
|
let confidenceScore = 0.7;
|
|
250
485
|
|
|
251
486
|
if (markerNames.has('package.json')) {
|
|
252
|
-
evidence.push('package.json');
|
|
487
|
+
evidence.push(withEvidencePrefix('package.json'));
|
|
253
488
|
confidenceScore += 0.12;
|
|
254
489
|
}
|
|
255
490
|
|
|
256
491
|
if (markerNames.has('tsconfig.json')) {
|
|
257
|
-
evidence.push('tsconfig.json');
|
|
492
|
+
evidence.push(withEvidencePrefix('tsconfig.json'));
|
|
258
493
|
confidenceScore += 0.12;
|
|
259
494
|
}
|
|
260
495
|
|
|
261
496
|
if (markerNames.has('next.config.js') || markerNames.has('next.config.mjs')) {
|
|
262
|
-
evidence.push('Next.js config');
|
|
497
|
+
evidence.push(withEvidencePrefix('Next.js config'));
|
|
263
498
|
confidenceScore += 0.05;
|
|
264
499
|
}
|
|
265
500
|
|
|
501
|
+
if (markerNames.has('vite.config.js') || markerNames.has('vite.config.mjs') || markerNames.has('vite.config.ts')) {
|
|
502
|
+
evidence.push(withEvidencePrefix('Vite config'));
|
|
503
|
+
confidenceScore += 0.08;
|
|
504
|
+
}
|
|
505
|
+
|
|
266
506
|
detectionCandidates.push({
|
|
267
507
|
stackFileName: 'typescript.md',
|
|
268
508
|
confidenceScore: Math.min(confidenceScore, 0.97),
|
|
@@ -274,14 +514,16 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
274
514
|
detectionCandidates.push({
|
|
275
515
|
stackFileName: 'python.md',
|
|
276
516
|
confidenceScore: markerNames.has('pyproject.toml') ? 0.96 : 0.78,
|
|
277
|
-
evidence: markerNames.has('pyproject.toml')
|
|
517
|
+
evidence: markerNames.has('pyproject.toml')
|
|
518
|
+
? [withEvidencePrefix('pyproject.toml')]
|
|
519
|
+
: [withEvidencePrefix('requirements.txt')],
|
|
278
520
|
});
|
|
279
521
|
}
|
|
280
522
|
|
|
281
523
|
if (markerNames.has('pom.xml') || markerNames.has('build.gradle') || markerNames.has('build.gradle.kts')) {
|
|
282
524
|
const evidence = [];
|
|
283
|
-
if (markerNames.has('pom.xml')) evidence.push('pom.xml');
|
|
284
|
-
if (markerNames.has('build.gradle') || markerNames.has('build.gradle.kts')) evidence.push('Gradle build file');
|
|
525
|
+
if (markerNames.has('pom.xml')) evidence.push(withEvidencePrefix('pom.xml'));
|
|
526
|
+
if (markerNames.has('build.gradle') || markerNames.has('build.gradle.kts')) evidence.push(withEvidencePrefix('Gradle build file'));
|
|
285
527
|
detectionCandidates.push({
|
|
286
528
|
stackFileName: 'java.md',
|
|
287
529
|
confidenceScore: markerNames.has('pom.xml') ? 0.95 : 0.84,
|
|
@@ -293,7 +535,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
293
535
|
detectionCandidates.push({
|
|
294
536
|
stackFileName: 'php.md',
|
|
295
537
|
confidenceScore: 0.95,
|
|
296
|
-
evidence: ['composer.json'],
|
|
538
|
+
evidence: [withEvidencePrefix('composer.json')],
|
|
297
539
|
});
|
|
298
540
|
}
|
|
299
541
|
|
|
@@ -301,7 +543,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
301
543
|
detectionCandidates.push({
|
|
302
544
|
stackFileName: 'go.md',
|
|
303
545
|
confidenceScore: 0.96,
|
|
304
|
-
evidence: ['go.mod'],
|
|
546
|
+
evidence: [withEvidencePrefix('go.mod')],
|
|
305
547
|
});
|
|
306
548
|
}
|
|
307
549
|
|
|
@@ -309,7 +551,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
309
551
|
detectionCandidates.push({
|
|
310
552
|
stackFileName: 'rust.md',
|
|
311
553
|
confidenceScore: 0.96,
|
|
312
|
-
evidence: ['Cargo.toml'],
|
|
554
|
+
evidence: [withEvidencePrefix('Cargo.toml')],
|
|
313
555
|
});
|
|
314
556
|
}
|
|
315
557
|
|
|
@@ -317,7 +559,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
317
559
|
detectionCandidates.push({
|
|
318
560
|
stackFileName: 'ruby.md',
|
|
319
561
|
confidenceScore: 0.95,
|
|
320
|
-
evidence: ['Gemfile'],
|
|
562
|
+
evidence: [withEvidencePrefix('Gemfile')],
|
|
321
563
|
});
|
|
322
564
|
}
|
|
323
565
|
|
|
@@ -326,7 +568,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
326
568
|
detectionCandidates.push({
|
|
327
569
|
stackFileName: 'csharp.md',
|
|
328
570
|
confidenceScore: 0.95,
|
|
329
|
-
evidence: ['.sln or .csproj file'],
|
|
571
|
+
evidence: [withEvidencePrefix('.sln or .csproj file')],
|
|
330
572
|
});
|
|
331
573
|
}
|
|
332
574
|
|
|
@@ -334,7 +576,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
334
576
|
detectionCandidates.push({
|
|
335
577
|
stackFileName: 'react-native.md',
|
|
336
578
|
confidenceScore: 0.9,
|
|
337
|
-
evidence: ['package.json', 'mobile runtime markers'],
|
|
579
|
+
evidence: [withEvidencePrefix('package.json'), withEvidencePrefix('mobile runtime markers')],
|
|
338
580
|
});
|
|
339
581
|
}
|
|
340
582
|
|
|
@@ -342,10 +584,122 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
342
584
|
detectionCandidates.push({
|
|
343
585
|
stackFileName: 'flutter.md',
|
|
344
586
|
confidenceScore: 0.94,
|
|
345
|
-
evidence: ['pubspec.yaml'],
|
|
587
|
+
evidence: [withEvidencePrefix('pubspec.yaml')],
|
|
346
588
|
});
|
|
347
589
|
}
|
|
348
590
|
|
|
591
|
+
return detectionCandidates;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export async function detectUiScopeSignals({
|
|
595
|
+
targetDirectoryPath,
|
|
596
|
+
selectedStackFileName,
|
|
597
|
+
selectedBlueprintFileName,
|
|
598
|
+
packageManifest = null,
|
|
599
|
+
projectScopeKey = null,
|
|
600
|
+
projectScopeSourceLabel = 'project scope',
|
|
601
|
+
}) {
|
|
602
|
+
const signalReasons = [];
|
|
603
|
+
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
604
|
+
const resolvedPackageManifest = packageManifest || await readPackageJsonIfExists(targetDirectoryPath);
|
|
605
|
+
const nestedWorkspaceProjects = await collectNestedWorkspaceProjects(targetDirectoryPath);
|
|
606
|
+
|
|
607
|
+
const normalizedProjectScopeKey = String(projectScopeKey || '').trim().toLowerCase();
|
|
608
|
+
if (normalizedProjectScopeKey === 'frontend-only' || normalizedProjectScopeKey === 'both') {
|
|
609
|
+
signalReasons.push(`${projectScopeSourceLabel}: ${normalizedProjectScopeKey}`);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const selectedStackKey = String(selectedStackFileName || '').trim().toLowerCase();
|
|
613
|
+
if (selectedStackKey === 'react-native.md' || selectedStackKey === 'flutter.md') {
|
|
614
|
+
signalReasons.push(`selected stack implies UI runtime: ${selectedStackKey}`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const selectedBlueprintKey = String(selectedBlueprintFileName || '').trim().toLowerCase();
|
|
618
|
+
if (selectedBlueprintKey.includes('frontend') || selectedBlueprintKey.includes('landing') || selectedBlueprintKey.includes('mobile-app')) {
|
|
619
|
+
signalReasons.push(`selected blueprint implies UI scope: ${selectedBlueprintKey}`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const rootUiSignals = analyzeUiSignalsForMarkerSet(markerNames, resolvedPackageManifest);
|
|
623
|
+
signalReasons.push(...rootUiSignals.signalReasons);
|
|
624
|
+
|
|
625
|
+
const nestedUiSignals = nestedWorkspaceProjects
|
|
626
|
+
.map((nestedWorkspaceProject) => ({
|
|
627
|
+
...nestedWorkspaceProject,
|
|
628
|
+
...analyzeUiSignalsForMarkerSet(
|
|
629
|
+
nestedWorkspaceProject.markerNames,
|
|
630
|
+
nestedWorkspaceProject.packageManifest,
|
|
631
|
+
`workspace ${nestedWorkspaceProject.relativePath}`
|
|
632
|
+
),
|
|
633
|
+
}))
|
|
634
|
+
.filter((nestedWorkspaceProject) => nestedWorkspaceProject.signalReasons.length > 0);
|
|
635
|
+
|
|
636
|
+
for (const nestedUiSignal of nestedUiSignals) {
|
|
637
|
+
signalReasons.push(...nestedUiSignal.signalReasons);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const detectedUiMarkers = Array.from(new Set([
|
|
641
|
+
...rootUiSignals.detectedUiMarkers,
|
|
642
|
+
...nestedUiSignals.flatMap((nestedUiSignal) => nestedUiSignal.detectedUiMarkers),
|
|
643
|
+
]));
|
|
644
|
+
const detectedUiDependencies = Array.from(new Set([
|
|
645
|
+
...rootUiSignals.detectedUiDependencies,
|
|
646
|
+
...nestedUiSignals.flatMap((nestedUiSignal) => nestedUiSignal.detectedUiDependencies),
|
|
647
|
+
]));
|
|
648
|
+
|
|
649
|
+
const hasStrongUiMarker = rootUiSignals.hasStrongUiMarker
|
|
650
|
+
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasStrongUiMarker);
|
|
651
|
+
const hasUiDependencies = rootUiSignals.hasUiDependencies
|
|
652
|
+
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasUiDependencies);
|
|
653
|
+
const hasStructuralUiMarkers = rootUiSignals.hasStructuralUiMarkers
|
|
654
|
+
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasStructuralUiMarkers);
|
|
655
|
+
const isUiScopeLikely = signalReasons.length > 0
|
|
656
|
+
&& (hasStrongUiMarker || hasUiDependencies || hasStructuralUiMarkers || normalizedProjectScopeKey.length > 0);
|
|
657
|
+
const preferredUiWorkspaceEntry = nestedUiSignals.find((nestedUiSignal) => (
|
|
658
|
+
nestedUiSignal.hasStrongUiMarker
|
|
659
|
+
|| nestedUiSignal.hasUiDependencies
|
|
660
|
+
|| nestedUiSignal.hasStructuralUiMarkers
|
|
661
|
+
)) || null;
|
|
662
|
+
const frontendScanRootDirectoryPaths = (
|
|
663
|
+
!rootUiSignals.hasStrongUiMarker
|
|
664
|
+
&& !rootUiSignals.hasUiDependencies
|
|
665
|
+
&& !rootUiSignals.hasStructuralUiMarkers
|
|
666
|
+
&& nestedUiSignals.length > 0
|
|
667
|
+
)
|
|
668
|
+
? nestedUiSignals.map((nestedUiSignal) => nestedUiSignal.directoryPath)
|
|
669
|
+
: [];
|
|
670
|
+
const frontendEvidenceMetrics = isUiScopeLikely
|
|
671
|
+
? await collectFrontendEvidenceMetrics(targetDirectoryPath, markerNames, frontendScanRootDirectoryPaths)
|
|
672
|
+
: null;
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
isUiScopeLikely,
|
|
676
|
+
signalReasons,
|
|
677
|
+
detectedUiMarkers,
|
|
678
|
+
detectedUiDependencies,
|
|
679
|
+
frontendEvidenceMetrics,
|
|
680
|
+
packageManifest: preferredUiWorkspaceEntry?.packageManifest || resolvedPackageManifest,
|
|
681
|
+
workspaceUiEntries: nestedUiSignals.map((nestedUiSignal) => ({
|
|
682
|
+
relativePath: nestedUiSignal.relativePath,
|
|
683
|
+
detectedUiMarkers: nestedUiSignal.detectedUiMarkers,
|
|
684
|
+
detectedUiDependencies: nestedUiSignal.detectedUiDependencies,
|
|
685
|
+
})),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export async function detectProjectContext(targetDirectoryPath) {
|
|
690
|
+
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
691
|
+
const nestedWorkspaceProjects = await collectNestedWorkspaceProjects(targetDirectoryPath);
|
|
692
|
+
const detectionCandidates = [
|
|
693
|
+
...collectStackDetectionCandidates(markerNames),
|
|
694
|
+
...nestedWorkspaceProjects.flatMap((nestedWorkspaceProject) => (
|
|
695
|
+
collectStackDetectionCandidates(
|
|
696
|
+
nestedWorkspaceProject.markerNames,
|
|
697
|
+
nestedWorkspaceProject.relativePath
|
|
698
|
+
)
|
|
699
|
+
)),
|
|
700
|
+
];
|
|
701
|
+
const hasExistingProjectFiles = markerNames.size > 0;
|
|
702
|
+
|
|
349
703
|
if (detectionCandidates.length === 0) {
|
|
350
704
|
return {
|
|
351
705
|
hasExistingProjectFiles,
|