readme-gen-analyzer 1.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.
Files changed (92) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +77 -0
  3. package/dist/analyzers/ast-feature.detector.d.ts +10 -0
  4. package/dist/analyzers/ast-feature.detector.js +151 -0
  5. package/dist/analyzers/definition.extractor.d.ts +9 -0
  6. package/dist/analyzers/definition.extractor.js +141 -0
  7. package/dist/analyzers/dependency.analyzer.d.ts +14 -0
  8. package/dist/analyzers/dependency.analyzer.js +30 -0
  9. package/dist/analyzers/devops.analyzer.d.ts +3 -0
  10. package/dist/analyzers/devops.analyzer.js +42 -0
  11. package/dist/analyzers/env.extractor.d.ts +7 -0
  12. package/dist/analyzers/env.extractor.js +46 -0
  13. package/dist/analyzers/example.analyzer.d.ts +6 -0
  14. package/dist/analyzers/example.analyzer.js +84 -0
  15. package/dist/analyzers/feature.detector.d.ts +4 -0
  16. package/dist/analyzers/feature.detector.js +68 -0
  17. package/dist/analyzers/package.parser.d.ts +28 -0
  18. package/dist/analyzers/package.parser.js +341 -0
  19. package/dist/analyzers/polyglot.extractors.d.ts +18 -0
  20. package/dist/analyzers/polyglot.extractors.js +153 -0
  21. package/dist/analyzers/route.extractor.d.ts +10 -0
  22. package/dist/analyzers/route.extractor.js +41 -0
  23. package/dist/analyzers/schema.analyzer.d.ts +3 -0
  24. package/dist/analyzers/schema.analyzer.js +48 -0
  25. package/dist/analyzers/semantic.refiner.d.ts +16 -0
  26. package/dist/analyzers/semantic.refiner.js +154 -0
  27. package/dist/analyzers/structure.analyzer.d.ts +18 -0
  28. package/dist/analyzers/structure.analyzer.js +150 -0
  29. package/dist/analyzers/trace.analyzer.d.ts +10 -0
  30. package/dist/analyzers/trace.analyzer.js +75 -0
  31. package/dist/index.d.ts +27 -0
  32. package/dist/index.js +44 -0
  33. package/dist/internal/analysis/chunker.d.ts +25 -0
  34. package/dist/internal/analysis/chunker.js +78 -0
  35. package/dist/internal/analysis/evidence.d.ts +17 -0
  36. package/dist/internal/analysis/evidence.js +130 -0
  37. package/dist/internal/analysis/techStack.d.ts +6 -0
  38. package/dist/internal/analysis/techStack.js +67 -0
  39. package/dist/internal/llm/llmClient.d.ts +69 -0
  40. package/dist/internal/llm/llmClient.js +204 -0
  41. package/dist/internal/pipeline/merge.d.ts +14 -0
  42. package/dist/internal/pipeline/merge.js +53 -0
  43. package/dist/internal/pipeline/persona.d.ts +7 -0
  44. package/dist/internal/pipeline/persona.js +28 -0
  45. package/dist/internal/pipeline/quality.d.ts +9 -0
  46. package/dist/internal/pipeline/quality.js +52 -0
  47. package/dist/internal/pipeline/readme.d.ts +3 -0
  48. package/dist/internal/pipeline/readme.js +80 -0
  49. package/dist/internal/pipeline/runPipeline.d.ts +57 -0
  50. package/dist/internal/pipeline/runPipeline.js +101 -0
  51. package/dist/internal/pipeline/stages.d.ts +5 -0
  52. package/dist/internal/pipeline/stages.js +85 -0
  53. package/dist/internal/pipeline/types.d.ts +98 -0
  54. package/dist/internal/pipeline/types.js +2 -0
  55. package/dist/types.d.ts +85 -0
  56. package/dist/types.js +2 -0
  57. package/dist/utils/scanner.d.ts +14 -0
  58. package/dist/utils/scanner.js +81 -0
  59. package/dist/utils/scriptsMarkdown.d.ts +9 -0
  60. package/dist/utils/scriptsMarkdown.js +131 -0
  61. package/package.json +19 -0
  62. package/src/analyzers/ast-feature.detector.ts +173 -0
  63. package/src/analyzers/definition.extractor.ts +156 -0
  64. package/src/analyzers/dependency.analyzer.ts +32 -0
  65. package/src/analyzers/devops.analyzer.ts +44 -0
  66. package/src/analyzers/env.extractor.ts +58 -0
  67. package/src/analyzers/example.analyzer.ts +96 -0
  68. package/src/analyzers/feature.detector.ts +65 -0
  69. package/src/analyzers/package.parser.ts +364 -0
  70. package/src/analyzers/polyglot.extractors.ts +169 -0
  71. package/src/analyzers/route.extractor.ts +54 -0
  72. package/src/analyzers/schema.analyzer.ts +50 -0
  73. package/src/analyzers/semantic.refiner.ts +163 -0
  74. package/src/analyzers/structure.analyzer.ts +156 -0
  75. package/src/analyzers/trace.analyzer.ts +75 -0
  76. package/src/index.ts +29 -0
  77. package/src/internal/analysis/chunker.ts +103 -0
  78. package/src/internal/analysis/evidence.ts +152 -0
  79. package/src/internal/analysis/techStack.ts +71 -0
  80. package/src/internal/llm/llmClient.ts +261 -0
  81. package/src/internal/pipeline/merge.ts +63 -0
  82. package/src/internal/pipeline/persona.ts +27 -0
  83. package/src/internal/pipeline/quality.ts +47 -0
  84. package/src/internal/pipeline/readme.ts +98 -0
  85. package/src/internal/pipeline/runPipeline.ts +153 -0
  86. package/src/internal/pipeline/stages.ts +89 -0
  87. package/src/internal/pipeline/types.ts +102 -0
  88. package/src/types.ts +100 -0
  89. package/src/utils/scanner.ts +48 -0
  90. package/src/utils/scriptsMarkdown.ts +140 -0
  91. package/test-local.ts +16 -0
  92. package/tsconfig.json +16 -0
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeatureDetector = void 0;
4
+ class FeatureDetector {
5
+ static FEATURE_MAP = {
6
+ 'express': 'REST API Framework',
7
+ 'fastify': 'High-performance REST API',
8
+ 'react': 'Frontend Development',
9
+ 'next': 'Server-side Rendering',
10
+ 'mongoose': 'MongoDB Database Support',
11
+ 'prisma': 'Modern Database ORM',
12
+ 'jest': 'Automated Unit Testing',
13
+ 'cypress': 'End-to-End Testing',
14
+ 'docker': 'Containerization Support',
15
+ 'passport': 'Authentication & Authorization',
16
+ 'jsonwebtoken': 'JWT Auth Tokens',
17
+ 'axios': 'External API Integration',
18
+ 'dotenv': 'Environment Configuration',
19
+ 'typescript': 'Static Type Safety',
20
+ 'turbo': 'Monorepo Pipeline Management',
21
+ 'stripe': 'Payment Processing',
22
+ 'redis': 'Cache & Session Management',
23
+ 'socket.io': 'Real-time Communication',
24
+ 'nest': 'Enterprise-grade Architecture',
25
+ 'fastapi': 'High-performance Python API',
26
+ 'flask': 'Lightweight Python Web Services',
27
+ 'django': 'Full-stack Python Framework',
28
+ 'starlette': 'ASGI Python framework',
29
+ 'uvicorn': 'ASGI server',
30
+ 'gunicorn': 'WSGI HTTP server',
31
+ 'celery': 'Distributed task queue',
32
+ 'sqlalchemy': 'SQL toolkit / ORM',
33
+ 'pydantic': 'Data validation',
34
+ 'httpx': 'HTTP client',
35
+ 'requests': 'HTTP client',
36
+ 'boto3': 'AWS SDK',
37
+ 'pytest': 'Python testing',
38
+ 'ruff': 'Python linter/formatter',
39
+ 'black': 'Python formatter',
40
+ 'gin-gonic/gin': 'Gin HTTP framework',
41
+ 'labstack/echo': 'Echo web framework',
42
+ 'gofiber/fiber': 'Fiber web framework',
43
+ 'go-chi/chi': 'chi router',
44
+ 'gorilla/mux': 'Gorilla mux router',
45
+ 'google.golang.org/grpc': 'gRPC',
46
+ 'grpc-go': 'gRPC Go',
47
+ 'spf13/cobra': 'Cobra CLI',
48
+ 'gorm.io/gorm': 'GORM ORM',
49
+ 'testify': 'Go assertions/mocks',
50
+ };
51
+ static detect(dependencies, devDependencies, hasDocker, hasRoutes) {
52
+ const features = new Set();
53
+ const allDeps = [...dependencies, ...devDependencies].map(d => d.toLowerCase());
54
+ for (const [dep, feature] of Object.entries(this.FEATURE_MAP)) {
55
+ if (allDeps.some(d => d.includes(dep))) {
56
+ features.add(feature);
57
+ }
58
+ }
59
+ if (hasDocker)
60
+ features.add('Docker Container Environments');
61
+ if (hasRoutes)
62
+ features.add('API Endpoint Management');
63
+ if (dependencies.length > 0)
64
+ features.add('Dependency-driven development');
65
+ return Array.from(features);
66
+ }
67
+ }
68
+ exports.FeatureDetector = FeatureDetector;
@@ -0,0 +1,28 @@
1
+ export interface PackageMetadata {
2
+ name: string;
3
+ version: string;
4
+ description: string;
5
+ scripts: Record<string, string>;
6
+ dependencies: {
7
+ production: string[];
8
+ development: string[];
9
+ peer: string[];
10
+ };
11
+ frameworks: string[];
12
+ packageManager: string;
13
+ }
14
+ export declare class PackageParser {
15
+ static parse(files: Record<string, string>): Promise<PackageMetadata | null>;
16
+ private static parseMultipleNode;
17
+ private static detectFramework;
18
+ private static parsePython;
19
+ private static detectPythonPm;
20
+ private static parseRequirementsTxt;
21
+ /** pip / requirements.txt line → distribution name */
22
+ private static parseRequirementLine;
23
+ private static parsePyProjectToml;
24
+ private static detectPythonFrameworks;
25
+ private static parseGo;
26
+ private static parseGoRequireBlock;
27
+ private static detectGoFrameworks;
28
+ }
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PackageParser = void 0;
4
+ class PackageParser {
5
+ static async parse(files) {
6
+ const packageFiles = Object.keys(files).filter(f => f.endsWith('package.json'));
7
+ if (packageFiles.length > 0) {
8
+ return this.parseMultipleNode(packageFiles, files);
9
+ }
10
+ const req = files['requirements.txt'] || '';
11
+ const pyproject = files['pyproject.toml'] || '';
12
+ if (req || pyproject) {
13
+ return this.parsePython(req, pyproject);
14
+ }
15
+ if (files['go.mod']) {
16
+ return this.parseGo(files['go.mod']);
17
+ }
18
+ return null;
19
+ }
20
+ static parseMultipleNode(packagePaths, allFiles) {
21
+ const mergedScripts = {};
22
+ const mergedDeps = new Set();
23
+ const mergedDevDeps = new Set();
24
+ const mergedPeerDeps = new Set();
25
+ const mergedFrameworks = new Set();
26
+ let primaryName = '';
27
+ let primaryDescription = '';
28
+ let primaryVersion = '0.0.0';
29
+ // Sort paths by depth (shallowest first) to pick primary metadata from root
30
+ const sortedPaths = [...packagePaths].sort((a, b) => a.split('/').length - b.split('/').length);
31
+ sortedPaths.forEach((path, index) => {
32
+ try {
33
+ const pkg = JSON.parse(allFiles[path]);
34
+ // Normalize path for splitting
35
+ const normalizedPath = path.replace(/\\/g, '/');
36
+ const parts = normalizedPath.split('/');
37
+ // If it's a nested package (e.g., apps/api/package.json), prefix with the directory path
38
+ let prefix = '';
39
+ if (parts.length > 1) {
40
+ const dirParts = parts.slice(0, -1);
41
+ prefix = dirParts.join('/') + ':';
42
+ }
43
+ // If no primary metadata yet, or if current one is more descriptive, pick it
44
+ if (!primaryName || (index === 0 && pkg.name)) {
45
+ primaryName = pkg.name || primaryName;
46
+ primaryDescription = pkg.description || primaryDescription;
47
+ primaryVersion = pkg.version || primaryVersion;
48
+ }
49
+ // Merge scripts with prefix if not root
50
+ if (pkg.scripts) {
51
+ Object.entries(pkg.scripts).forEach(([name, cmd]) => {
52
+ const key = prefix ? `${prefix}${name}` : name;
53
+ mergedScripts[key] = cmd;
54
+ });
55
+ }
56
+ // Merge dependencies
57
+ if (pkg.dependencies) {
58
+ Object.keys(pkg.dependencies).forEach(d => {
59
+ mergedDeps.add(d);
60
+ this.detectFramework(d, mergedFrameworks);
61
+ });
62
+ }
63
+ if (pkg.devDependencies) {
64
+ Object.keys(pkg.devDependencies).forEach(d => mergedDevDeps.add(d));
65
+ }
66
+ if (pkg.peerDependencies) {
67
+ Object.keys(pkg.peerDependencies).forEach(d => mergedPeerDeps.add(d));
68
+ }
69
+ }
70
+ catch (e) {
71
+ console.warn(`Failed to parse ${path}:`, e);
72
+ }
73
+ });
74
+ let packageManager = 'npm';
75
+ if (Object.keys(allFiles).some(f => f.includes('pnpm-lock.yaml')))
76
+ packageManager = 'pnpm';
77
+ else if (Object.keys(allFiles).some(f => f.includes('yarn.lock')))
78
+ packageManager = 'yarn';
79
+ else if (Object.keys(allFiles).some(f => f.endsWith('bun.lockb')))
80
+ packageManager = 'bun';
81
+ return {
82
+ name: primaryName || 'Unknown Node Project',
83
+ version: primaryVersion,
84
+ description: primaryDescription,
85
+ scripts: mergedScripts,
86
+ dependencies: {
87
+ production: Array.from(mergedDeps),
88
+ development: Array.from(mergedDevDeps),
89
+ peer: Array.from(mergedPeerDeps),
90
+ },
91
+ frameworks: Array.from(mergedFrameworks),
92
+ packageManager,
93
+ };
94
+ }
95
+ static detectFramework(dep, frameworks) {
96
+ const frameworkMap = {
97
+ 'express': 'Express',
98
+ 'react': 'React',
99
+ 'next': 'Next.js',
100
+ 'vue': 'Vue',
101
+ '@nestjs/core': 'NestJS',
102
+ 'koa': 'Koa',
103
+ 'fastify': 'Fastify',
104
+ 'socket.io': 'Socket.io',
105
+ 'mongoose': 'Mongoose',
106
+ 'prisma': 'Prisma',
107
+ 'tailwindcss': 'TailwindCSS',
108
+ 'vite': 'Vite'
109
+ };
110
+ if (frameworkMap[dep])
111
+ frameworks.add(frameworkMap[dep]);
112
+ }
113
+ static parsePython(reqs, pyproject) {
114
+ const py = pyproject ? this.parsePyProjectToml(pyproject) : null;
115
+ const reqDeps = this.parseRequirementsTxt(reqs);
116
+ const prod = Array.from(new Set([...(py?.deps || []), ...reqDeps]));
117
+ const dev = py?.devDeps || [];
118
+ const frameworks = this.detectPythonFrameworks(prod, dev, pyproject + reqs);
119
+ const scripts = { ...py?.scripts };
120
+ if (frameworks.includes('Django') && !scripts['manage']) {
121
+ scripts['manage'] = 'python manage.py <command>';
122
+ }
123
+ const packageManager = this.detectPythonPm(pyproject);
124
+ return {
125
+ name: py?.name || 'Python Project',
126
+ version: py?.version || '1.0.0',
127
+ description: py?.description || '',
128
+ scripts,
129
+ dependencies: {
130
+ production: prod,
131
+ development: dev,
132
+ peer: [],
133
+ },
134
+ frameworks,
135
+ packageManager,
136
+ };
137
+ }
138
+ static detectPythonPm(pyproject) {
139
+ if (!pyproject)
140
+ return 'pip';
141
+ if (/\[tool\.poetry\]/i.test(pyproject))
142
+ return 'poetry';
143
+ if (/\[tool\.uv\]/i.test(pyproject) || /uv\.lock/i.test(pyproject))
144
+ return 'uv';
145
+ if (/\[tool\.pdm\]/i.test(pyproject))
146
+ return 'pdm';
147
+ if (/\[tool\.hatch\]/i.test(pyproject))
148
+ return 'hatch';
149
+ return 'pip';
150
+ }
151
+ static parseRequirementsTxt(content) {
152
+ const names = [];
153
+ for (const line of content.split('\n')) {
154
+ const name = this.parseRequirementLine(line);
155
+ if (name)
156
+ names.push(name);
157
+ }
158
+ return names;
159
+ }
160
+ /** pip / requirements.txt line → distribution name */
161
+ static parseRequirementLine(line) {
162
+ const t = line.trim();
163
+ if (!t || t.startsWith('#') || t.startsWith('-'))
164
+ return null;
165
+ const head = t.split(/[;#]/)[0].trim();
166
+ if (!head)
167
+ return null;
168
+ const noVersion = head.split(/\s*[<>=~!]/)[0].trim();
169
+ const noExtra = noVersion.split(/[\[]/)[0].trim();
170
+ const name = noExtra.split(/[@\s]/)[0].trim();
171
+ if (!name || !/^[a-zA-Z0-9._-]+$/.test(name))
172
+ return null;
173
+ return name;
174
+ }
175
+ static parsePyProjectToml(content) {
176
+ const deps = [];
177
+ const devDeps = [];
178
+ const scripts = {};
179
+ let name;
180
+ let version;
181
+ let description;
182
+ let section = '';
183
+ const lines = content.split('\n');
184
+ for (const raw of lines) {
185
+ const line = raw.split('#')[0].trim();
186
+ const sec = line.match(/^\[([^\]]+)\]/);
187
+ if (sec) {
188
+ section = sec[1].toLowerCase();
189
+ continue;
190
+ }
191
+ if (section === 'project') {
192
+ const nm = line.match(/^name\s*=\s*["']([^"']+)["']/i);
193
+ const ver = line.match(/^version\s*=\s*["']([^"']+)["']/i);
194
+ const desc = line.match(/^description\s*=\s*["']([^"']*)["']/i);
195
+ if (nm)
196
+ name = nm[1];
197
+ if (ver)
198
+ version = ver[1];
199
+ if (desc)
200
+ description = desc[1];
201
+ }
202
+ if (section === 'tool.poetry.dependencies' ||
203
+ section === 'tool.poetry.group.dev.dependencies') {
204
+ const depKey = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
205
+ if (depKey && depKey[1].toLowerCase() !== 'python') {
206
+ if (section.includes('dev'))
207
+ devDeps.push(depKey[1]);
208
+ else
209
+ deps.push(depKey[1]);
210
+ }
211
+ }
212
+ if (section === 'tool.poetry.scripts') {
213
+ const sc = line.match(/^([a-zA-Z0-9_.-]+)\s*=\s*["']([^"']+)["']/);
214
+ if (sc)
215
+ scripts[sc[1]] = sc[2];
216
+ }
217
+ }
218
+ return { name, version, description, deps, devDeps, scripts };
219
+ }
220
+ static detectPythonFrameworks(prod, dev, raw) {
221
+ const all = [...prod, ...dev].map((d) => d.toLowerCase());
222
+ const text = raw.toLowerCase();
223
+ const frameworks = [];
224
+ const add = (f) => {
225
+ if (!frameworks.includes(f))
226
+ frameworks.push(f);
227
+ };
228
+ const has = (s) => all.some((d) => d.includes(s)) || text.includes(s);
229
+ if (has('fastapi'))
230
+ add('FastAPI');
231
+ if (has('flask'))
232
+ add('Flask');
233
+ if (has('django'))
234
+ add('Django');
235
+ if (has('starlette'))
236
+ add('Starlette');
237
+ if (has('tornado'))
238
+ add('Tornado');
239
+ if (has('sanic'))
240
+ add('Sanic');
241
+ if (has('litestar'))
242
+ add('Litestar');
243
+ if (has('sqlalchemy'))
244
+ add('SQLAlchemy');
245
+ if (has('pydantic'))
246
+ add('Pydantic');
247
+ if (has('celery'))
248
+ add('Celery');
249
+ if (has('pytest'))
250
+ add('pytest');
251
+ return frameworks;
252
+ }
253
+ static parseGo(content) {
254
+ const lines = content.split('\n');
255
+ const moduleLine = lines.find((l) => l.trim().startsWith('module '));
256
+ const name = moduleLine
257
+ ? moduleLine.replace(/^\s*module\s+/, '').trim().split(/\s+/)[0]
258
+ : 'Go Project';
259
+ const modules = this.parseGoRequireBlock(content);
260
+ const frameworks = this.detectGoFrameworks(modules);
261
+ const scripts = {
262
+ build: 'go build ./...',
263
+ test: 'go test ./...',
264
+ vet: 'go vet ./...',
265
+ mod: 'go mod tidy',
266
+ };
267
+ return {
268
+ name,
269
+ version: '1.0.0',
270
+ description: '',
271
+ scripts,
272
+ dependencies: {
273
+ production: modules,
274
+ development: [],
275
+ peer: [],
276
+ },
277
+ frameworks,
278
+ packageManager: 'go mod',
279
+ };
280
+ }
281
+ static parseGoRequireBlock(content) {
282
+ const mods = [];
283
+ const lines = content.split('\n');
284
+ let inBlock = false;
285
+ for (const raw of lines) {
286
+ const line = raw.trim();
287
+ if (line.startsWith('require (')) {
288
+ inBlock = true;
289
+ continue;
290
+ }
291
+ if (inBlock && line === ')') {
292
+ inBlock = false;
293
+ continue;
294
+ }
295
+ if (inBlock && line && !line.startsWith('//') && !line.startsWith('replace ')) {
296
+ const modPath = line.split(/\s+/)[0];
297
+ if (modPath)
298
+ mods.push(modPath);
299
+ continue;
300
+ }
301
+ if (line.startsWith('require ') && !line.includes('(')) {
302
+ const rest = line.replace(/^require\s+/, '').trim();
303
+ const modPath = rest.split(/\s+/)[0];
304
+ if (modPath && !modPath.startsWith('//'))
305
+ mods.push(modPath);
306
+ }
307
+ }
308
+ return Array.from(new Set(mods));
309
+ }
310
+ static detectGoFrameworks(modules) {
311
+ const frameworks = [];
312
+ const add = (f) => {
313
+ if (!frameworks.includes(f))
314
+ frameworks.push(f);
315
+ };
316
+ const s = modules.join(' ').toLowerCase();
317
+ const pairs = [
318
+ ['gin-gonic/gin', 'Gin'],
319
+ ['labstack/echo', 'Echo'],
320
+ ['gofiber/fiber', 'Fiber'],
321
+ ['go-chi/chi', 'chi'],
322
+ ['gorilla/mux', 'Gorilla mux'],
323
+ ['google.golang.org/grpc', 'gRPC'],
324
+ ['grpc-go', 'gRPC'],
325
+ ['grpc/grpc-go', 'gRPC'],
326
+ ['spf13/cobra', 'Cobra'],
327
+ ['spf13/viper', 'Viper'],
328
+ ['stretchr/testify', 'testify'],
329
+ ['jackc/pgx', 'pgx'],
330
+ ['go-redis/redis', 'Redis'],
331
+ ['gorm.io/gorm', 'GORM'],
332
+ ['entgo.io/ent', 'Ent'],
333
+ ];
334
+ for (const [needle, label] of pairs) {
335
+ if (s.includes(needle))
336
+ add(label);
337
+ }
338
+ return frameworks;
339
+ }
340
+ }
341
+ exports.PackageParser = PackageParser;
@@ -0,0 +1,18 @@
1
+ /** Same shape as RouteExtractor Route */
2
+ export interface PolyglotRoute {
3
+ method: string;
4
+ path: string;
5
+ file: string;
6
+ snippet?: string;
7
+ }
8
+ /** Routes & API surface for Python (FastAPI, Flask, Starlette-style) and Go (net/http, gin, echo, chi, mux, fiber). */
9
+ export declare class PolyglotExtractors {
10
+ static extractRoutes(files: Record<string, string>): PolyglotRoute[];
11
+ static extractDefinitions(files: Record<string, string>): Record<string, string[]>;
12
+ private static pythonRoutes;
13
+ private static flaskMethodsNear;
14
+ private static goRoutes;
15
+ private static pythonDefinitions;
16
+ private static goDefinitions;
17
+ private static snip;
18
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PolyglotExtractors = void 0;
4
+ /** Routes & API surface for Python (FastAPI, Flask, Starlette-style) and Go (net/http, gin, echo, chi, mux, fiber). */
5
+ class PolyglotExtractors {
6
+ static extractRoutes(files) {
7
+ const routes = [];
8
+ for (const [filePath, content] of Object.entries(files)) {
9
+ if (filePath.endsWith(".py")) {
10
+ routes.push(...this.pythonRoutes(filePath, content));
11
+ }
12
+ else if (filePath.endsWith(".go")) {
13
+ routes.push(...this.goRoutes(filePath, content));
14
+ }
15
+ }
16
+ return routes;
17
+ }
18
+ static extractDefinitions(files) {
19
+ const result = {};
20
+ for (const [filePath, content] of Object.entries(files)) {
21
+ if (filePath.endsWith(".py")) {
22
+ const defs = this.pythonDefinitions(content);
23
+ if (defs.length)
24
+ result[filePath] = defs;
25
+ }
26
+ else if (filePath.endsWith(".go")) {
27
+ const defs = this.goDefinitions(content);
28
+ if (defs.length)
29
+ result[filePath] = defs;
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ static pythonRoutes(file, content) {
35
+ const out = [];
36
+ const lines = content.split("\n");
37
+ // FastAPI / Starlette: @app.get("/path") @router.post( APIRouter
38
+ const decRe = /@(?:app|router|api|route)\.(get|post|put|delete|patch|options|head)\s*\(\s*["'`]([^"'`]+)["'`]/gi;
39
+ let m;
40
+ while ((m = decRe.exec(content)) !== null) {
41
+ out.push({
42
+ method: m[1].toUpperCase(),
43
+ path: m[2],
44
+ file,
45
+ snippet: this.snip(lines, m.index),
46
+ });
47
+ }
48
+ // Flask: @app.route("/x", methods=['GET','POST'])
49
+ const flaskRe = /@\w+\.route\s*\(\s*["'`]([^"'`]+)["'`]/gi;
50
+ while ((m = flaskRe.exec(content)) !== null) {
51
+ const methods = this.flaskMethodsNear(content, m.index);
52
+ for (const method of methods) {
53
+ out.push({
54
+ method,
55
+ path: m[1],
56
+ file,
57
+ snippet: this.snip(lines, m.index),
58
+ });
59
+ }
60
+ }
61
+ // Django path() re_path()
62
+ const djangoPath = /(?:path|re_path)\s*\(\s*["']([^"']+)["']\s*,/g;
63
+ while ((m = djangoPath.exec(content)) !== null) {
64
+ out.push({
65
+ method: "GET",
66
+ path: m[1],
67
+ file,
68
+ snippet: this.snip(lines, m.index),
69
+ });
70
+ }
71
+ return out.slice(0, 200);
72
+ }
73
+ static flaskMethodsNear(content, idx) {
74
+ const slice = content.slice(idx, idx + 400);
75
+ const mm = slice.match(/methods\s*=\s*\[([^\]]+)\]/i);
76
+ if (!mm)
77
+ return ["GET"];
78
+ const raw = mm[1];
79
+ const methods = [];
80
+ const part = raw.matchAll(/['"](GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)['"]/gi);
81
+ for (const x of part)
82
+ methods.push(x[1].toUpperCase());
83
+ return methods.length ? methods : ["GET"];
84
+ }
85
+ static goRoutes(file, content) {
86
+ const out = [];
87
+ const lines = content.split("\n");
88
+ // gin/echo/fiber/chi style: .Get("/path" .POST("/path"
89
+ const methodPath = /\.(Get|Post|Put|Delete|Patch|Options|Head|Connect|Trace)\s*\(\s*["']([^"']+)["']/gi;
90
+ let m;
91
+ while ((m = methodPath.exec(content)) !== null) {
92
+ out.push({
93
+ method: m[1].toUpperCase(),
94
+ path: m[2],
95
+ file,
96
+ snippet: this.snip(lines, m.index),
97
+ });
98
+ }
99
+ // stdlib
100
+ const handleFunc = /HandleFunc\s*\(\s*["']([^"']+)["']/gi;
101
+ while ((m = handleFunc.exec(content)) !== null) {
102
+ out.push({
103
+ method: "GET",
104
+ path: m[1],
105
+ file,
106
+ snippet: this.snip(lines, m.index),
107
+ });
108
+ }
109
+ return out.slice(0, 200);
110
+ }
111
+ static pythonDefinitions(content) {
112
+ const defs = [];
113
+ const lines = content.split("\n");
114
+ for (const line of lines) {
115
+ const t = line.trim();
116
+ if (/^class\s+[A-Za-z_][\w]*/.test(t)) {
117
+ defs.push(`Class: ${t.slice(0, 200)}`);
118
+ continue;
119
+ }
120
+ if (/^(async\s+)?def\s+[A-Za-z_][\w]*\s*\(/.test(t)) {
121
+ defs.push(`Function: ${t.slice(0, 200)}`);
122
+ }
123
+ }
124
+ return defs.slice(0, 80);
125
+ }
126
+ static goDefinitions(content) {
127
+ const defs = [];
128
+ const funcRe = /^func\s+.+$/gm;
129
+ let m;
130
+ while ((m = funcRe.exec(content)) !== null) {
131
+ const line = m[0].trim();
132
+ if (line.length > 8)
133
+ defs.push(`Func: ${line.slice(0, 220)}`);
134
+ }
135
+ const typeRe = /^type\s+\w+\s+(struct|interface)\b.*/gm;
136
+ while ((m = typeRe.exec(content)) !== null) {
137
+ defs.push(`Type: ${m[0].trim().slice(0, 200)}`);
138
+ }
139
+ return defs.slice(0, 80);
140
+ }
141
+ static snip(lines, charIndex) {
142
+ let pos = 0;
143
+ for (let i = 0; i < lines.length; i++) {
144
+ const next = pos + lines[i].length + 1;
145
+ if (charIndex < next) {
146
+ return lines[i].trim().slice(0, 200);
147
+ }
148
+ pos = next;
149
+ }
150
+ return undefined;
151
+ }
152
+ }
153
+ exports.PolyglotExtractors = PolyglotExtractors;
@@ -0,0 +1,10 @@
1
+ export interface Route {
2
+ method: string;
3
+ path: string;
4
+ file: string;
5
+ snippet?: string;
6
+ }
7
+ export declare class RouteExtractor {
8
+ private static METHODS;
9
+ static extract(files: Record<string, string>): Route[];
10
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteExtractor = void 0;
4
+ const ts_morph_1 = require("ts-morph");
5
+ const polyglot_extractors_1 = require("./polyglot.extractors");
6
+ class RouteExtractor {
7
+ static METHODS = ['get', 'post', 'put', 'delete', 'patch', 'use'];
8
+ static extract(files) {
9
+ const project = new ts_morph_1.Project({ useInMemoryFileSystem: true });
10
+ const routes = [];
11
+ for (const [filePath, content] of Object.entries(files)) {
12
+ if (!filePath.endsWith('.ts') && !filePath.endsWith('.js') && !filePath.endsWith('.tsx') && !filePath.endsWith('.jsx')) {
13
+ continue;
14
+ }
15
+ const sourceFile = project.createSourceFile(filePath, content);
16
+ // Look for call expressions like app.get('/', ...) or router.post('/', ...)
17
+ const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
18
+ for (const call of callExpressions) {
19
+ const expression = call.getExpression();
20
+ if (expression.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
21
+ const pae = expression;
22
+ const methodName = pae.getName().toLowerCase();
23
+ if (this.METHODS.includes(methodName)) {
24
+ const args = call.getArguments();
25
+ if (args.length > 0 && args[0].getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
26
+ const routePath = args[0].getText().replace(/['"]/g, '');
27
+ routes.push({
28
+ method: methodName.toUpperCase(),
29
+ path: routePath,
30
+ file: filePath,
31
+ snippet: call.getText().substring(0, 200) // snippet limited to 200 chars
32
+ });
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return [...routes, ...polyglot_extractors_1.PolyglotExtractors.extractRoutes(files)];
39
+ }
40
+ }
41
+ exports.RouteExtractor = RouteExtractor;
@@ -0,0 +1,3 @@
1
+ export declare class SchemaAnalyzer {
2
+ static analyze(files: Record<string, string>): any[] | undefined;
3
+ }