getprismo 0.1.24 → 0.1.25

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.
@@ -0,0 +1,342 @@
1
+ module.exports = function createContextDetect(deps) {
2
+ const { fs, path, safeReadJson, readIfText, formatBytes } = deps;
3
+ const { execFileSync } = require("child_process");
4
+
5
+ function findRepoFiles(result, predicate, limit = 40) {
6
+ return result.files
7
+ ? result.files.filter((file) => !file.ignored && file.kind !== "binary" && predicate(file.path, file)).slice(0, limit)
8
+ : [];
9
+ }
10
+
11
+ function detectFrameworks(root, result) {
12
+ const frameworks = new Set();
13
+ const packageFiles = findRepoFiles(result, (rel) => path.basename(rel) === "package.json" && !rel.includes("node_modules/"), 12);
14
+ for (const file of packageFiles) {
15
+ const pkg = safeReadJson(path.join(root, file.path));
16
+ const deps = { ...(pkg && pkg.dependencies), ...(pkg && pkg.devDependencies) };
17
+ if (deps.next) frameworks.add("Next.js");
18
+ if (deps.react) frameworks.add("React");
19
+ if (deps.vue || deps["@vue/runtime-core"]) frameworks.add("Vue");
20
+ if (deps.svelte || deps["@sveltejs/kit"]) frameworks.add(deps["@sveltejs/kit"] ? "SvelteKit" : "Svelte");
21
+ if (deps["solid-js"]) frameworks.add("Solid");
22
+ if (deps.astro) frameworks.add("Astro");
23
+ if (deps.nuxt) frameworks.add("Nuxt");
24
+ if (deps.express) frameworks.add("Express");
25
+ if (deps["@nestjs/core"]) frameworks.add("NestJS");
26
+ if (deps.prisma || deps["@prisma/client"]) frameworks.add("Prisma");
27
+ if (deps.tailwindcss) frameworks.add("Tailwind");
28
+ if (deps.typescript) frameworks.add("TypeScript");
29
+ if (pkg) frameworks.add("Node.js");
30
+ }
31
+
32
+ const textFiles = new Map(result.files.map((file) => [file.path, file]));
33
+ const requirements = [...textFiles.keys()].filter((rel) => rel.endsWith("requirements.txt"));
34
+ for (const rel of requirements) {
35
+ const text = readIfText(path.join(root, rel)) || "";
36
+ if (/fastapi/i.test(text)) frameworks.add("FastAPI");
37
+ if (/django/i.test(text)) frameworks.add("Django");
38
+ if (/flask/i.test(text)) frameworks.add("Flask");
39
+ if (/psycopg2|asyncpg|sqlalchemy/i.test(text)) frameworks.add("PostgreSQL");
40
+ if (/redis/i.test(text)) frameworks.add("Redis");
41
+ frameworks.add("Python");
42
+ }
43
+
44
+ const pyprojectFiles = [...textFiles.keys()].filter((rel) => rel.endsWith("pyproject.toml") && !rel.includes("node_modules/"));
45
+ for (const rel of pyprojectFiles) {
46
+ const text = readIfText(path.join(root, rel)) || "";
47
+ if (/fastapi/i.test(text)) frameworks.add("FastAPI");
48
+ if (/django/i.test(text)) frameworks.add("Django");
49
+ if (/flask/i.test(text)) frameworks.add("Flask");
50
+ if (/sqlalchemy/i.test(text)) frameworks.add("SQLAlchemy");
51
+ if (/psycopg|asyncpg/i.test(text)) frameworks.add("PostgreSQL");
52
+ if (/redis/i.test(text)) frameworks.add("Redis");
53
+ if (/celery/i.test(text)) frameworks.add("Celery");
54
+ frameworks.add("Python");
55
+ }
56
+
57
+ if (pyprojectFiles.length && !frameworks.has("Python")) frameworks.add("Python");
58
+ const pythonFiles = [...textFiles.values()].filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)).slice(0, 80);
59
+ for (const file of pythonFiles) {
60
+ const text = readIfText(path.join(root, file.path), 128 * 1024) || "";
61
+ if (/FastAPI\s*\(|from\s+fastapi\s+import|APIRouter\s*\(/.test(text)) frameworks.add("FastAPI");
62
+ if (/from\s+django|import\s+django|DJANGO_SETTINGS_MODULE/.test(text)) frameworks.add("Django");
63
+ if (/Flask\s*\(|from\s+flask\s+import/.test(text)) frameworks.add("Flask");
64
+ if (/sqlalchemy|create_engine|SessionLocal/i.test(text)) frameworks.add("SQLAlchemy");
65
+ }
66
+ if (pythonFiles.length) frameworks.add("Python");
67
+ if ([...textFiles.keys()].some((rel) => rel.endsWith("Cargo.toml"))) frameworks.add("Rust");
68
+ if ([...textFiles.keys()].some((rel) => rel.endsWith("go.mod"))) frameworks.add("Go");
69
+ if ([...textFiles.keys()].some((rel) => rel.endsWith("docker-compose.yml") || rel.endsWith("docker-compose.yaml"))) frameworks.add("Docker");
70
+ if ([...textFiles.keys()].some((rel) => rel.includes("prisma/schema.prisma"))) frameworks.add("Prisma");
71
+ if ([...textFiles.keys()].some((rel) => rel.includes("next.config."))) frameworks.add("Next.js");
72
+ if ([...textFiles.keys()].some((rel) => rel.includes("vite.config."))) frameworks.add("Vite");
73
+ if ([...textFiles.keys()].some((rel) => rel.includes("svelte.config."))) frameworks.add("SvelteKit");
74
+ if ([...textFiles.keys()].some((rel) => rel.includes("astro.config."))) frameworks.add("Astro");
75
+ if ([...textFiles.keys()].some((rel) => rel.includes("nuxt.config."))) frameworks.add("Nuxt");
76
+ if ([...textFiles.keys()].some((rel) => rel.endsWith(".vue"))) frameworks.add("Vue");
77
+ if ([...textFiles.keys()].some((rel) => rel.endsWith(".svelte"))) frameworks.add("Svelte");
78
+ if ([...textFiles.keys()].some((rel) => rel.includes("tailwind.config."))) frameworks.add("Tailwind");
79
+ if ([...textFiles.keys()].some((rel) => rel.endsWith("tsconfig.json"))) frameworks.add("TypeScript");
80
+ if ([...textFiles.keys()].some((rel) => rel.includes("alembic/") || rel.endsWith("alembic.ini"))) frameworks.add("Alembic");
81
+ if ([...textFiles.keys()].some((rel) => /postgres|postgresql/i.test(rel))) frameworks.add("PostgreSQL");
82
+ return Array.from(frameworks).sort();
83
+ }
84
+
85
+ function topLevelDirectories(root) {
86
+ try {
87
+ return fs.readdirSync(root, { withFileTypes: true })
88
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules")
89
+ .map((entry) => entry.name)
90
+ .sort();
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+
96
+ function hasPath(result, matcher) {
97
+ return result.files.some((file) => matcher(file.path));
98
+ }
99
+
100
+ function detectEntrypoints(result) {
101
+ const candidates = [
102
+ "backend/app/main.py",
103
+ "backend/main.py",
104
+ "app/main.py",
105
+ "main.py",
106
+ "manage.py",
107
+ "frontend/src/app/page.tsx",
108
+ "frontend/src/app/layout.tsx",
109
+ "src/app/page.tsx",
110
+ "src/main.tsx",
111
+ "src/index.tsx",
112
+ "src/App.vue",
113
+ "src/App.svelte",
114
+ "src/routes/+page.svelte",
115
+ "src/pages/index.astro",
116
+ "app.vue",
117
+ "server.js",
118
+ "index.js",
119
+ "docker/docker-compose.yml",
120
+ "docker-compose.yml",
121
+ ];
122
+ const paths = new Set(result.files.map((file) => file.path));
123
+ const found = candidates.filter((candidate) => paths.has(candidate));
124
+ if (!found.length) {
125
+ const pyMain = result.files.find((f) => /^[^/]+\/__main__\.py$/.test(f.path));
126
+ if (pyMain) found.push(pyMain.path);
127
+ const pyInit = result.files.find((f) => /^[^/]+\/__init__\.py$/.test(f.path) && !f.path.includes("test"));
128
+ if (pyInit && !found.length) found.push(pyInit.path);
129
+ }
130
+ return found;
131
+ }
132
+
133
+ function isNonSourcePath(rel) {
134
+ if (/\.(test|spec|e2e)\.[jt]sx?$/.test(rel)) return true;
135
+ return /^(docs|docs_src|examples|samples|tutorials|tests|test|spec|__tests__|fixtures)\//i.test(rel) ||
136
+ /\/(docs|docs_src|examples|samples|tutorials|tests|test|__tests__|fixtures)\//.test(rel);
137
+ }
138
+
139
+ function detectBackendPaths(result) {
140
+ const pythonText = (file) => readIfText(path.join(result.root, file.path), 256 * 1024) || "";
141
+ const isRootPython = (rel) => /^[^/]+\.py$/.test(rel);
142
+ const isPython = (rel) => rel.endsWith(".py");
143
+ const hasFastApiSignal = (file) => /FastAPI\s*\(|APIRouter\s*\(|@app\.(get|post|put|patch|delete)|@router\.(get|post|put|patch|delete)/.test(pythonText(file));
144
+ const api = findRepoFiles(result, (rel, file) => !isNonSourcePath(rel) && (
145
+ /(backend|app|src)\/.*(router|routes|api)\//.test(rel) ||
146
+ /(backend|app|src)\/.*(router|routes).*\.py$/.test(rel) ||
147
+ /\/(routing|routers?)\.py$/.test(rel) ||
148
+ (isRootPython(rel) && hasFastApiSignal(file))
149
+ ), 30).map((f) => f.path);
150
+ const services = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
151
+ /(backend|app|src)\/.*(service|services|application)/.test(rel) ||
152
+ /\/applications?\.py$/.test(rel) ||
153
+ (isRootPython(rel) && /(service|worker|manager|client|heartbeat|memory|chat|tool|orchestrator|pipeline)/i.test(rel))
154
+ ), 40).map((f) => f.path);
155
+ const models = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
156
+ /(backend|app|src)\/.*models\.py$/.test(rel) ||
157
+ /(backend|app|src)\/.*schema/.test(rel) ||
158
+ /\/models\.py$/.test(rel) ||
159
+ (isRootPython(rel) && /(model|schema|entity|types?)/i.test(rel))
160
+ ), 30).map((f) => f.path);
161
+ const db = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
162
+ /(backend|app|src)\/.*\/(db|database|alembic|migrations)[/.]/.test(rel) ||
163
+ (isPython(rel) && /(sqlite|qdrant|neo4j|database|storage|repository|vector|graph|migration)/i.test(rel))
164
+ ), 30).map((f) => f.path);
165
+ const config = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
166
+ /(backend|app|src)\/.*(config|settings|env).*\.py$/.test(rel) ||
167
+ (isRootPython(rel) && /(config|settings|env)/i.test(rel)) ||
168
+ rel.endsWith("requirements.txt") ||
169
+ rel === "pyproject.toml"
170
+ ), 20).map((f) => f.path);
171
+ const auth = findRepoFiles(result, (rel, file) => !isNonSourcePath(rel) && (
172
+ /(backend|app|src)\/.*auth/.test(rel) ||
173
+ /\/security\.py$/.test(rel) ||
174
+ (isPython(rel) && /(auth|security|permission|token|session|oauth|jwt|middleware)/i.test(rel)) ||
175
+ (isRootPython(rel) && /(Depends|HTTPBearer|OAuth2|JWT|Authorization)/.test(pythonText(file)))
176
+ ), 30).map((f) => f.path);
177
+ return { api, services, models, db, config, auth };
178
+ }
179
+
180
+ function detectFrontendPaths(result) {
181
+ const appSurface = /(^|\/)(frontend\/)?src\/(app|pages|routes)\//;
182
+ const appFile = /(^|\/)(frontend\/)?src\/(App|main|index)\.(jsx?|tsx?|vue|svelte)$/;
183
+ const componentFile = /(^|\/)(frontend\/)?src\/(components|ui|widgets)\//;
184
+ const apiFile = /(^|\/)(frontend\/)?src\/(lib|hooks|composables|stores|services|api)\/.*(api|client|query|fetch|request|finops)/;
185
+ const stylingFile = /tailwind\.config|globals\.css|app\.css|\.module\.css|\.scss$|(^|\/)(frontend\/)?src\/styles?\//;
186
+ const stateFile = /providers?\.(tsx?|jsx?)|react-query|use[A-Z].*\.(ts|tsx|js|jsx)|(^|\/)(stores?|state|pinia|zustand|redux)\//;
187
+ return {
188
+ app: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (appSurface.test(rel) || appFile.test(rel) || /(^|\/)app\.vue$/.test(rel) || /apps\/[^/]+\/(app|pages|routes|src\/app|src\/pages|src\/routes)\//.test(rel)), 24).map((f) => f.path),
189
+ components: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (componentFile.test(rel) || /apps\/[^/]+\/.*(components|ui|widgets)\//.test(rel) || /packages\/[^/]+\/.*(components|ui|widgets)\//.test(rel)), 24).map((f) => f.path),
190
+ apiClient: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (apiFile.test(rel) || /apps\/[^/]+\/.*(lib|hooks|composables|services|api)\/.*(api|client|query|fetch|request)/.test(rel)), 24).map((f) => f.path),
191
+ styling: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && stylingFile.test(rel), 24).map((f) => f.path),
192
+ state: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && stateFile.test(rel), 24).map((f) => f.path),
193
+ };
194
+ }
195
+
196
+ function moduleNameForPath(rel) {
197
+ return rel
198
+ .replace(/\.[^.]+$/, "")
199
+ .replace(/\/__init__$/, "")
200
+ .replace(/\/index$/, "")
201
+ .replace(/\//g, ".");
202
+ }
203
+
204
+ function basenameStem(rel) {
205
+ return path.basename(rel).replace(/\.[^.]+$/, "");
206
+ }
207
+
208
+ function extractPythonImports(text) {
209
+ const imports = new Set();
210
+ const importRe = /^\s*import\s+([a-zA-Z0-9_.,\s]+)/gm;
211
+ const fromRe = /^\s*from\s+([a-zA-Z0-9_.]+)\s+import\s+/gm;
212
+ let match;
213
+ while ((match = importRe.exec(text))) {
214
+ match[1].split(",").map((part) => part.trim().split(/\s+as\s+/)[0]).filter(Boolean).forEach((name) => imports.add(name));
215
+ }
216
+ while ((match = fromRe.exec(text))) imports.add(match[1]);
217
+ return imports;
218
+ }
219
+
220
+ function resolveJsImport(fromRel, specifier, sourcePaths) {
221
+ if (!specifier.startsWith(".")) return null;
222
+ const fromDir = path.posix.dirname(fromRel);
223
+ const base = path.posix.normalize(path.posix.join(fromDir, specifier));
224
+ const candidates = [
225
+ base,
226
+ `${base}.ts`,
227
+ `${base}.tsx`,
228
+ `${base}.js`,
229
+ `${base}.jsx`,
230
+ `${base}/index.ts`,
231
+ `${base}/index.tsx`,
232
+ `${base}/index.js`,
233
+ `${base}/index.jsx`,
234
+ ];
235
+ return candidates.find((candidate) => sourcePaths.has(candidate)) || null;
236
+ }
237
+
238
+ function extractJsImports(fromRel, text, sourcePaths) {
239
+ const imports = new Set();
240
+ const importRe = /\bfrom\s+["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)|require\s*\(\s*["']([^"']+)["']\s*\)/g;
241
+ let match;
242
+ while ((match = importRe.exec(text))) {
243
+ const specifier = match[1] || match[2] || match[3];
244
+ const resolved = resolveJsImport(fromRel, specifier, sourcePaths);
245
+ if (resolved) imports.add(resolved);
246
+ }
247
+ return imports;
248
+ }
249
+
250
+ function getGitActivity(root) {
251
+ const activity = new Map();
252
+ try {
253
+ const output = execFileSync("git", ["-C", root, "log", "--since=180 days ago", "--name-only", "--format=%ct"], {
254
+ encoding: "utf8",
255
+ maxBuffer: 8 * 1024 * 1024,
256
+ stdio: ["ignore", "pipe", "ignore"],
257
+ });
258
+ let currentTs = 0;
259
+ for (const rawLine of output.split(/\r?\n/)) {
260
+ const line = rawLine.trim();
261
+ if (!line) continue;
262
+ if (/^\d{9,}$/.test(line)) {
263
+ currentTs = Number(line);
264
+ continue;
265
+ }
266
+ const rel = line.replace(/\\/g, "/");
267
+ const previous = activity.get(rel) || { touches: 0, lastTouched: 0 };
268
+ previous.touches += 1;
269
+ previous.lastTouched = Math.max(previous.lastTouched, currentTs);
270
+ activity.set(rel, previous);
271
+ }
272
+ } catch {
273
+ return activity;
274
+ }
275
+ return activity;
276
+ }
277
+
278
+ function formatGitActivity(activity) {
279
+ if (!activity || !activity.touches) return "no recent git touches";
280
+ const date = activity.lastTouched ? new Date(activity.lastTouched * 1000).toISOString().slice(0, 10) : "unknown date";
281
+ return `${activity.touches} recent git touch${activity.touches === 1 ? "" : "es"}, last ${date}`;
282
+ }
283
+
284
+ function topLoadBearing(root, files, allFiles, gitActivity, limit = 8) {
285
+ const textFiles = (allFiles || []).filter((file) => !file.ignored && file.kind !== "binary" && /\.(py|tsx?|jsx?|mjs|cjs|vue|svelte)$/.test(file.path));
286
+ const sourcePaths = new Set(textFiles.map((file) => file.path));
287
+ const pythonModuleToPath = new Map();
288
+ for (const file of textFiles.filter((candidate) => candidate.path.endsWith(".py"))) {
289
+ pythonModuleToPath.set(moduleNameForPath(file.path), file.path);
290
+ pythonModuleToPath.set(basenameStem(file.path), file.path);
291
+ }
292
+
293
+ const importRefs = new Map();
294
+ const textRefs = new Map();
295
+ const corpus = textFiles.map((file) => {
296
+ const text = readIfText(path.join(root, file.path), 512 * 1024) || "";
297
+ if (file.path.endsWith(".py")) {
298
+ for (const imported of extractPythonImports(text)) {
299
+ const target = pythonModuleToPath.get(imported) || pythonModuleToPath.get(imported.split(".").pop());
300
+ if (target && target !== file.path) importRefs.set(target, (importRefs.get(target) || 0) + 1);
301
+ }
302
+ } else if (/\.(tsx?|jsx?|mjs|cjs|vue|svelte)$/.test(file.path)) {
303
+ for (const target of extractJsImports(file.path, text, sourcePaths)) {
304
+ if (target !== file.path) importRefs.set(target, (importRefs.get(target) || 0) + 1);
305
+ }
306
+ }
307
+ return `${file.path}\n${text}`;
308
+ });
309
+
310
+ for (const file of files || []) {
311
+ const base = basenameStem(file.path);
312
+ const importName = base.replace(/[-.]/g, "_");
313
+ const refs = corpus.reduce((sum, text) => sum + (text.includes(base) || text.includes(importName) ? 1 : 0), 0);
314
+ textRefs.set(file.path, refs);
315
+ }
316
+
317
+ return [...(files || [])]
318
+ .filter((file) => !file.ignored && file.kind !== "binary")
319
+ .map((file) => {
320
+ const imports = importRefs.get(file.path) || 0;
321
+ const refs = textRefs.get(file.path) || 0;
322
+ const git = gitActivity.get(file.path) || { touches: 0, lastTouched: 0 };
323
+ const score = imports * 1000 + refs * 25 + Math.min(git.touches, 20) * 10 + Math.log10(file.size + 1);
324
+ return { file, imports, refs, git, score };
325
+ })
326
+ .sort((a, b) => b.score - a.score || b.file.size - a.file.size)
327
+ .slice(0, limit)
328
+ .map(({ file, imports, refs, git }) => `${file.path} (${imports} import ref${imports === 1 ? "" : "s"}, ${refs} text reference signal${refs === 1 ? "" : "s"}, ${formatGitActivity(git)}, ${formatBytes(file.size)})`);
329
+ }
330
+
331
+ return {
332
+ detectBackendPaths,
333
+ detectEntrypoints,
334
+ detectFrameworks,
335
+ detectFrontendPaths,
336
+ getGitActivity,
337
+ hasPath,
338
+ isNonSourcePath,
339
+ topLevelDirectories,
340
+ topLoadBearing,
341
+ };
342
+ };