getprismo 0.1.23 → 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.
- package/README.md +5 -1
- package/lib/prismo-dev/context-detect.js +342 -0
- package/lib/prismo-dev/context-optimize.js +11 -324
- package/lib/prismo-dev/scan-detect.js +306 -0
- package/lib/prismo-dev/scan-score.js +473 -0
- package/lib/prismo-dev/scan.js +29 -739
- package/lib/prismo-dev/usage-cost.js +336 -0
- package/lib/prismo-dev/usage-sessions.js +298 -0
- package/lib/prismo-dev/usage-watch.js +59 -1198
- package/lib/prismo-dev/watch-live.js +353 -0
- package/lib/prismo-dev/watch-render.js +290 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -866,9 +866,13 @@ lib/prismo-dev/report.js terminal, markdown, ci reports
|
|
|
866
866
|
lib/prismo-dev/scan.js repo scanning, scoring, readiness
|
|
867
867
|
lib/prismo-dev/scan-path-utils.js scan ignore/path helper logic
|
|
868
868
|
lib/prismo-dev/shield.js local command shield and searchable output index
|
|
869
|
+
lib/prismo-dev/usage-cost.js Claude Code cost and timeline analysis
|
|
869
870
|
lib/prismo-dev/usage-log-utils.js local session log parsing helpers
|
|
870
|
-
lib/prismo-dev/usage-
|
|
871
|
+
lib/prismo-dev/usage-sessions.js local Codex/Claude session discovery
|
|
872
|
+
lib/prismo-dev/usage-watch.js watch orchestration, JSON payloads, live files
|
|
871
873
|
lib/prismo-dev/utils.js shared terminal/file/token helpers
|
|
874
|
+
lib/prismo-dev/watch-live.js live context-pressure decisions
|
|
875
|
+
lib/prismo-dev/watch-render.js watch terminal and guardrail renderers
|
|
872
876
|
```
|
|
873
877
|
|
|
874
878
|
---
|
|
@@ -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
|
+
};
|