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 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-watch.js local logs, watch, cost, timeline
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
+ };