@ryuenn3123/agentic-senior-core 3.0.50 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent-context/review-checklists/pr-checklist.md +1 -0
- package/.agent-context/rules/api-docs.md +63 -47
- package/.agent-context/rules/architecture.md +133 -120
- package/.agent-context/rules/database-design.md +36 -18
- package/.agent-context/rules/docker-runtime.md +66 -43
- package/.agent-context/rules/efficiency-vs-hype.md +38 -17
- package/.agent-context/rules/error-handling.md +35 -16
- package/.agent-context/rules/event-driven.md +35 -18
- package/.agent-context/rules/frontend-architecture.md +103 -76
- package/.agent-context/rules/git-workflow.md +81 -197
- package/.agent-context/rules/microservices.md +42 -41
- package/.agent-context/rules/naming-conv.md +27 -8
- package/.agent-context/rules/performance.md +32 -12
- package/.agent-context/rules/realtime.md +26 -9
- package/.agent-context/rules/security.md +39 -20
- package/.agent-context/rules/testing.md +36 -16
- package/AGENTS.md +9 -9
- package/README.md +10 -1
- package/lib/cli/commands/init.mjs +1 -0
- package/lib/cli/compiler.mjs +1 -0
- package/lib/cli/detector/constants.mjs +135 -0
- package/lib/cli/detector/design-evidence/collector.mjs +256 -0
- package/lib/cli/detector/design-evidence/constants.mjs +39 -0
- package/lib/cli/detector/design-evidence/file-traversal.mjs +83 -0
- package/lib/cli/detector/design-evidence/structured-attribute-evidence.mjs +117 -0
- package/lib/cli/detector/design-evidence/summary.mjs +109 -0
- package/lib/cli/detector/design-evidence/utility-helpers.mjs +122 -0
- package/lib/cli/detector/design-evidence.mjs +25 -610
- package/lib/cli/detector/stack-detection.mjs +243 -0
- package/lib/cli/detector/ui-signals.mjs +150 -0
- package/lib/cli/detector/workspace-scan.mjs +177 -0
- package/lib/cli/detector.mjs +20 -688
- package/lib/cli/memory-continuity.mjs +1 -0
- package/lib/cli/project-scaffolder/design-contract/sections/audits.mjs +96 -0
- package/lib/cli/project-scaffolder/design-contract/sections/conceptual-anchor.mjs +116 -0
- package/lib/cli/project-scaffolder/design-contract/sections/execution-handoff.mjs +211 -0
- package/lib/cli/project-scaffolder/design-contract/seed-signals.mjs +79 -0
- package/lib/cli/project-scaffolder/design-contract/signal-vocab.mjs +64 -0
- package/lib/cli/project-scaffolder/design-contract/validation/anchor-validators.mjs +222 -0
- package/lib/cli/project-scaffolder/design-contract/validation/audit-validators.mjs +117 -0
- package/lib/cli/project-scaffolder/design-contract/validation/completeness.mjs +83 -0
- package/lib/cli/project-scaffolder/design-contract/validation/execution-validators.mjs +328 -0
- package/lib/cli/project-scaffolder/design-contract/validation/helpers.mjs +8 -0
- package/lib/cli/project-scaffolder/design-contract/validation/structural-validators.mjs +79 -0
- package/lib/cli/project-scaffolder/design-contract/validation/system-validators.mjs +256 -0
- package/lib/cli/project-scaffolder/design-contract/validation.mjs +59 -896
- package/lib/cli/project-scaffolder/design-contract.mjs +147 -557
- package/mcp.json +30 -9
- package/package.json +17 -2
- package/scripts/audit-cache-layer-contract.mjs +258 -0
- package/scripts/audit-caching-scope-hygiene.mjs +263 -0
- package/scripts/audit-file-size.mjs +219 -0
- package/scripts/audit-reflection-citations.mjs +163 -0
- package/scripts/audit-release-bundle.mjs +170 -0
- package/scripts/audit-rule-id-uniqueness.mjs +313 -0
- package/scripts/benchmark-evidence-bundle.mjs +1 -0
- package/scripts/build-release-benchmark-bundle.mjs +204 -0
- package/scripts/context-triggered-audit.mjs +1 -0
- package/scripts/documentation-boundary-audit.mjs +1 -0
- package/scripts/explain-on-demand-audit.mjs +2 -1
- package/scripts/frontend-usability-audit.mjs +10 -10
- package/scripts/llm-judge/checklist-loader.mjs +45 -0
- package/scripts/llm-judge/constants.mjs +66 -0
- package/scripts/llm-judge/diff-collection.mjs +74 -0
- package/scripts/llm-judge/prompting.mjs +78 -0
- package/scripts/llm-judge/providers.mjs +111 -0
- package/scripts/llm-judge/verdict.mjs +134 -0
- package/scripts/llm-judge.mjs +21 -482
- package/scripts/mcp-server/tool-registry.mjs +55 -0
- package/scripts/mcp-server/tools.mjs +137 -1
- package/scripts/migrate-rule-format/id-prefix-table.mjs +37 -0
- package/scripts/migrate-rule-format/parse-legacy.mjs +180 -0
- package/scripts/migrate-rule-format/render-new.mjs +169 -0
- package/scripts/migrate-rule-format/roundtrip-validate.mjs +89 -0
- package/scripts/migrate-rule-format.mjs +192 -0
- package/scripts/release-gate/constants.mjs +1 -1
- package/scripts/release-gate/static-checks.mjs +1 -1
- package/scripts/rules-guardian-audit.mjs +5 -2
- package/scripts/single-source-lazy-loading-audit.mjs +2 -1
- package/scripts/ui-design-judge/git-input.mjs +3 -0
- package/scripts/validate/config.mjs +3 -2
- package/scripts/validate/coverage-checks.mjs +1 -1
- package/scripts/validate.mjs +93 -1
package/lib/cli/detector.mjs
CHANGED
|
@@ -1,691 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stack Detector —
|
|
3
|
-
*
|
|
2
|
+
* Stack Detector — project context auto-detection. Aggregator that re-exports
|
|
3
|
+
* the public surface of the detector subsystem. Implementation lives under
|
|
4
|
+
* lib/cli/detector/* split per concern: workspace scan, UI signal analysis,
|
|
5
|
+
* and stack detection scoring.
|
|
6
|
+
*
|
|
7
|
+
* Public exports:
|
|
8
|
+
* collectProjectMarkers — read top-level markers in a directory
|
|
9
|
+
* detectProjectContext — top-level stack detection with ranked candidates
|
|
10
|
+
* detectUiScopeSignals — UI scope detection plus design evidence scan;
|
|
11
|
+
* returns frontendEvidenceMetrics and
|
|
12
|
+
* designEvidenceSummary when UI scope is detected
|
|
13
|
+
* buildDetectionSummary — human-readable detection summary text
|
|
14
|
+
* formatDetectionCandidates — formatter for ranked detection candidates
|
|
4
15
|
*/
|
|
5
|
-
import fs from 'node:fs/promises';
|
|
6
|
-
import path from 'node:path';
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const WORKSPACE_SCAN_MAX_DIRECTORIES = 120;
|
|
16
|
-
const WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES = new Set([
|
|
17
|
-
...FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES,
|
|
18
|
-
'.agent-context',
|
|
19
|
-
'.agents',
|
|
20
|
-
'.cursor',
|
|
21
|
-
'.gemini',
|
|
22
|
-
'.github',
|
|
23
|
-
'.idea',
|
|
24
|
-
'.vscode',
|
|
25
|
-
'.windsurf',
|
|
26
|
-
'.zed',
|
|
27
|
-
]);
|
|
28
|
-
const WORKSPACE_CONTAINER_DIRECTORY_NAMES = new Set([
|
|
29
|
-
'admin',
|
|
30
|
-
'admins',
|
|
31
|
-
'api',
|
|
32
|
-
'apis',
|
|
33
|
-
'app',
|
|
34
|
-
'apps',
|
|
35
|
-
'backend',
|
|
36
|
-
'backends',
|
|
37
|
-
'client',
|
|
38
|
-
'clients',
|
|
39
|
-
'dashboard',
|
|
40
|
-
'dashboards',
|
|
41
|
-
'frontend',
|
|
42
|
-
'frontends',
|
|
43
|
-
'mobile',
|
|
44
|
-
'mobiles',
|
|
45
|
-
'package',
|
|
46
|
-
'packages',
|
|
47
|
-
'pkg',
|
|
48
|
-
'server',
|
|
49
|
-
'servers',
|
|
50
|
-
'service',
|
|
51
|
-
'services',
|
|
52
|
-
'site',
|
|
53
|
-
'sites',
|
|
54
|
-
'ui',
|
|
55
|
-
'web',
|
|
56
|
-
'worker',
|
|
57
|
-
'workers',
|
|
58
|
-
]);
|
|
59
|
-
const WORKSPACE_ROOT_MARKER_FILE_NAMES = new Set([
|
|
60
|
-
'lerna.json',
|
|
61
|
-
'nx.json',
|
|
62
|
-
'pnpm-workspace.yaml',
|
|
63
|
-
'turbo.json',
|
|
64
|
-
]);
|
|
65
|
-
const DIRECT_UI_MARKER_NAMES = [
|
|
66
|
-
'src',
|
|
67
|
-
'next.config.js',
|
|
68
|
-
'next.config.mjs',
|
|
69
|
-
'next.config.ts',
|
|
70
|
-
'tailwind.config.js',
|
|
71
|
-
'tailwind.config.mjs',
|
|
72
|
-
'tailwind.config.ts',
|
|
73
|
-
'vite.config.js',
|
|
74
|
-
'vite.config.mjs',
|
|
75
|
-
'vite.config.ts',
|
|
76
|
-
'react-native.config.js',
|
|
77
|
-
'app',
|
|
78
|
-
'pages',
|
|
79
|
-
'components',
|
|
80
|
-
'public',
|
|
81
|
-
'styles',
|
|
82
|
-
'android',
|
|
83
|
-
'ios',
|
|
84
|
-
'index.html',
|
|
85
|
-
];
|
|
86
|
-
const PROJECT_MARKER_FILE_NAMES = new Set([
|
|
87
|
-
'Cargo.toml',
|
|
88
|
-
'Gemfile',
|
|
89
|
-
'build.gradle',
|
|
90
|
-
'build.gradle.kts',
|
|
91
|
-
'composer.json',
|
|
92
|
-
'go.mod',
|
|
93
|
-
'package.json',
|
|
94
|
-
'pom.xml',
|
|
95
|
-
'pubspec.yaml',
|
|
96
|
-
'pyproject.toml',
|
|
97
|
-
'react-native.config.js',
|
|
98
|
-
'requirements.txt',
|
|
99
|
-
'tsconfig.json',
|
|
100
|
-
...DIRECT_UI_MARKER_NAMES,
|
|
101
|
-
]);
|
|
102
|
-
const INTERNAL_GOVERNANCE_SURFACE_NAMES = new Set([
|
|
103
|
-
'.agent-context',
|
|
104
|
-
'.agent-instructions.md',
|
|
105
|
-
'.agentic-backup',
|
|
106
|
-
'.agents',
|
|
107
|
-
'.clauderc',
|
|
108
|
-
'.cursorrules',
|
|
109
|
-
'.cursor',
|
|
110
|
-
'.gemini',
|
|
111
|
-
'.github',
|
|
112
|
-
'.instructions.md',
|
|
113
|
-
'.vscode',
|
|
114
|
-
'.windsurf',
|
|
115
|
-
'.windsurfrules',
|
|
116
|
-
'.zed',
|
|
117
|
-
'AGENTS.md',
|
|
118
|
-
'CLAUDE.md',
|
|
119
|
-
'GEMINI.md',
|
|
120
|
-
'mcp.json',
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
function looksLikeWorkspaceSearchCandidate(directoryName) {
|
|
124
|
-
const normalizedDirectoryName = String(directoryName || '').trim().toLowerCase();
|
|
125
|
-
|
|
126
|
-
if (!normalizedDirectoryName) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(normalizedDirectoryName)) {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return [
|
|
135
|
-
'admin',
|
|
136
|
-
'api',
|
|
137
|
-
'app',
|
|
138
|
-
'backend',
|
|
139
|
-
'client',
|
|
140
|
-
'dashboard',
|
|
141
|
-
'frontend',
|
|
142
|
-
'mobile',
|
|
143
|
-
'package',
|
|
144
|
-
'server',
|
|
145
|
-
'service',
|
|
146
|
-
'site',
|
|
147
|
-
'ui',
|
|
148
|
-
'web',
|
|
149
|
-
'worker',
|
|
150
|
-
].some((keyword) => normalizedDirectoryName.includes(keyword));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function hasProjectMarkers(markerNames) {
|
|
154
|
-
return Array.from(markerNames).some((markerName) => (
|
|
155
|
-
PROJECT_MARKER_FILE_NAMES.has(markerName)
|
|
156
|
-
|| markerName.endsWith('.csproj')
|
|
157
|
-
|| markerName.endsWith('.sln')
|
|
158
|
-
));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function collectProjectMarkers(targetDirectoryPath) {
|
|
162
|
-
const markerNames = new Set();
|
|
163
|
-
const directoryEntries = await fs.readdir(targetDirectoryPath, { withFileTypes: true });
|
|
164
|
-
|
|
165
|
-
for (const directoryEntry of directoryEntries) {
|
|
166
|
-
if (directoryEntry.name === '.git' || directoryEntry.name === 'node_modules') {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (INTERNAL_GOVERNANCE_SURFACE_NAMES.has(directoryEntry.name)) {
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
markerNames.add(directoryEntry.name);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return markerNames;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function readPackageJsonIfExists(targetDirectoryPath) {
|
|
181
|
-
const packageJsonPath = path.join(targetDirectoryPath, 'package.json');
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
|
|
185
|
-
return JSON.parse(packageJsonContent);
|
|
186
|
-
} catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function readDirectoryEntries(directoryPath) {
|
|
192
|
-
try {
|
|
193
|
-
return await fs.readdir(directoryPath, { withFileTypes: true });
|
|
194
|
-
} catch {
|
|
195
|
-
return [];
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async function collectNestedWorkspaceProjects(targetDirectoryPath) {
|
|
200
|
-
const rootDirectoryEntries = await readDirectoryEntries(targetDirectoryPath);
|
|
201
|
-
const rootMarkerNames = new Set(rootDirectoryEntries.map((directoryEntry) => directoryEntry.name));
|
|
202
|
-
const rootLooksLikeWorkspace = Array.from(rootMarkerNames).some((markerName) => (
|
|
203
|
-
WORKSPACE_ROOT_MARKER_FILE_NAMES.has(markerName)
|
|
204
|
-
|| looksLikeWorkspaceSearchCandidate(markerName)
|
|
205
|
-
));
|
|
206
|
-
const nestedWorkspaceProjects = [];
|
|
207
|
-
const queuedWorkspacePaths = new Set();
|
|
208
|
-
const workspaceQueue = [];
|
|
209
|
-
let scannedDirectoryCount = 0;
|
|
210
|
-
|
|
211
|
-
for (const rootDirectoryEntry of rootDirectoryEntries) {
|
|
212
|
-
if (!rootDirectoryEntry.isDirectory()) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES.has(rootDirectoryEntry.name)) {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const shouldInspectRootChild = rootLooksLikeWorkspace
|
|
221
|
-
|| looksLikeWorkspaceSearchCandidate(rootDirectoryEntry.name);
|
|
222
|
-
|
|
223
|
-
if (!shouldInspectRootChild) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const rootChildDirectoryPath = path.join(targetDirectoryPath, rootDirectoryEntry.name);
|
|
228
|
-
const rootChildEntries = await readDirectoryEntries(rootChildDirectoryPath);
|
|
229
|
-
const rootChildMarkerNames = new Set(rootChildEntries.map((directoryEntry) => directoryEntry.name));
|
|
230
|
-
const rootChildRelativePath = rootDirectoryEntry.name.replace(/\\/g, '/');
|
|
231
|
-
|
|
232
|
-
workspaceQueue.push({
|
|
233
|
-
directoryPath: rootChildDirectoryPath,
|
|
234
|
-
relativePath: rootChildRelativePath,
|
|
235
|
-
markerNames: rootChildMarkerNames,
|
|
236
|
-
depth: 1,
|
|
237
|
-
underWorkspaceContainer: WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(rootDirectoryEntry.name.toLowerCase()),
|
|
238
|
-
});
|
|
239
|
-
queuedWorkspacePaths.add(rootChildRelativePath);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
while (workspaceQueue.length > 0 && scannedDirectoryCount < WORKSPACE_SCAN_MAX_DIRECTORIES) {
|
|
243
|
-
const currentWorkspaceEntry = workspaceQueue.shift();
|
|
244
|
-
scannedDirectoryCount += 1;
|
|
245
|
-
|
|
246
|
-
const isProjectCandidate = hasProjectMarkers(currentWorkspaceEntry.markerNames);
|
|
247
|
-
const currentDirectoryName = path.basename(currentWorkspaceEntry.directoryPath).toLowerCase();
|
|
248
|
-
const isWorkspaceContainer = WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(currentDirectoryName);
|
|
249
|
-
|
|
250
|
-
if (isProjectCandidate) {
|
|
251
|
-
nestedWorkspaceProjects.push({
|
|
252
|
-
directoryPath: currentWorkspaceEntry.directoryPath,
|
|
253
|
-
relativePath: currentWorkspaceEntry.relativePath,
|
|
254
|
-
markerNames: currentWorkspaceEntry.markerNames,
|
|
255
|
-
packageManifest: currentWorkspaceEntry.markerNames.has('package.json')
|
|
256
|
-
? await readPackageJsonIfExists(currentWorkspaceEntry.directoryPath)
|
|
257
|
-
: null,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (currentWorkspaceEntry.depth >= WORKSPACE_SCAN_MAX_DEPTH) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const shouldTraverseChildren = currentWorkspaceEntry.underWorkspaceContainer
|
|
266
|
-
|| isWorkspaceContainer
|
|
267
|
-
|| !isProjectCandidate;
|
|
268
|
-
|
|
269
|
-
if (!shouldTraverseChildren) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const childEntries = await readDirectoryEntries(currentWorkspaceEntry.directoryPath);
|
|
274
|
-
for (const childEntry of childEntries) {
|
|
275
|
-
if (!childEntry.isDirectory()) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (WORKSPACE_SCAN_IGNORE_DIRECTORY_NAMES.has(childEntry.name)) {
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const childLooksRelevant = looksLikeWorkspaceSearchCandidate(childEntry.name);
|
|
284
|
-
if (!childLooksRelevant && !currentWorkspaceEntry.underWorkspaceContainer && !isWorkspaceContainer) {
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const childDirectoryPath = path.join(currentWorkspaceEntry.directoryPath, childEntry.name);
|
|
289
|
-
const childRelativePath = path.join(currentWorkspaceEntry.relativePath, childEntry.name).replace(/\\/g, '/');
|
|
290
|
-
|
|
291
|
-
if (queuedWorkspacePaths.has(childRelativePath)) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const childDirectoryEntries = await readDirectoryEntries(childDirectoryPath);
|
|
296
|
-
const childMarkerNames = new Set(childDirectoryEntries.map((directoryEntry) => directoryEntry.name));
|
|
297
|
-
const childIsProjectCandidate = hasProjectMarkers(childMarkerNames);
|
|
298
|
-
const childIsWorkspaceContainer = WORKSPACE_CONTAINER_DIRECTORY_NAMES.has(childEntry.name.toLowerCase());
|
|
299
|
-
|
|
300
|
-
if (!childIsProjectCandidate && !childIsWorkspaceContainer && !childLooksRelevant) {
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
workspaceQueue.push({
|
|
305
|
-
directoryPath: childDirectoryPath,
|
|
306
|
-
relativePath: childRelativePath,
|
|
307
|
-
markerNames: childMarkerNames,
|
|
308
|
-
depth: currentWorkspaceEntry.depth + 1,
|
|
309
|
-
underWorkspaceContainer: currentWorkspaceEntry.underWorkspaceContainer || isWorkspaceContainer || childIsWorkspaceContainer,
|
|
310
|
-
});
|
|
311
|
-
queuedWorkspacePaths.add(childRelativePath);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return nestedWorkspaceProjects;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function analyzeUiSignalsForMarkerSet(markerNames, packageManifest, sourceLabel = null) {
|
|
319
|
-
const detectedUiMarkers = DIRECT_UI_MARKER_NAMES.filter((markerName) => markerNames.has(markerName));
|
|
320
|
-
const dependencySource = {
|
|
321
|
-
...(packageManifest?.dependencies || {}),
|
|
322
|
-
...(packageManifest?.devDependencies || {}),
|
|
323
|
-
};
|
|
324
|
-
const detectableUiDependencies = [
|
|
325
|
-
'next',
|
|
326
|
-
'react',
|
|
327
|
-
'react-dom',
|
|
328
|
-
'react-native',
|
|
329
|
-
'expo',
|
|
330
|
-
'tailwindcss',
|
|
331
|
-
];
|
|
332
|
-
const detectedUiDependencies = detectableUiDependencies.filter((dependencyName) => dependencySource[dependencyName]);
|
|
333
|
-
const hasStrongUiMarker = detectedUiMarkers.some((markerName) => (
|
|
334
|
-
markerName.startsWith('next.config')
|
|
335
|
-
|| markerName === 'react-native.config.js'
|
|
336
|
-
|| markerName === 'android'
|
|
337
|
-
|| markerName === 'ios'
|
|
338
|
-
));
|
|
339
|
-
const hasUiDependencies = detectedUiDependencies.length > 0;
|
|
340
|
-
const hasStructuralUiMarkers = detectedUiMarkers.length >= 2;
|
|
341
|
-
const signalReasons = [];
|
|
342
|
-
const sourcePrefix = sourceLabel ? `${sourceLabel}: ` : '';
|
|
343
|
-
|
|
344
|
-
if (detectedUiMarkers.length > 0) {
|
|
345
|
-
signalReasons.push(`${sourcePrefix}ui markers: ${detectedUiMarkers.join(', ')}`);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (detectedUiDependencies.length > 0) {
|
|
349
|
-
signalReasons.push(`${sourcePrefix}ui dependencies: ${detectedUiDependencies.join(', ')}`);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
signalReasons,
|
|
354
|
-
detectedUiMarkers,
|
|
355
|
-
detectedUiDependencies,
|
|
356
|
-
hasStrongUiMarker,
|
|
357
|
-
hasUiDependencies,
|
|
358
|
-
hasStructuralUiMarkers,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function collectStackDetectionCandidates(markerNames, evidencePrefix = null) {
|
|
363
|
-
const detectionCandidates = [];
|
|
364
|
-
const withEvidencePrefix = (evidenceItem) => evidencePrefix ? `${evidencePrefix}: ${evidenceItem}` : evidenceItem;
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
markerNames.has('package.json')
|
|
368
|
-
|| markerNames.has('tsconfig.json')
|
|
369
|
-
|| markerNames.has('next.config.js')
|
|
370
|
-
|| markerNames.has('next.config.mjs')
|
|
371
|
-
|| markerNames.has('vite.config.js')
|
|
372
|
-
|| markerNames.has('vite.config.mjs')
|
|
373
|
-
|| markerNames.has('vite.config.ts')
|
|
374
|
-
) {
|
|
375
|
-
const evidence = [];
|
|
376
|
-
let confidenceScore = 0.7;
|
|
377
|
-
|
|
378
|
-
if (markerNames.has('package.json')) {
|
|
379
|
-
evidence.push(withEvidencePrefix('package.json'));
|
|
380
|
-
confidenceScore += 0.12;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (markerNames.has('tsconfig.json')) {
|
|
384
|
-
evidence.push(withEvidencePrefix('tsconfig.json'));
|
|
385
|
-
confidenceScore += 0.12;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (markerNames.has('next.config.js') || markerNames.has('next.config.mjs')) {
|
|
389
|
-
evidence.push(withEvidencePrefix('Next.js config'));
|
|
390
|
-
confidenceScore += 0.05;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (markerNames.has('vite.config.js') || markerNames.has('vite.config.mjs') || markerNames.has('vite.config.ts')) {
|
|
394
|
-
evidence.push(withEvidencePrefix('Vite config'));
|
|
395
|
-
confidenceScore += 0.08;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
detectionCandidates.push({
|
|
399
|
-
stackFileName: 'typescript.md',
|
|
400
|
-
confidenceScore: Math.min(confidenceScore, 0.97),
|
|
401
|
-
evidence,
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (markerNames.has('pyproject.toml') || markerNames.has('requirements.txt')) {
|
|
406
|
-
detectionCandidates.push({
|
|
407
|
-
stackFileName: 'python.md',
|
|
408
|
-
confidenceScore: markerNames.has('pyproject.toml') ? 0.96 : 0.78,
|
|
409
|
-
evidence: markerNames.has('pyproject.toml')
|
|
410
|
-
? [withEvidencePrefix('pyproject.toml')]
|
|
411
|
-
: [withEvidencePrefix('requirements.txt')],
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (markerNames.has('pom.xml') || markerNames.has('build.gradle') || markerNames.has('build.gradle.kts')) {
|
|
416
|
-
const evidence = [];
|
|
417
|
-
if (markerNames.has('pom.xml')) evidence.push(withEvidencePrefix('pom.xml'));
|
|
418
|
-
if (markerNames.has('build.gradle') || markerNames.has('build.gradle.kts')) evidence.push(withEvidencePrefix('Gradle build file'));
|
|
419
|
-
detectionCandidates.push({
|
|
420
|
-
stackFileName: 'java.md',
|
|
421
|
-
confidenceScore: markerNames.has('pom.xml') ? 0.95 : 0.84,
|
|
422
|
-
evidence,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (markerNames.has('composer.json')) {
|
|
427
|
-
detectionCandidates.push({
|
|
428
|
-
stackFileName: 'php.md',
|
|
429
|
-
confidenceScore: 0.95,
|
|
430
|
-
evidence: [withEvidencePrefix('composer.json')],
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (markerNames.has('go.mod')) {
|
|
435
|
-
detectionCandidates.push({
|
|
436
|
-
stackFileName: 'go.md',
|
|
437
|
-
confidenceScore: 0.96,
|
|
438
|
-
evidence: [withEvidencePrefix('go.mod')],
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (markerNames.has('Cargo.toml')) {
|
|
443
|
-
detectionCandidates.push({
|
|
444
|
-
stackFileName: 'rust.md',
|
|
445
|
-
confidenceScore: 0.96,
|
|
446
|
-
evidence: [withEvidencePrefix('Cargo.toml')],
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (markerNames.has('Gemfile')) {
|
|
451
|
-
detectionCandidates.push({
|
|
452
|
-
stackFileName: 'ruby.md',
|
|
453
|
-
confidenceScore: 0.95,
|
|
454
|
-
evidence: [withEvidencePrefix('Gemfile')],
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const hasDotNetMarker = Array.from(markerNames).some((markerName) => markerName.endsWith('.sln') || markerName.endsWith('.csproj'));
|
|
459
|
-
if (hasDotNetMarker) {
|
|
460
|
-
detectionCandidates.push({
|
|
461
|
-
stackFileName: 'csharp.md',
|
|
462
|
-
confidenceScore: 0.95,
|
|
463
|
-
evidence: [withEvidencePrefix('.sln or .csproj file')],
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (markerNames.has('package.json') && (markerNames.has('android') || markerNames.has('ios') || markerNames.has('react-native.config.js'))) {
|
|
468
|
-
detectionCandidates.push({
|
|
469
|
-
stackFileName: 'react-native.md',
|
|
470
|
-
confidenceScore: 0.9,
|
|
471
|
-
evidence: [withEvidencePrefix('package.json'), withEvidencePrefix('mobile runtime markers')],
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (markerNames.has('pubspec.yaml')) {
|
|
476
|
-
detectionCandidates.push({
|
|
477
|
-
stackFileName: 'flutter.md',
|
|
478
|
-
confidenceScore: 0.94,
|
|
479
|
-
evidence: [withEvidencePrefix('pubspec.yaml')],
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return detectionCandidates;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
export async function detectUiScopeSignals({
|
|
487
|
-
targetDirectoryPath,
|
|
488
|
-
selectedStackFileName,
|
|
489
|
-
selectedBlueprintFileName,
|
|
490
|
-
packageManifest = null,
|
|
491
|
-
projectScopeKey = null,
|
|
492
|
-
projectScopeSourceLabel = 'project scope',
|
|
493
|
-
}) {
|
|
494
|
-
const signalReasons = [];
|
|
495
|
-
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
496
|
-
const resolvedPackageManifest = packageManifest || await readPackageJsonIfExists(targetDirectoryPath);
|
|
497
|
-
const nestedWorkspaceProjects = await collectNestedWorkspaceProjects(targetDirectoryPath);
|
|
498
|
-
|
|
499
|
-
const normalizedProjectScopeKey = String(projectScopeKey || '').trim().toLowerCase();
|
|
500
|
-
if (normalizedProjectScopeKey === 'frontend-only' || normalizedProjectScopeKey === 'both') {
|
|
501
|
-
signalReasons.push(`${projectScopeSourceLabel}: ${normalizedProjectScopeKey}`);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const selectedStackKey = String(selectedStackFileName || '').trim().toLowerCase();
|
|
505
|
-
if (selectedStackKey === 'react-native.md' || selectedStackKey === 'flutter.md') {
|
|
506
|
-
signalReasons.push(`selected stack implies UI runtime: ${selectedStackKey}`);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const selectedBlueprintKey = String(selectedBlueprintFileName || '').trim().toLowerCase();
|
|
510
|
-
if (selectedBlueprintKey.includes('frontend') || selectedBlueprintKey.includes('landing') || selectedBlueprintKey.includes('mobile-app')) {
|
|
511
|
-
signalReasons.push(`selected blueprint implies UI scope: ${selectedBlueprintKey}`);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const rootUiSignals = analyzeUiSignalsForMarkerSet(markerNames, resolvedPackageManifest);
|
|
515
|
-
signalReasons.push(...rootUiSignals.signalReasons);
|
|
516
|
-
|
|
517
|
-
const nestedUiSignals = nestedWorkspaceProjects
|
|
518
|
-
.map((nestedWorkspaceProject) => ({
|
|
519
|
-
...nestedWorkspaceProject,
|
|
520
|
-
...analyzeUiSignalsForMarkerSet(
|
|
521
|
-
nestedWorkspaceProject.markerNames,
|
|
522
|
-
nestedWorkspaceProject.packageManifest,
|
|
523
|
-
`workspace ${nestedWorkspaceProject.relativePath}`
|
|
524
|
-
),
|
|
525
|
-
}))
|
|
526
|
-
.filter((nestedWorkspaceProject) => nestedWorkspaceProject.signalReasons.length > 0);
|
|
527
|
-
|
|
528
|
-
for (const nestedUiSignal of nestedUiSignals) {
|
|
529
|
-
signalReasons.push(...nestedUiSignal.signalReasons);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const detectedUiMarkers = Array.from(new Set([
|
|
533
|
-
...rootUiSignals.detectedUiMarkers,
|
|
534
|
-
...nestedUiSignals.flatMap((nestedUiSignal) => nestedUiSignal.detectedUiMarkers),
|
|
535
|
-
]));
|
|
536
|
-
const detectedUiDependencies = Array.from(new Set([
|
|
537
|
-
...rootUiSignals.detectedUiDependencies,
|
|
538
|
-
...nestedUiSignals.flatMap((nestedUiSignal) => nestedUiSignal.detectedUiDependencies),
|
|
539
|
-
]));
|
|
540
|
-
|
|
541
|
-
const hasStrongUiMarker = rootUiSignals.hasStrongUiMarker
|
|
542
|
-
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasStrongUiMarker);
|
|
543
|
-
const hasUiDependencies = rootUiSignals.hasUiDependencies
|
|
544
|
-
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasUiDependencies);
|
|
545
|
-
const hasStructuralUiMarkers = rootUiSignals.hasStructuralUiMarkers
|
|
546
|
-
|| nestedUiSignals.some((nestedUiSignal) => nestedUiSignal.hasStructuralUiMarkers);
|
|
547
|
-
const isUiScopeLikely = signalReasons.length > 0
|
|
548
|
-
&& (hasStrongUiMarker || hasUiDependencies || hasStructuralUiMarkers || normalizedProjectScopeKey.length > 0);
|
|
549
|
-
const preferredUiWorkspaceEntry = nestedUiSignals.find((nestedUiSignal) => (
|
|
550
|
-
nestedUiSignal.hasStrongUiMarker
|
|
551
|
-
|| nestedUiSignal.hasUiDependencies
|
|
552
|
-
|| nestedUiSignal.hasStructuralUiMarkers
|
|
553
|
-
)) || null;
|
|
554
|
-
const frontendScanRootDirectoryPaths = (
|
|
555
|
-
!rootUiSignals.hasStrongUiMarker
|
|
556
|
-
&& !rootUiSignals.hasUiDependencies
|
|
557
|
-
&& !rootUiSignals.hasStructuralUiMarkers
|
|
558
|
-
&& nestedUiSignals.length > 0
|
|
559
|
-
)
|
|
560
|
-
? nestedUiSignals.map((nestedUiSignal) => nestedUiSignal.directoryPath)
|
|
561
|
-
: [];
|
|
562
|
-
const designEvidence = isUiScopeLikely
|
|
563
|
-
? await collectFrontendDesignEvidence({
|
|
564
|
-
targetDirectoryPath,
|
|
565
|
-
markerNames,
|
|
566
|
-
scanRootDirectoryPaths: frontendScanRootDirectoryPaths,
|
|
567
|
-
})
|
|
568
|
-
: null;
|
|
569
|
-
const frontendEvidenceMetrics = designEvidence?.frontendEvidenceMetrics || null;
|
|
570
|
-
const designEvidenceSummary = designEvidence?.designEvidenceSummary || null;
|
|
571
|
-
|
|
572
|
-
return {
|
|
573
|
-
isUiScopeLikely,
|
|
574
|
-
signalReasons,
|
|
575
|
-
detectedUiMarkers,
|
|
576
|
-
detectedUiDependencies,
|
|
577
|
-
frontendEvidenceMetrics,
|
|
578
|
-
designEvidenceSummary,
|
|
579
|
-
packageManifest: preferredUiWorkspaceEntry?.packageManifest || resolvedPackageManifest,
|
|
580
|
-
workspaceUiEntries: nestedUiSignals.map((nestedUiSignal) => ({
|
|
581
|
-
relativePath: nestedUiSignal.relativePath,
|
|
582
|
-
detectedUiMarkers: nestedUiSignal.detectedUiMarkers,
|
|
583
|
-
detectedUiDependencies: nestedUiSignal.detectedUiDependencies,
|
|
584
|
-
})),
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export async function detectProjectContext(targetDirectoryPath) {
|
|
589
|
-
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
590
|
-
const nestedWorkspaceProjects = await collectNestedWorkspaceProjects(targetDirectoryPath);
|
|
591
|
-
const detectionCandidates = [
|
|
592
|
-
...collectStackDetectionCandidates(markerNames),
|
|
593
|
-
...nestedWorkspaceProjects.flatMap((nestedWorkspaceProject) => (
|
|
594
|
-
collectStackDetectionCandidates(
|
|
595
|
-
nestedWorkspaceProject.markerNames,
|
|
596
|
-
nestedWorkspaceProject.relativePath
|
|
597
|
-
)
|
|
598
|
-
)),
|
|
599
|
-
];
|
|
600
|
-
const hasExistingProjectFiles = markerNames.size > 0;
|
|
601
|
-
|
|
602
|
-
if (detectionCandidates.length === 0) {
|
|
603
|
-
return {
|
|
604
|
-
hasExistingProjectFiles,
|
|
605
|
-
detectedStackFileName: null,
|
|
606
|
-
secondaryStackFileNames: [],
|
|
607
|
-
detectedBlueprintFileName: null,
|
|
608
|
-
confidenceLabel: null,
|
|
609
|
-
confidenceScore: 0,
|
|
610
|
-
confidenceGap: 0,
|
|
611
|
-
detectionReasoning: 'No known project markers were detected.',
|
|
612
|
-
rankedCandidates: [],
|
|
613
|
-
evidence: [],
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
detectionCandidates.sort((leftCandidate, rightCandidate) => rightCandidate.confidenceScore - leftCandidate.confidenceScore);
|
|
618
|
-
const strongestCandidate = detectionCandidates[0];
|
|
619
|
-
const secondStrongestCandidate = detectionCandidates[1];
|
|
620
|
-
const confidenceGap = secondStrongestCandidate
|
|
621
|
-
? Number((strongestCandidate.confidenceScore - secondStrongestCandidate.confidenceScore).toFixed(2))
|
|
622
|
-
: Number(strongestCandidate.confidenceScore.toFixed(2));
|
|
623
|
-
const isAmbiguous = secondStrongestCandidate
|
|
624
|
-
&& confidenceGap < 0.08;
|
|
625
|
-
const confidenceLabel = strongestCandidate.confidenceScore >= 0.9
|
|
626
|
-
? 'high'
|
|
627
|
-
: strongestCandidate.confidenceScore >= 0.78
|
|
628
|
-
? 'medium'
|
|
629
|
-
: 'low';
|
|
630
|
-
const evidence = isAmbiguous
|
|
631
|
-
? [...strongestCandidate.evidence, `multiple stack signals detected`]
|
|
632
|
-
: strongestCandidate.evidence;
|
|
633
|
-
const rankedCandidates = detectionCandidates.slice(0, 3).map((detectionCandidate) => ({
|
|
634
|
-
stackFileName: detectionCandidate.stackFileName,
|
|
635
|
-
confidenceScore: Number(detectionCandidate.confidenceScore.toFixed(2)),
|
|
636
|
-
evidence: detectionCandidate.evidence,
|
|
637
|
-
}));
|
|
638
|
-
const secondaryStackFileNames = rankedCandidates
|
|
639
|
-
.slice(1)
|
|
640
|
-
.filter((rankedCandidate) => (strongestCandidate.confidenceScore - rankedCandidate.confidenceScore) < 0.08)
|
|
641
|
-
.map((rankedCandidate) => rankedCandidate.stackFileName);
|
|
642
|
-
const detectionReasoning = isAmbiguous
|
|
643
|
-
? `Top signal ${toTitleCase(strongestCandidate.stackFileName)} is close to ${toTitleCase(secondStrongestCandidate.stackFileName)} (confidence gap ${confidenceGap}).`
|
|
644
|
-
: `Top signal ${toTitleCase(strongestCandidate.stackFileName)} won with confidence ${strongestCandidate.confidenceScore.toFixed(2)} from markers: ${strongestCandidate.evidence.join(', ') || 'none'}.`;
|
|
645
|
-
|
|
646
|
-
return {
|
|
647
|
-
hasExistingProjectFiles,
|
|
648
|
-
detectedStackFileName: strongestCandidate.stackFileName,
|
|
649
|
-
secondaryStackFileNames,
|
|
650
|
-
detectedBlueprintFileName: null,
|
|
651
|
-
confidenceLabel,
|
|
652
|
-
confidenceScore: strongestCandidate.confidenceScore,
|
|
653
|
-
confidenceGap,
|
|
654
|
-
detectionReasoning,
|
|
655
|
-
rankedCandidates,
|
|
656
|
-
evidence,
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
export function buildDetectionSummary(projectDetection) {
|
|
661
|
-
if (!projectDetection.detectedStackFileName) {
|
|
662
|
-
return 'I did not find enough stack markers to auto-detect this project confidently.';
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
const readableEvidence = projectDetection.evidence.length > 0
|
|
666
|
-
? projectDetection.evidence.join(', ')
|
|
667
|
-
: 'basic project markers';
|
|
668
|
-
|
|
669
|
-
const confidenceGapSummary = typeof projectDetection.confidenceGap === 'number'
|
|
670
|
-
? ` Confidence gap: ${projectDetection.confidenceGap}.`
|
|
671
|
-
: '';
|
|
672
|
-
|
|
673
|
-
const secondaryStacksSummary = projectDetection.secondaryStackFileNames?.length
|
|
674
|
-
? ` Secondary stack signals: ${projectDetection.secondaryStackFileNames.map((stackFileName) => toTitleCase(stackFileName)).join(', ')}.`
|
|
675
|
-
: '';
|
|
676
|
-
|
|
677
|
-
return `This folder looks like ${toTitleCase(projectDetection.detectedStackFileName)} with ${projectDetection.confidenceLabel} confidence based on ${readableEvidence}.${confidenceGapSummary}${secondaryStacksSummary}`;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
export function formatDetectionCandidates(rankedCandidates) {
|
|
681
|
-
if (!rankedCandidates?.length) {
|
|
682
|
-
return 'No ranked candidates available.';
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
return rankedCandidates
|
|
686
|
-
.map((candidate, candidateIndex) => {
|
|
687
|
-
const evidenceSummary = candidate.evidence?.length ? candidate.evidence.join(', ') : 'no direct markers';
|
|
688
|
-
return `${candidateIndex + 1}. ${toTitleCase(candidate.stackFileName)} (score ${candidate.confidenceScore}) via ${evidenceSummary}`;
|
|
689
|
-
})
|
|
690
|
-
.join('\n');
|
|
691
|
-
}
|
|
17
|
+
export { collectProjectMarkers } from './detector/workspace-scan.mjs';
|
|
18
|
+
export { detectUiScopeSignals } from './detector/ui-signals.mjs';
|
|
19
|
+
export {
|
|
20
|
+
buildDetectionSummary,
|
|
21
|
+
detectProjectContext,
|
|
22
|
+
formatDetectionCandidates,
|
|
23
|
+
} from './detector/stack-detection.mjs';
|