yap2app 1.1.0 → 1.1.2
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/bin/cli-esm.js +5 -4
- package/bin/cli.bundle.js +16388 -0
- package/bin/cli.js +16381 -12
- package/bridge.js +15 -7
- package/package.json +3 -3
- package/scanner.js +288 -26
package/bridge.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import http from 'http';
|
|
4
|
-
import fs from 'fs/promises';
|
|
5
2
|
import fsSync from 'fs';
|
|
6
3
|
import path from 'path';
|
|
4
|
+
import util from 'util';
|
|
7
5
|
import { scanCodebase } from './scanner.js';
|
|
8
6
|
|
|
7
|
+
const fs = fsSync.promises || {
|
|
8
|
+
readdir: util.promisify(fsSync.readdir),
|
|
9
|
+
readFile: util.promisify(fsSync.readFile),
|
|
10
|
+
writeFile: util.promisify(fsSync.writeFile),
|
|
11
|
+
mkdir: util.promisify(fsSync.mkdir),
|
|
12
|
+
stat: util.promisify(fsSync.stat)
|
|
13
|
+
};
|
|
14
|
+
|
|
9
15
|
const PORT = process.env.YAP2APP_PORT || 10420;
|
|
10
16
|
const PROJECT_ROOT = process.cwd();
|
|
11
17
|
|
|
@@ -91,11 +97,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
91
97
|
return;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
// 3. GET /api/context (Shallow Codebase Scanner)
|
|
95
|
-
if (req.method === 'GET' && req.url
|
|
100
|
+
// 3. GET /api/context (Shallow Codebase Scanner with Optional Prompt Query)
|
|
101
|
+
if (req.method === 'GET' && req.url.startsWith('/api/context')) {
|
|
96
102
|
try {
|
|
97
|
-
|
|
98
|
-
const
|
|
103
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
104
|
+
const promptQuery = parsedUrl.searchParams.get('q') || '';
|
|
105
|
+
console.log(`[Yap2App Bridge] 🔍 Scanning shallow codebase context in: ${PROJECT_ROOT}${promptQuery ? ` (ranking for query: "${promptQuery}")` : ''}`);
|
|
106
|
+
const context = await scanCodebase(PROJECT_ROOT, promptQuery);
|
|
99
107
|
res.writeHead(200, headers);
|
|
100
108
|
res.end(JSON.stringify(context));
|
|
101
109
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yap2app",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Autonomous Vibe-to-Prod Localhost Bridge & Model Context Protocol (MCP) Server",
|
|
5
|
-
"main": "bridge.js",
|
|
3
|
+
"version": "1.1.2",
|
|
6
4
|
"type": "module",
|
|
5
|
+
"description": "Autonomous Vibe-to-Prod Localhost Bridge & Model Context Protocol (MCP) Server",
|
|
6
|
+
"main": "bin/cli.bundle.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"yap2app": "./bin/cli.js"
|
|
9
9
|
},
|
package/scanner.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
1
|
import fsSync from 'fs';
|
|
3
2
|
import path from 'path';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
|
|
5
|
+
const fs = fsSync.promises || {
|
|
6
|
+
readdir: util.promisify(fsSync.readdir),
|
|
7
|
+
readFile: util.promisify(fsSync.readFile),
|
|
8
|
+
writeFile: util.promisify(fsSync.writeFile),
|
|
9
|
+
mkdir: util.promisify(fsSync.mkdir),
|
|
10
|
+
stat: util.promisify(fsSync.stat)
|
|
11
|
+
};
|
|
4
12
|
|
|
5
13
|
const IGNORED_DIRS = new Set([
|
|
6
14
|
'node_modules',
|
|
@@ -28,18 +36,32 @@ async function walkDir(dir, baseDir, maxDepth = 6, currentDepth = 0) {
|
|
|
28
36
|
if (currentDepth > maxDepth) return [];
|
|
29
37
|
let results = [];
|
|
30
38
|
try {
|
|
31
|
-
const
|
|
32
|
-
for (const
|
|
33
|
-
const
|
|
39
|
+
const rawEntries = await fs.readdir(dir);
|
|
40
|
+
for (const rawEntry of rawEntries) {
|
|
41
|
+
const entryName = typeof rawEntry === 'string' ? rawEntry : rawEntry.name;
|
|
42
|
+
const fullPath = path.join(dir, entryName);
|
|
34
43
|
const relativePath = path.relative(baseDir, fullPath);
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
let isDir = false;
|
|
46
|
+
let isFile = false;
|
|
47
|
+
if (typeof rawEntry === 'object' && rawEntry.isDirectory && rawEntry.isFile) {
|
|
48
|
+
isDir = rawEntry.isDirectory();
|
|
49
|
+
isFile = rawEntry.isFile();
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
const st = fsSync.statSync(fullPath);
|
|
53
|
+
isDir = st.isDirectory();
|
|
54
|
+
isFile = st.isFile();
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isDir) {
|
|
59
|
+
if (!IGNORED_DIRS.has(entryName) && !entryName.startsWith('.')) {
|
|
38
60
|
const sub = await walkDir(fullPath, baseDir, maxDepth, currentDepth + 1);
|
|
39
61
|
results = results.concat(sub);
|
|
40
62
|
}
|
|
41
|
-
} else if (
|
|
42
|
-
const ext = path.extname(
|
|
63
|
+
} else if (isFile) {
|
|
64
|
+
const ext = path.extname(entryName).toLowerCase();
|
|
43
65
|
if ([
|
|
44
66
|
'.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.hbs', '.handlebars',
|
|
45
67
|
'.html', '.css', '.scss', '.sass', '.less', '.vm', '.ftl', '.jsp', '.php',
|
|
@@ -49,7 +71,7 @@ async function walkDir(dir, baseDir, maxDepth = 6, currentDepth = 0) {
|
|
|
49
71
|
].includes(ext)) {
|
|
50
72
|
results.push({
|
|
51
73
|
relative: relativePath,
|
|
52
|
-
name:
|
|
74
|
+
name: entryName,
|
|
53
75
|
ext,
|
|
54
76
|
dir: path.relative(baseDir, dir)
|
|
55
77
|
});
|
|
@@ -84,11 +106,33 @@ function parsePackageJson(content) {
|
|
|
84
106
|
* Returns null if no verified signature matches with high confidence.
|
|
85
107
|
*/
|
|
86
108
|
function detectVerifiedFramework(manifests, allFiles, extensionHistogram) {
|
|
87
|
-
// 1. Google Brightspot / Glue-Bundle
|
|
88
|
-
|
|
109
|
+
// 1. Google Brightspot CMS / Glue-Bundle / Handlebars Frontend
|
|
110
|
+
const hasHbsFiles = (extensionHistogram['.hbs'] || 0) > 0 || (extensionHistogram['.handlebars'] || 0) > 0;
|
|
111
|
+
const hasBrightspotConfigs = allFiles.some(f =>
|
|
112
|
+
f.name === 'louhi.json' ||
|
|
113
|
+
f.name === '_fields.config.json' ||
|
|
114
|
+
f.name === '_data.json' ||
|
|
115
|
+
f.name === 'brightspot.json' ||
|
|
116
|
+
f.name.includes('_config.json') ||
|
|
117
|
+
f.relative.includes('styleguide/') ||
|
|
118
|
+
f.relative.includes('brightspot')
|
|
119
|
+
);
|
|
120
|
+
const hasBrightspotManifest = manifests['louhi.json'] ||
|
|
121
|
+
manifests['_fields.config.json'] ||
|
|
122
|
+
(manifests['package.json'] && (
|
|
123
|
+
manifests['package.json'].includes('google-marketing-bundle-glue') ||
|
|
124
|
+
manifests['package.json'].includes('brightspot') ||
|
|
125
|
+
manifests['package.json'].includes('handlebars')
|
|
126
|
+
)) ||
|
|
127
|
+
(manifests['pom.xml'] && (
|
|
128
|
+
manifests['pom.xml'].includes('brightspot') ||
|
|
129
|
+
manifests['pom.xml'].includes('psddev')
|
|
130
|
+
));
|
|
131
|
+
|
|
132
|
+
if (hasHbsFiles || hasBrightspotConfigs || hasBrightspotManifest) {
|
|
89
133
|
return {
|
|
90
134
|
framework: 'brightspot_glue',
|
|
91
|
-
displayName: 'Google Brightspot CMS /
|
|
135
|
+
displayName: 'Google Brightspot CMS / Handlebars'
|
|
92
136
|
};
|
|
93
137
|
}
|
|
94
138
|
|
|
@@ -116,28 +160,36 @@ function detectVerifiedFramework(manifests, allFiles, extensionHistogram) {
|
|
|
116
160
|
return { framework: 'angular', displayName: 'Angular Project' };
|
|
117
161
|
}
|
|
118
162
|
|
|
119
|
-
// 4.
|
|
163
|
+
// 4. Standalone UI file distribution inspection
|
|
164
|
+
const htmlCount = extensionHistogram['.html'] || 0;
|
|
165
|
+
const scssCount = (extensionHistogram['.scss'] || 0) + (extensionHistogram['.css'] || 0);
|
|
166
|
+
const jsCount = (extensionHistogram['.js'] || 0) + (extensionHistogram['.ts'] || 0);
|
|
167
|
+
|
|
168
|
+
// 5. Python Ecosystem (Only if no primary UI files found)
|
|
120
169
|
if (manifests['requirements.txt'] || manifests['pyproject.toml']) {
|
|
121
170
|
const pyReqs = (manifests['requirements.txt'] || '') + (manifests['pyproject.toml'] || '');
|
|
122
171
|
if (/django/i.test(pyReqs)) return { framework: 'django', displayName: 'Django' };
|
|
123
|
-
if (/fastapi/i.test(pyReqs)) return { framework: 'fastapi', displayName: 'FastAPI' };
|
|
124
|
-
if (/flask/i.test(pyReqs)) return { framework: 'flask', displayName: 'Flask' };
|
|
172
|
+
if (/fastapi/i.test(pyReqs) && htmlCount === 0 && scssCount === 0) return { framework: 'fastapi', displayName: 'FastAPI' };
|
|
173
|
+
if (/flask/i.test(pyReqs) && htmlCount === 0 && scssCount === 0) return { framework: 'flask', displayName: 'Flask' };
|
|
125
174
|
}
|
|
126
175
|
|
|
127
|
-
//
|
|
176
|
+
// 6. PHP Ecosystem
|
|
128
177
|
if (manifests['composer.json']) {
|
|
129
178
|
if (manifests['composer.json'].includes('laravel/framework')) return { framework: 'laravel', displayName: 'Laravel' };
|
|
130
179
|
if (manifests['composer.json'].includes('symfony/')) return { framework: 'symfony', displayName: 'Symfony' };
|
|
131
180
|
}
|
|
132
181
|
|
|
133
|
-
//
|
|
182
|
+
// 7. Java Ecosystem
|
|
134
183
|
if (manifests['pom.xml'] || manifests['build.gradle']) {
|
|
135
184
|
const jContent = (manifests['pom.xml'] || '') + (manifests['build.gradle'] || '');
|
|
136
185
|
if (jContent.includes('spring-boot')) return { framework: 'spring_boot', displayName: 'Spring Boot' };
|
|
137
186
|
}
|
|
138
187
|
|
|
139
|
-
//
|
|
140
|
-
return
|
|
188
|
+
// Fallback to Plain HTML / CSS / JS for standard web projects
|
|
189
|
+
return {
|
|
190
|
+
framework: 'plain_html_css_js',
|
|
191
|
+
displayName: 'Plain HTML / CSS / JS'
|
|
192
|
+
};
|
|
141
193
|
}
|
|
142
194
|
|
|
143
195
|
/**
|
|
@@ -243,6 +295,119 @@ async function extractUtilitiesAndIcons(projectRoot, allFiles, manifests) {
|
|
|
243
295
|
return uiEcosystem;
|
|
244
296
|
}
|
|
245
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Deep Design System & SCSS Mixins Extraction.
|
|
300
|
+
* Detects whether the workspace uses Google Glue, Tailwind CSS, Bootstrap, or custom SCSS mixins,
|
|
301
|
+
* and catalogs reusable project mixins, import paths, and class conventions.
|
|
302
|
+
*/
|
|
303
|
+
async function extractDesignSystemAndMixins(projectRoot, allFiles, manifests) {
|
|
304
|
+
const designSystem = {
|
|
305
|
+
name: 'custom_bem',
|
|
306
|
+
display_name: 'Custom BEM / SCSS',
|
|
307
|
+
is_glue: false,
|
|
308
|
+
is_tailwind: false,
|
|
309
|
+
is_bootstrap: false,
|
|
310
|
+
class_prefix: '',
|
|
311
|
+
mixins_available: [],
|
|
312
|
+
import_paths: [],
|
|
313
|
+
guidelines: []
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// 1. Detect Google Glue Design System
|
|
317
|
+
const hasGluePackage = Boolean(
|
|
318
|
+
manifests['package.json'] && (
|
|
319
|
+
manifests['package.json'].includes('@google/glue') ||
|
|
320
|
+
manifests['package.json'].includes('google-marketing-bundle-glue') ||
|
|
321
|
+
manifests['package.json'].includes('glue')
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
const hasGlueFiles = allFiles.some(f =>
|
|
325
|
+
f.name.toLowerCase().includes('glue') ||
|
|
326
|
+
f.relative.toLowerCase().includes('glue/')
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Check code samples/style files for glue references
|
|
330
|
+
let hasGlueClasses = false;
|
|
331
|
+
const styleAndTemplateFiles = allFiles.filter(f =>
|
|
332
|
+
['.scss', '.css', '.hbs', '.handlebars', '.html'].includes(f.ext)
|
|
333
|
+
).slice(0, 25);
|
|
334
|
+
|
|
335
|
+
for (const f of styleAndTemplateFiles) {
|
|
336
|
+
try {
|
|
337
|
+
const fullP = path.join(projectRoot, f.relative);
|
|
338
|
+
const content = await fs.readFile(fullP, 'utf8');
|
|
339
|
+
if (content.includes('glue-') || content.includes('@include glue-') || content.includes('@import "glue') || content.includes("@import 'glue")) {
|
|
340
|
+
hasGlueClasses = true;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
} catch {}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (hasGluePackage || hasGlueFiles || hasGlueClasses) {
|
|
347
|
+
designSystem.name = 'google_glue';
|
|
348
|
+
designSystem.display_name = 'Google Glue Web Design System';
|
|
349
|
+
designSystem.is_glue = true;
|
|
350
|
+
designSystem.class_prefix = 'glue-';
|
|
351
|
+
designSystem.guidelines = [
|
|
352
|
+
'MUST use official Google Glue HTML classes: glue-headline (glue-headline--1, glue-headline--2, glue-headline--3), glue-button (glue-button--primary, glue-button--secondary), glue-card (glue-card--elevated), glue-grid, glue-mod-elevated, glue-mod-color-primary, glue-eyebrow, glue-body.',
|
|
353
|
+
'In SCSS, MUST @import existing Glue stylesheets and use Glue mixins (@include glue-typography(...), @include glue-elevation(...), @include glue-button(...)) instead of creating redundant custom mixins, custom classes, or ad-hoc variables.',
|
|
354
|
+
'Maintain strict Google Glue design system alignment and BEM modifier syntax.'
|
|
355
|
+
];
|
|
356
|
+
} else if (
|
|
357
|
+
(manifests['package.json'] && manifests['package.json'].includes('tailwindcss')) ||
|
|
358
|
+
allFiles.some(f => f.name.startsWith('tailwind.config'))
|
|
359
|
+
) {
|
|
360
|
+
designSystem.name = 'tailwind';
|
|
361
|
+
designSystem.display_name = 'Tailwind CSS';
|
|
362
|
+
designSystem.is_tailwind = true;
|
|
363
|
+
designSystem.guidelines = [
|
|
364
|
+
'Use standard Tailwind utility classes directly on HTML elements.',
|
|
365
|
+
'Avoid custom ad-hoc CSS rules when standard Tailwind classes can be used.'
|
|
366
|
+
];
|
|
367
|
+
} else if (
|
|
368
|
+
(manifests['package.json'] && manifests['package.json'].includes('bootstrap')) ||
|
|
369
|
+
allFiles.some(f => f.name.includes('bootstrap'))
|
|
370
|
+
) {
|
|
371
|
+
designSystem.name = 'bootstrap';
|
|
372
|
+
designSystem.display_name = 'Bootstrap';
|
|
373
|
+
designSystem.is_bootstrap = true;
|
|
374
|
+
designSystem.guidelines = [
|
|
375
|
+
'Use Bootstrap utility classes and components (container, row, col, btn, card, etc.).'
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// 2. Scan SCSS Files for Available Mixins, Functions, and Import Paths
|
|
380
|
+
const scssFiles = allFiles.filter(f => f.ext === '.scss').slice(0, 35);
|
|
381
|
+
for (const sf of scssFiles) {
|
|
382
|
+
try {
|
|
383
|
+
const fullP = path.join(projectRoot, sf.relative);
|
|
384
|
+
const content = await fs.readFile(fullP, 'utf8');
|
|
385
|
+
|
|
386
|
+
// Extract @mixin declarations
|
|
387
|
+
const mixinMatches = content.matchAll(/@mixin\s+([a-zA-Z0-9_-]+)\s*(\([^)]*\))?/g);
|
|
388
|
+
for (const m of mixinMatches) {
|
|
389
|
+
if (designSystem.mixins_available.length < 35) {
|
|
390
|
+
designSystem.mixins_available.push({
|
|
391
|
+
name: m[1],
|
|
392
|
+
signature: m[0].trim(),
|
|
393
|
+
file: sf.relative
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// If this file contains mixins or variables, record as reusable import path
|
|
399
|
+
if (sf.name.startsWith('_') || sf.relative.includes('mixin') || sf.relative.includes('variable') || sf.relative.includes('theme') || sf.relative.includes('styleguide') || sf.relative.includes('glue')) {
|
|
400
|
+
const importPath = sf.relative.replace(/\.scss$/, '').replace(/^_/, '');
|
|
401
|
+
if (!designSystem.import_paths.includes(importPath) && designSystem.import_paths.length < 20) {
|
|
402
|
+
designSystem.import_paths.push(importPath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch {}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return designSystem;
|
|
409
|
+
}
|
|
410
|
+
|
|
246
411
|
/**
|
|
247
412
|
* Extracts reusable UI components with their export signatures and interface snippets.
|
|
248
413
|
*/
|
|
@@ -316,13 +481,100 @@ function extractPathAliases(manifests) {
|
|
|
316
481
|
}
|
|
317
482
|
} catch {}
|
|
318
483
|
}
|
|
319
|
-
return '
|
|
484
|
+
return '@/';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Deterministic Routing Topology Detection.
|
|
489
|
+
* Categorizes the project architecture into known routing paradigms.
|
|
490
|
+
*/
|
|
491
|
+
function detectRoutingTopology(projectRoot, allFiles, manifests) {
|
|
492
|
+
// 1. Brightspot CMS / Glue-Bundle
|
|
493
|
+
const isBrightspot = allFiles.some(f =>
|
|
494
|
+
f.name === 'louhi.json' ||
|
|
495
|
+
f.name === '_fields.config.json' ||
|
|
496
|
+
f.name === 'brightspot.json' ||
|
|
497
|
+
f.relative.includes('styleguide/') ||
|
|
498
|
+
f.relative.includes('brightspot')
|
|
499
|
+
) || Boolean(manifests['louhi.json'] || manifests['_fields.config.json']);
|
|
500
|
+
|
|
501
|
+
if (isBrightspot) {
|
|
502
|
+
return 'brightspot-glue';
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 2. Next.js App Router (src/app or app/)
|
|
506
|
+
const hasAppRouter = allFiles.some(f =>
|
|
507
|
+
(f.relative.startsWith('app/') || f.relative.startsWith('src/app/')) &&
|
|
508
|
+
(f.name.startsWith('page.') || f.name.startsWith('layout.') || f.name.startsWith('route.'))
|
|
509
|
+
);
|
|
510
|
+
if (hasAppRouter) {
|
|
511
|
+
return 'nextjs-app-router';
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// 3. Next.js Pages Router (src/pages or pages/)
|
|
515
|
+
const hasPagesRouter = allFiles.some(f =>
|
|
516
|
+
(f.relative.startsWith('pages/') || f.relative.startsWith('src/pages/')) &&
|
|
517
|
+
(f.name.startsWith('_app.') || f.name.startsWith('_document.') || f.name.startsWith('index.'))
|
|
518
|
+
);
|
|
519
|
+
if (hasPagesRouter) {
|
|
520
|
+
return 'nextjs-pages-router';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 4. Default Single Page Application / Static Architecture
|
|
524
|
+
return 'standard-spa';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Prompt-Aware Lexical Ranking of Reusable Components.
|
|
529
|
+
* Ranks components by semantic/lexical overlap with a given prompt or keywords,
|
|
530
|
+
* returning the top 15 most relevant components.
|
|
531
|
+
*/
|
|
532
|
+
export function rankComponentsByRelevance(components = [], prompt = '') {
|
|
533
|
+
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
534
|
+
return components.slice(0, 15);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Extract alphanumeric tokens from prompt (lowercased, min length 2)
|
|
538
|
+
const promptTokens = prompt
|
|
539
|
+
.toLowerCase()
|
|
540
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
541
|
+
.split(/\s+/)
|
|
542
|
+
.filter(t => t.length >= 2);
|
|
543
|
+
|
|
544
|
+
if (promptTokens.length === 0) {
|
|
545
|
+
return components.slice(0, 15);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const scored = components.map(comp => {
|
|
549
|
+
const compName = (typeof comp === 'string' ? comp : comp.name || comp.path || '').toLowerCase();
|
|
550
|
+
const compPath = (typeof comp === 'object' && comp.path ? comp.path : '').toLowerCase();
|
|
551
|
+
const compSig = (typeof comp === 'object' && comp.signature ? comp.signature : '').toLowerCase();
|
|
552
|
+
const compProps = (typeof comp === 'object' && comp.props ? comp.props : '').toLowerCase();
|
|
553
|
+
|
|
554
|
+
const targetText = `${compName} ${compPath} ${compSig} ${compProps}`;
|
|
555
|
+
let score = 0;
|
|
556
|
+
|
|
557
|
+
for (const token of promptTokens) {
|
|
558
|
+
if (compName.includes(token)) score += 10;
|
|
559
|
+
if (compPath.includes(token)) score += 5;
|
|
560
|
+
if (compSig.includes(token)) score += 3;
|
|
561
|
+
if (compProps.includes(token)) score += 2;
|
|
562
|
+
if (targetText.includes(token)) score += 1;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return { comp, score };
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Sort descending by score, maintaining original order on ties
|
|
569
|
+
scored.sort((a, b) => b.score - a.score);
|
|
570
|
+
|
|
571
|
+
return scored.slice(0, 15).map(item => item.comp);
|
|
320
572
|
}
|
|
321
573
|
|
|
322
574
|
/**
|
|
323
575
|
* AI-Driven Universal Codebase Analyzer for Yap2App.
|
|
324
576
|
*/
|
|
325
|
-
export async function scanCodebase(targetDir = process.cwd()) {
|
|
577
|
+
export async function scanCodebase(targetDir = process.cwd(), promptQuery = '') {
|
|
326
578
|
const projectRoot = path.resolve(targetDir);
|
|
327
579
|
const projectName = path.basename(projectRoot) || 'local-project';
|
|
328
580
|
|
|
@@ -375,8 +627,9 @@ export async function scanCodebase(targetDir = process.cwd()) {
|
|
|
375
627
|
} catch (e) {}
|
|
376
628
|
}
|
|
377
629
|
|
|
378
|
-
// 5. Deep Catalog of Component Signatures & Primitives
|
|
379
|
-
const
|
|
630
|
+
// 5. Deep Catalog of Component Signatures & Primitives (Ranked to top 15)
|
|
631
|
+
const rawComponentSignatures = await extractComponentSignatures(projectRoot, allFiles);
|
|
632
|
+
const componentSignatures = rankComponentsByRelevance(rawComponentSignatures, promptQuery);
|
|
380
633
|
const componentNames = componentSignatures.map(c => c.name);
|
|
381
634
|
const realComponents = componentSignatures.map(c => c.path);
|
|
382
635
|
|
|
@@ -386,15 +639,22 @@ export async function scanCodebase(targetDir = process.cwd()) {
|
|
|
386
639
|
// 7. Extract UI Library Ecosystem & Helpers
|
|
387
640
|
const uiEcosystem = await extractUtilitiesAndIcons(projectRoot, allFiles, manifestsFound);
|
|
388
641
|
|
|
389
|
-
// 8.
|
|
642
|
+
// 8. Extract Deep Design System & Reusable SCSS Mixins
|
|
643
|
+
const designSystem = await extractDesignSystemAndMixins(projectRoot, allFiles, manifestsFound);
|
|
644
|
+
uiEcosystem.design_system = designSystem;
|
|
645
|
+
|
|
646
|
+
// 9. Deterministic Framework Detection (Strict Verification)
|
|
390
647
|
const verified = detectVerifiedFramework(manifestsFound, allFiles, extensionHistogram);
|
|
391
648
|
const inferredFramework = verified ? verified.framework : null;
|
|
392
649
|
const frameworkDisplayName = verified ? verified.displayName : null;
|
|
393
650
|
|
|
394
|
-
//
|
|
651
|
+
// 10. Detect Routing Topology
|
|
652
|
+
const routingTopology = detectRoutingTopology(projectRoot, allFiles, manifestsFound);
|
|
653
|
+
|
|
654
|
+
// 11. Path Aliases
|
|
395
655
|
const pathAlias = extractPathAliases(manifestsFound);
|
|
396
656
|
|
|
397
|
-
//
|
|
657
|
+
// 12. Extract Routes & Pages
|
|
398
658
|
const routesDiscovered = allFiles
|
|
399
659
|
.filter(f => f.relative.includes('pages/') || f.relative.includes('app/') || f.name.startsWith('page.'))
|
|
400
660
|
.map(f => f.relative)
|
|
@@ -405,12 +665,14 @@ export async function scanCodebase(targetDir = process.cwd()) {
|
|
|
405
665
|
project_root: projectRoot,
|
|
406
666
|
framework: inferredFramework,
|
|
407
667
|
framework_display_name: frameworkDisplayName,
|
|
668
|
+
routing_topology: routingTopology,
|
|
408
669
|
extension_histogram: extensionHistogram,
|
|
409
670
|
manifests: manifestsFound,
|
|
410
671
|
codebase_samples: codeSamples,
|
|
411
672
|
existing_components: realComponents,
|
|
412
673
|
component_signatures: componentSignatures,
|
|
413
674
|
component_names: componentNames,
|
|
675
|
+
design_system: designSystem,
|
|
414
676
|
ui_ecosystem: uiEcosystem,
|
|
415
677
|
routes_discovered: routesDiscovered,
|
|
416
678
|
discovered_files: discoveredFiles.slice(0, 80),
|