getprismo 0.1.4 → 0.1.6
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/LICENSE +21 -0
- package/NOTICE +16 -0
- package/README.md +614 -88
- package/docs/prismodev-user-testing.md +10 -10
- package/lib/prismo-dev/constants.js +173 -0
- package/lib/prismo-dev/context-optimize.js +629 -0
- package/lib/prismo-dev/doctor.js +453 -0
- package/lib/prismo-dev/firewall.js +215 -0
- package/lib/prismo-dev/fixes.js +109 -0
- package/lib/prismo-dev/report.js +360 -0
- package/lib/prismo-dev/scan.js +1126 -0
- package/lib/prismo-dev/usage-watch.js +1852 -0
- package/lib/prismo-dev-scan.js +468 -2685
- package/package.json +24 -7
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
module.exports = function createContextOptimize(deps) {
|
|
2
|
+
const {
|
|
3
|
+
fs,
|
|
4
|
+
path,
|
|
5
|
+
NPX_COMMAND,
|
|
6
|
+
scanRepo,
|
|
7
|
+
safeReadJson,
|
|
8
|
+
readIfText,
|
|
9
|
+
formatBytes,
|
|
10
|
+
color,
|
|
11
|
+
writeGeneratedFile,
|
|
12
|
+
} = deps;
|
|
13
|
+
|
|
14
|
+
function findRepoFiles(result, predicate, limit = 40) {
|
|
15
|
+
return result.files
|
|
16
|
+
? result.files.filter((file) => !file.ignored && file.kind !== "binary" && predicate(file.path, file)).slice(0, limit)
|
|
17
|
+
: [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function detectFrameworks(root, result) {
|
|
21
|
+
const frameworks = new Set();
|
|
22
|
+
const packageFiles = findRepoFiles(result, (rel) => path.basename(rel) === "package.json" && !rel.includes("node_modules/"), 12);
|
|
23
|
+
for (const file of packageFiles) {
|
|
24
|
+
const pkg = safeReadJson(path.join(root, file.path));
|
|
25
|
+
const deps = { ...(pkg && pkg.dependencies), ...(pkg && pkg.devDependencies) };
|
|
26
|
+
if (deps.next) frameworks.add("Next.js");
|
|
27
|
+
if (deps.react) frameworks.add("React");
|
|
28
|
+
if (deps.express) frameworks.add("Express");
|
|
29
|
+
if (deps["@nestjs/core"]) frameworks.add("NestJS");
|
|
30
|
+
if (deps.prisma || deps["@prisma/client"]) frameworks.add("Prisma");
|
|
31
|
+
if (deps.tailwindcss) frameworks.add("Tailwind");
|
|
32
|
+
if (deps.typescript) frameworks.add("TypeScript");
|
|
33
|
+
if (pkg) frameworks.add("Node.js");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const textFiles = new Map(result.files.map((file) => [file.path, file]));
|
|
37
|
+
const requirements = [...textFiles.keys()].filter((rel) => rel.endsWith("requirements.txt"));
|
|
38
|
+
for (const rel of requirements) {
|
|
39
|
+
const text = readIfText(path.join(root, rel)) || "";
|
|
40
|
+
if (/fastapi/i.test(text)) frameworks.add("FastAPI");
|
|
41
|
+
if (/django/i.test(text)) frameworks.add("Django");
|
|
42
|
+
if (/flask/i.test(text)) frameworks.add("Flask");
|
|
43
|
+
if (/psycopg2|asyncpg|sqlalchemy/i.test(text)) frameworks.add("PostgreSQL");
|
|
44
|
+
if (/redis/i.test(text)) frameworks.add("Redis");
|
|
45
|
+
frameworks.add("Python");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const pyprojectFiles = [...textFiles.keys()].filter((rel) => rel.endsWith("pyproject.toml") && !rel.includes("node_modules/"));
|
|
49
|
+
for (const rel of pyprojectFiles) {
|
|
50
|
+
const text = readIfText(path.join(root, rel)) || "";
|
|
51
|
+
if (/fastapi/i.test(text)) frameworks.add("FastAPI");
|
|
52
|
+
if (/django/i.test(text)) frameworks.add("Django");
|
|
53
|
+
if (/flask/i.test(text)) frameworks.add("Flask");
|
|
54
|
+
if (/sqlalchemy/i.test(text)) frameworks.add("SQLAlchemy");
|
|
55
|
+
if (/psycopg|asyncpg/i.test(text)) frameworks.add("PostgreSQL");
|
|
56
|
+
if (/redis/i.test(text)) frameworks.add("Redis");
|
|
57
|
+
if (/celery/i.test(text)) frameworks.add("Celery");
|
|
58
|
+
frameworks.add("Python");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (pyprojectFiles.length && !frameworks.has("Python")) frameworks.add("Python");
|
|
62
|
+
if ([...textFiles.keys()].some((rel) => rel.endsWith("Cargo.toml"))) frameworks.add("Rust");
|
|
63
|
+
if ([...textFiles.keys()].some((rel) => rel.endsWith("go.mod"))) frameworks.add("Go");
|
|
64
|
+
if ([...textFiles.keys()].some((rel) => rel.endsWith("docker-compose.yml") || rel.endsWith("docker-compose.yaml"))) frameworks.add("Docker");
|
|
65
|
+
if ([...textFiles.keys()].some((rel) => rel.includes("prisma/schema.prisma"))) frameworks.add("Prisma");
|
|
66
|
+
if ([...textFiles.keys()].some((rel) => rel.includes("next.config."))) frameworks.add("Next.js");
|
|
67
|
+
if ([...textFiles.keys()].some((rel) => rel.includes("vite.config."))) frameworks.add("Vite");
|
|
68
|
+
if ([...textFiles.keys()].some((rel) => rel.includes("tailwind.config."))) frameworks.add("Tailwind");
|
|
69
|
+
if ([...textFiles.keys()].some((rel) => rel.endsWith("tsconfig.json"))) frameworks.add("TypeScript");
|
|
70
|
+
if ([...textFiles.keys()].some((rel) => rel.includes("alembic/") || rel.endsWith("alembic.ini"))) frameworks.add("Alembic");
|
|
71
|
+
if ([...textFiles.keys()].some((rel) => /postgres|postgresql/i.test(rel))) frameworks.add("PostgreSQL");
|
|
72
|
+
return Array.from(frameworks).sort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function topLevelDirectories(root) {
|
|
76
|
+
try {
|
|
77
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
78
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules")
|
|
79
|
+
.map((entry) => entry.name)
|
|
80
|
+
.sort();
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasPath(result, matcher) {
|
|
87
|
+
return result.files.some((file) => matcher(file.path));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function detectEntrypoints(result) {
|
|
91
|
+
const candidates = [
|
|
92
|
+
"backend/app/main.py",
|
|
93
|
+
"backend/main.py",
|
|
94
|
+
"app/main.py",
|
|
95
|
+
"main.py",
|
|
96
|
+
"manage.py",
|
|
97
|
+
"frontend/src/app/page.tsx",
|
|
98
|
+
"frontend/src/app/layout.tsx",
|
|
99
|
+
"src/app/page.tsx",
|
|
100
|
+
"src/main.tsx",
|
|
101
|
+
"src/index.tsx",
|
|
102
|
+
"server.js",
|
|
103
|
+
"index.js",
|
|
104
|
+
"docker/docker-compose.yml",
|
|
105
|
+
"docker-compose.yml",
|
|
106
|
+
];
|
|
107
|
+
const paths = new Set(result.files.map((file) => file.path));
|
|
108
|
+
const found = candidates.filter((candidate) => paths.has(candidate));
|
|
109
|
+
if (!found.length) {
|
|
110
|
+
const pyMain = result.files.find((f) => /^[^/]+\/__main__\.py$/.test(f.path));
|
|
111
|
+
if (pyMain) found.push(pyMain.path);
|
|
112
|
+
const pyInit = result.files.find((f) => /^[^/]+\/__init__\.py$/.test(f.path) && !f.path.includes("test"));
|
|
113
|
+
if (pyInit && !found.length) found.push(pyInit.path);
|
|
114
|
+
}
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isNonSourcePath(rel) {
|
|
119
|
+
if (/\.(test|spec|e2e)\.[jt]sx?$/.test(rel)) return true;
|
|
120
|
+
return /^(docs|docs_src|examples|samples|tutorials|tests|test|spec|__tests__|fixtures)\//i.test(rel) ||
|
|
121
|
+
/\/(docs|docs_src|examples|samples|tutorials|tests|test|__tests__|fixtures)\//.test(rel);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function detectBackendPaths(result) {
|
|
125
|
+
const api = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/(backend|app|src)\/.*(router|routes|api)\//.test(rel) || /(backend|app|src)\/.*(router|routes).*\.py$/.test(rel) || /\/(routing|routers?)\.py$/.test(rel)), 20).map((f) => f.path);
|
|
126
|
+
const services = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/(backend|app|src)\/.*(service|services|application)/.test(rel) || /\/applications?\.py$/.test(rel)), 20).map((f) => f.path);
|
|
127
|
+
const models = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/(backend|app|src)\/.*models\.py$/.test(rel) || /(backend|app|src)\/.*schema/.test(rel) || /\/models\.py$/.test(rel)), 20).map((f) => f.path);
|
|
128
|
+
const db = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && /(backend|app|src)\/.*\/(db|database|alembic|migrations)[/.]/.test(rel), 20).map((f) => f.path);
|
|
129
|
+
const config = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/(backend|app|src)\/.*(config|settings|env).*\.py$/.test(rel) || rel.endsWith("requirements.txt") || rel === "pyproject.toml"), 20).map((f) => f.path);
|
|
130
|
+
const auth = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/(backend|app|src)\/.*auth/.test(rel) || /\/security\.py$/.test(rel)), 20).map((f) => f.path);
|
|
131
|
+
return { api, services, models, db, config, auth };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function detectFrontendPaths(result) {
|
|
135
|
+
return {
|
|
136
|
+
app: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/frontend\/src\/app\//.test(rel) || /src\/app\//.test(rel) || /apps\/[^/]+\/app\//.test(rel) || /apps\/[^/]+\/src\/app\//.test(rel)), 24).map((f) => f.path),
|
|
137
|
+
components: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/frontend\/src\/components\//.test(rel) || /src\/components\//.test(rel) || /apps\/[^/]+\/.*components\//.test(rel) || /packages\/[^/]+\/.*components\//.test(rel)), 20).map((f) => f.path),
|
|
138
|
+
apiClient: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/frontend\/src\/(lib|hooks)\/.*(api|client|query|finops)/.test(rel) || /src\/(lib|hooks)\/.*(api|client|query)/.test(rel)), 20).map((f) => f.path),
|
|
139
|
+
styling: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/tailwind\.config|globals\.css|\.module\.css|frontend\/src\/app\/globals/.test(rel)), 20).map((f) => f.path),
|
|
140
|
+
state: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/providers\.tsx|react-query|use[A-Z].*\.ts/.test(rel)), 20).map((f) => f.path),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createOptimizeContext(rootDir = process.cwd(), scope = null) {
|
|
145
|
+
const scan = scanRepo(rootDir);
|
|
146
|
+
const root = scan.root;
|
|
147
|
+
const frameworks = detectFrameworks(root, scan);
|
|
148
|
+
const folders = topLevelDirectories(root);
|
|
149
|
+
const entrypoints = detectEntrypoints(scan);
|
|
150
|
+
const backendDetected = folders.includes("backend") || frameworks.some((name) => ["FastAPI", "Django", "Flask"].includes(name));
|
|
151
|
+
const frontendDetected = folders.includes("frontend") || frameworks.some((name) => ["Next.js", "React", "Vite"].includes(name));
|
|
152
|
+
const backend = detectBackendPaths(scan);
|
|
153
|
+
const frontend = detectFrontendPaths(scan);
|
|
154
|
+
const warnings = [];
|
|
155
|
+
if (scan.exposedLargeFiles.length) warnings.push(`${scan.exposedLargeFiles.length} exposed large file(s) may bloat AI context.`);
|
|
156
|
+
if (!scan.hasClaudeIgnore) warnings.push(".claudeignore is missing.");
|
|
157
|
+
if (scan.exposedHighRiskDirs.length) warnings.push(`${scan.exposedHighRiskDirs.length} generated/cache directories may be visible.`);
|
|
158
|
+
|
|
159
|
+
const suggestions = [
|
|
160
|
+
"Use .prismo/architecture-summary.md as first-pass repo context instead of asking agents to explore broadly.",
|
|
161
|
+
"Keep CLAUDE.md and AGENTS.md concise; link to generated summaries when deeper context is needed.",
|
|
162
|
+
"Use scoped context packs for focused work, especially frontend/backend/auth tasks.",
|
|
163
|
+
"Keep generated files, logs, coverage, build output, and lockfiles out of coding-agent context.",
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
root,
|
|
168
|
+
scope,
|
|
169
|
+
scan,
|
|
170
|
+
frameworks,
|
|
171
|
+
folders,
|
|
172
|
+
entrypoints,
|
|
173
|
+
backendDetected,
|
|
174
|
+
frontendDetected,
|
|
175
|
+
backend,
|
|
176
|
+
frontend,
|
|
177
|
+
warnings,
|
|
178
|
+
suggestions,
|
|
179
|
+
estimatedContextReduction: scan.avoidableWaste,
|
|
180
|
+
generatedAt: new Date().toISOString(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function mdList(items, empty = "None detected.") {
|
|
185
|
+
if (!items || !items.length) return `- ${empty}`;
|
|
186
|
+
return items.map((item) => `- \`${item}\``).join("\n");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function proseList(items, empty = "none detected") {
|
|
190
|
+
return items && items.length ? items.join(", ") : empty;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function renderArchitectureSummary(ctx) {
|
|
194
|
+
const apiLayer = ctx.backend.api.slice(0, 6);
|
|
195
|
+
const dbLayer = ctx.backend.db.slice(0, 6);
|
|
196
|
+
const frontendLayer = ctx.frontend.app.slice(0, 6);
|
|
197
|
+
const readOrder = [
|
|
198
|
+
"- Start here.",
|
|
199
|
+
ctx.backendDetected ? "- For backend work, read `.prismo/backend-summary.md` next." : null,
|
|
200
|
+
ctx.frontendDetected ? "- For frontend work, read `.prismo/frontend-summary.md` next." : null,
|
|
201
|
+
"- Then inspect only the files directly relevant to the task.",
|
|
202
|
+
"- Avoid broad recursive reads unless the task truly needs a repo-wide audit.",
|
|
203
|
+
].filter(Boolean);
|
|
204
|
+
return [
|
|
205
|
+
"# Architecture Summary",
|
|
206
|
+
"",
|
|
207
|
+
"Use this file as the first repo-context attachment for Claude Code, Codex, Cursor, and similar tools. It is intentionally concise so agents do not have to rediscover the project from scratch.",
|
|
208
|
+
"",
|
|
209
|
+
"## Detected Frameworks",
|
|
210
|
+
"",
|
|
211
|
+
mdList(ctx.frameworks, "No common framework markers detected."),
|
|
212
|
+
"",
|
|
213
|
+
"## Major Folders",
|
|
214
|
+
"",
|
|
215
|
+
mdList(ctx.folders),
|
|
216
|
+
"",
|
|
217
|
+
"## Likely Architecture",
|
|
218
|
+
"",
|
|
219
|
+
`- Frontend detected: ${ctx.frontendDetected ? "yes" : "no"}`,
|
|
220
|
+
`- Backend detected: ${ctx.backendDetected ? "yes" : "no"}`,
|
|
221
|
+
`- API layer likely lives in: ${apiLayer.map((p) => `\`${p}\``).join(", ") || "not detected"}`,
|
|
222
|
+
`- Database/migration layer likely lives in: ${dbLayer.map((p) => `\`${p}\``).join(", ") || "not detected"}`,
|
|
223
|
+
`- Frontend routes/app surface likely lives in: ${frontendLayer.map((p) => `\`${p}\``).join(", ") || "not detected"}`,
|
|
224
|
+
"",
|
|
225
|
+
"## Recommended Read Order",
|
|
226
|
+
"",
|
|
227
|
+
readOrder.join("\n"),
|
|
228
|
+
"",
|
|
229
|
+
"## Key Entrypoints",
|
|
230
|
+
"",
|
|
231
|
+
mdList(ctx.entrypoints),
|
|
232
|
+
"",
|
|
233
|
+
"## Context Risks",
|
|
234
|
+
"",
|
|
235
|
+
ctx.warnings.length ? ctx.warnings.map((warning) => `- ${warning}`).join("\n") : "- No major local context risks detected.",
|
|
236
|
+
"",
|
|
237
|
+
"## AI Workflow Notes",
|
|
238
|
+
"",
|
|
239
|
+
"- Prefer this summary before broad repo reads.",
|
|
240
|
+
"- Use scoped context files for focused tasks.",
|
|
241
|
+
"- Avoid generated folders, caches, logs, coverage output, and large analysis files.",
|
|
242
|
+
"",
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function renderBackendSummary(ctx) {
|
|
247
|
+
return [
|
|
248
|
+
"# Backend Summary",
|
|
249
|
+
"",
|
|
250
|
+
"Only reasonably inferable backend structure is listed here.",
|
|
251
|
+
"",
|
|
252
|
+
"## API / Router Files",
|
|
253
|
+
"",
|
|
254
|
+
mdList(ctx.backend.api),
|
|
255
|
+
"",
|
|
256
|
+
"## Services / Application Logic",
|
|
257
|
+
"",
|
|
258
|
+
mdList(ctx.backend.services),
|
|
259
|
+
"",
|
|
260
|
+
"## Models / Schemas",
|
|
261
|
+
"",
|
|
262
|
+
mdList(ctx.backend.models),
|
|
263
|
+
"",
|
|
264
|
+
"## Database / Migration Layer",
|
|
265
|
+
"",
|
|
266
|
+
mdList(ctx.backend.db),
|
|
267
|
+
"",
|
|
268
|
+
"## Auth-Related Paths",
|
|
269
|
+
"",
|
|
270
|
+
mdList(ctx.backend.auth),
|
|
271
|
+
"",
|
|
272
|
+
"## Config / Environment Hints",
|
|
273
|
+
"",
|
|
274
|
+
mdList(ctx.backend.config),
|
|
275
|
+
"",
|
|
276
|
+
].join("\n");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function renderFrontendSummary(ctx) {
|
|
280
|
+
return [
|
|
281
|
+
"# Frontend Summary",
|
|
282
|
+
"",
|
|
283
|
+
"Only reasonably inferable frontend structure is listed here.",
|
|
284
|
+
"",
|
|
285
|
+
"## App / Routing Surface",
|
|
286
|
+
"",
|
|
287
|
+
mdList(ctx.frontend.app),
|
|
288
|
+
"",
|
|
289
|
+
"## Components",
|
|
290
|
+
"",
|
|
291
|
+
mdList(ctx.frontend.components),
|
|
292
|
+
"",
|
|
293
|
+
"## API Clients / Data Hooks",
|
|
294
|
+
"",
|
|
295
|
+
mdList(ctx.frontend.apiClient),
|
|
296
|
+
"",
|
|
297
|
+
"## State / Providers",
|
|
298
|
+
"",
|
|
299
|
+
mdList(ctx.frontend.state),
|
|
300
|
+
"",
|
|
301
|
+
"## Styling",
|
|
302
|
+
"",
|
|
303
|
+
mdList(ctx.frontend.styling),
|
|
304
|
+
"",
|
|
305
|
+
].join("\n");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function renderRecommendedClaude(ctx) {
|
|
309
|
+
const commands = [];
|
|
310
|
+
const importantPaths = [
|
|
311
|
+
"- `.prismo/architecture-summary.md`",
|
|
312
|
+
ctx.backendDetected ? "- `.prismo/backend-summary.md`" : null,
|
|
313
|
+
ctx.frontendDetected ? "- `.prismo/frontend-summary.md`" : null,
|
|
314
|
+
].filter(Boolean);
|
|
315
|
+
if (hasPath(ctx.scan, (rel) => rel === "package.json")) {
|
|
316
|
+
commands.push("npm run scan");
|
|
317
|
+
commands.push("npm run test:scan");
|
|
318
|
+
}
|
|
319
|
+
if (hasPath(ctx.scan, (rel) => rel === "frontend/package.json")) commands.push("cd frontend && npm run test");
|
|
320
|
+
if (hasPath(ctx.scan, (rel) => rel === "backend/pytest.ini" || rel.startsWith("backend/tests/"))) commands.push("cd backend && pytest");
|
|
321
|
+
return [
|
|
322
|
+
"# CLAUDE.md",
|
|
323
|
+
"",
|
|
324
|
+
"Keep context small. Start with `.prismo/architecture-summary.md`; use scoped `.prismo/*-summary.md` files only when relevant.",
|
|
325
|
+
"",
|
|
326
|
+
"## Commands",
|
|
327
|
+
"",
|
|
328
|
+
...(commands.length ? commands.map((cmd) => `- \`${cmd}\``) : ["- Check package-specific scripts before running tests."]),
|
|
329
|
+
"",
|
|
330
|
+
"## Architecture",
|
|
331
|
+
"",
|
|
332
|
+
`- Frameworks: ${ctx.frameworks.join(", ") || "not detected"}.`,
|
|
333
|
+
`- Backend: ${ctx.backendDetected ? "see `.prismo/backend-summary.md`" : "not detected"}.`,
|
|
334
|
+
`- Frontend: ${ctx.frontendDetected ? "see `.prismo/frontend-summary.md`" : "not detected"}.`,
|
|
335
|
+
`- Entrypoints: ${proseList(ctx.entrypoints)}.`,
|
|
336
|
+
"",
|
|
337
|
+
"## Rules",
|
|
338
|
+
"",
|
|
339
|
+
"- Do not read generated folders, logs, coverage reports, build output, or lockfiles unless explicitly needed.",
|
|
340
|
+
"- Prefer existing project patterns and narrow edits.",
|
|
341
|
+
"- Use focused context packs for auth/frontend/backend tasks.",
|
|
342
|
+
"- Keep long implementation notes out of persistent instructions.",
|
|
343
|
+
"- Summarize any extra files opened before making broad changes.",
|
|
344
|
+
"",
|
|
345
|
+
"## Important Paths",
|
|
346
|
+
"",
|
|
347
|
+
importantPaths.join("\n"),
|
|
348
|
+
"",
|
|
349
|
+
].join("\n");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function renderRecommendedAgents(ctx) {
|
|
353
|
+
return [
|
|
354
|
+
"# AGENTS.md",
|
|
355
|
+
"",
|
|
356
|
+
"Use `.prismo/architecture-summary.md` first to avoid repeated broad repo exploration. Keep this file durable and short; task-specific details belong in the prompt or scoped context files.",
|
|
357
|
+
"",
|
|
358
|
+
"## Repo Structure",
|
|
359
|
+
"",
|
|
360
|
+
mdList(ctx.folders),
|
|
361
|
+
"",
|
|
362
|
+
"## Conventions",
|
|
363
|
+
"",
|
|
364
|
+
"- Keep changes scoped and follow nearby patterns.",
|
|
365
|
+
"- Use generated `.prismo/*-summary.md` files as compact context.",
|
|
366
|
+
"- Do not load generated artifacts, logs, coverage, caches, binary/media files, or lockfiles by default.",
|
|
367
|
+
"- For focused work, request or attach the relevant scoped context pack.",
|
|
368
|
+
"- Prefer small file reads and targeted searches before opening large documents.",
|
|
369
|
+
"- Call out uncertainty instead of inferring architecture that is not present in the repo.",
|
|
370
|
+
"",
|
|
371
|
+
"## Suggested Workflow",
|
|
372
|
+
"",
|
|
373
|
+
"1. Read `.prismo/architecture-summary.md`.",
|
|
374
|
+
"2. Read the scoped context file for the task, if one exists.",
|
|
375
|
+
"3. Inspect only relevant source files.",
|
|
376
|
+
"4. Run the narrowest useful tests.",
|
|
377
|
+
"",
|
|
378
|
+
"## Important Paths",
|
|
379
|
+
"",
|
|
380
|
+
mdList([".prismo/architecture-summary.md", ".prismo/recommended-.claudeignore", ".prismo/optimize-report.md"]),
|
|
381
|
+
"",
|
|
382
|
+
].join("\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function renderGitignoreAdditions(ctx) {
|
|
386
|
+
const additions = [
|
|
387
|
+
".prismo/*.bak",
|
|
388
|
+
"logs/",
|
|
389
|
+
"test-results/",
|
|
390
|
+
"playwright-report/",
|
|
391
|
+
"*.tmp",
|
|
392
|
+
"*.bak",
|
|
393
|
+
];
|
|
394
|
+
for (const file of ctx.scan.exposedLargeFiles) {
|
|
395
|
+
if (file.size >= 1024 * 1024) additions.push(file.path);
|
|
396
|
+
}
|
|
397
|
+
return Array.from(new Set(additions)).join("\n") + "\n";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function renderOptimizeReport(ctx, generatedFiles) {
|
|
401
|
+
return [
|
|
402
|
+
"# Prismo Optimize Report",
|
|
403
|
+
"",
|
|
404
|
+
"## Executive Summary",
|
|
405
|
+
"",
|
|
406
|
+
`- Estimated context reduction: ${ctx.estimatedContextReduction}`,
|
|
407
|
+
`- Frameworks detected: ${ctx.frameworks.join(", ") || "none"}`,
|
|
408
|
+
`- Generated at: ${ctx.generatedAt}`,
|
|
409
|
+
"",
|
|
410
|
+
"## AI Context Risk Areas",
|
|
411
|
+
"",
|
|
412
|
+
ctx.warnings.length ? ctx.warnings.map((warning) => `- ${warning}`).join("\n") : "- No major local context risks detected.",
|
|
413
|
+
"",
|
|
414
|
+
"## Token-Heavy Directories",
|
|
415
|
+
"",
|
|
416
|
+
mdList(ctx.scan.highRiskDirs.map((dir) => `${dir.path}/${dir.exposed ? " (exposed)" : " (ignored)"}`)),
|
|
417
|
+
"",
|
|
418
|
+
"## Optimization Suggestions",
|
|
419
|
+
"",
|
|
420
|
+
ctx.suggestions.map((suggestion, index) => `${index + 1}. ${suggestion}`).join("\n"),
|
|
421
|
+
"",
|
|
422
|
+
"## Generated Files",
|
|
423
|
+
"",
|
|
424
|
+
mdList(generatedFiles),
|
|
425
|
+
"",
|
|
426
|
+
"## Workflow Improvements",
|
|
427
|
+
"",
|
|
428
|
+
"- Start Claude Code/Codex with architecture-summary.md instead of asking for a broad repo scan.",
|
|
429
|
+
"- Use frontend/backend/auth context packs for scoped tasks.",
|
|
430
|
+
"- Keep persistent instruction files under roughly 500 tokens.",
|
|
431
|
+
"- Avoid pasting giant logs directly into AI coding sessions.",
|
|
432
|
+
"",
|
|
433
|
+
].join("\n");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function renderScopedContext(ctx, scope) {
|
|
437
|
+
const scopeLower = scope.toLowerCase();
|
|
438
|
+
const relevant = ctx.scan.files
|
|
439
|
+
.filter((file) => {
|
|
440
|
+
const rel = file.path.toLowerCase();
|
|
441
|
+
if (scopeLower === "frontend") return rel.includes("frontend/") || rel.includes("src/app/") || rel.includes("src/components/");
|
|
442
|
+
if (scopeLower === "backend") return rel.includes("backend/") || rel.includes("app/modules/") || rel.includes("app/shared/");
|
|
443
|
+
if (scopeLower === "auth") return rel.includes("auth") || rel.includes("supabase") || rel.includes("login") || rel.includes("signup");
|
|
444
|
+
return rel.includes(scopeLower);
|
|
445
|
+
})
|
|
446
|
+
.filter((file) => file.kind !== "binary")
|
|
447
|
+
.slice(0, 60)
|
|
448
|
+
.map((file) => file.path);
|
|
449
|
+
|
|
450
|
+
return [
|
|
451
|
+
`# ${scope.charAt(0).toUpperCase()}${scope.slice(1)} Context`,
|
|
452
|
+
"",
|
|
453
|
+
"Use this as a focused context pack for AI coding workflows.",
|
|
454
|
+
"",
|
|
455
|
+
"## Relevant Files",
|
|
456
|
+
"",
|
|
457
|
+
mdList(relevant),
|
|
458
|
+
"",
|
|
459
|
+
"## Notes",
|
|
460
|
+
"",
|
|
461
|
+
"- This file is generated from deterministic path heuristics.",
|
|
462
|
+
"- Verify flow details in source before making behavioral changes.",
|
|
463
|
+
"- Keep follow-up context narrow; do not attach generated files or logs unless needed.",
|
|
464
|
+
"- If this context pack is too broad, search within the listed files before opening all of them.",
|
|
465
|
+
"",
|
|
466
|
+
].join("\n");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function getContextFileForScope(ctx, scope) {
|
|
470
|
+
if (!scope) return ".prismo/architecture-summary.md";
|
|
471
|
+
const normalized = scope.toLowerCase();
|
|
472
|
+
if (normalized === "frontend") return ".prismo/frontend-context.md";
|
|
473
|
+
if (normalized === "backend") return ".prismo/backend-context.md";
|
|
474
|
+
if (normalized === "auth") return ".prismo/auth-context.md";
|
|
475
|
+
return `.prismo/${normalized}-context.md`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderStarterPrompt(ctx, scope = null) {
|
|
479
|
+
const contextFile = getContextFileForScope(ctx, scope);
|
|
480
|
+
const supporting = [];
|
|
481
|
+
if (!scope) {
|
|
482
|
+
if (ctx.backendDetected) supporting.push(".prismo/backend-summary.md");
|
|
483
|
+
if (ctx.frontendDetected) supporting.push(".prismo/frontend-summary.md");
|
|
484
|
+
} else if (scope === "frontend") {
|
|
485
|
+
supporting.push(".prismo/frontend-summary.md");
|
|
486
|
+
} else if (scope === "backend") {
|
|
487
|
+
supporting.push(".prismo/backend-summary.md");
|
|
488
|
+
} else if (scope === "auth") {
|
|
489
|
+
supporting.push(".prismo/architecture-summary.md");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const lines = [
|
|
493
|
+
"Use Prismo's compact repo context before exploring files.",
|
|
494
|
+
`Start with ${contextFile}.`,
|
|
495
|
+
];
|
|
496
|
+
if (supporting.length) lines.push(`Also use ${supporting.join(" and ")} if needed.`);
|
|
497
|
+
lines.push("Only inspect files directly relevant to the task.");
|
|
498
|
+
lines.push("Do not read generated folders, logs, coverage reports, node_modules, .next, dist, build, cache folders, lockfiles, or large analysis files unless I explicitly ask.");
|
|
499
|
+
lines.push("Before editing, summarize the small set of files you actually inspected and why.");
|
|
500
|
+
return lines.join(" ");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function renderContextCommand(ctx, scope = null) {
|
|
504
|
+
const label = scope ? `${scope.charAt(0).toUpperCase()}${scope.slice(1)} Context Prompt` : "Project Context Prompt";
|
|
505
|
+
const contextFile = getContextFileForScope(ctx, scope);
|
|
506
|
+
const existing = fs.existsSync(path.join(ctx.root, contextFile));
|
|
507
|
+
return [
|
|
508
|
+
`# Prismo ${label}`,
|
|
509
|
+
"",
|
|
510
|
+
renderStarterPrompt(ctx, scope),
|
|
511
|
+
"",
|
|
512
|
+
"## Context Files",
|
|
513
|
+
"",
|
|
514
|
+
`- ${contextFile}${existing ? "" : " (run `prismo optimize" + (scope ? ` ${scope}` : "") + "` to generate)"}`,
|
|
515
|
+
!scope && ctx.backendDetected ? "- .prismo/backend-summary.md" : "",
|
|
516
|
+
!scope && ctx.frontendDetected ? "- .prismo/frontend-summary.md" : "",
|
|
517
|
+
scope === "frontend" ? "- .prismo/frontend-summary.md" : "",
|
|
518
|
+
scope === "backend" ? "- .prismo/backend-summary.md" : "",
|
|
519
|
+
"",
|
|
520
|
+
"## Copy/Paste Task Wrapper",
|
|
521
|
+
"",
|
|
522
|
+
"```text",
|
|
523
|
+
`${renderStarterPrompt(ctx, scope)}\n\nTask: <describe the change here>`,
|
|
524
|
+
"```",
|
|
525
|
+
"",
|
|
526
|
+
].filter(Boolean).join("\n");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
function getOptimizePendingFiles(ctx) {
|
|
531
|
+
const pending = [
|
|
532
|
+
["architecture-summary.md", renderArchitectureSummary(ctx)],
|
|
533
|
+
["recommended-CLAUDE.md", renderRecommendedClaude(ctx)],
|
|
534
|
+
["recommended-AGENTS.md", renderRecommendedAgents(ctx)],
|
|
535
|
+
["recommended-.claudeignore", `${ctx.scan.recommendedClaudeIgnore.join("\n")}\n`],
|
|
536
|
+
["recommended-.cursorignore", `${ctx.scan.recommendedCursorIgnore.join("\n")}\n`],
|
|
537
|
+
["recommended-.gitignore-additions", renderGitignoreAdditions(ctx)],
|
|
538
|
+
];
|
|
539
|
+
if (ctx.backendDetected) pending.push(["backend-summary.md", renderBackendSummary(ctx)]);
|
|
540
|
+
if (ctx.frontendDetected) pending.push(["frontend-summary.md", renderFrontendSummary(ctx)]);
|
|
541
|
+
if (ctx.scope) pending.push([`${ctx.scope.toLowerCase()}-context.md`, renderScopedContext(ctx, ctx.scope)]);
|
|
542
|
+
return pending;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function runOptimize(rootDir = process.cwd(), options = {}) {
|
|
546
|
+
const ctx = createOptimizeContext(rootDir, options.scope || null);
|
|
547
|
+
const generated = [];
|
|
548
|
+
const pending = getOptimizePendingFiles(ctx);
|
|
549
|
+
|
|
550
|
+
if (options.dryRun) {
|
|
551
|
+
const generatedFiles = pending.map(([name]) => path.join(".prismo", name));
|
|
552
|
+
generatedFiles.push(".prismo/optimize-report.md");
|
|
553
|
+
return {
|
|
554
|
+
root: ctx.root,
|
|
555
|
+
scope: ctx.scope,
|
|
556
|
+
frameworks: ctx.frameworks,
|
|
557
|
+
generatedFiles,
|
|
558
|
+
warnings: ctx.warnings,
|
|
559
|
+
riskScore: ctx.scan.score,
|
|
560
|
+
estimatedContextReduction: ctx.estimatedContextReduction,
|
|
561
|
+
optimizationSuggestions: ctx.suggestions,
|
|
562
|
+
starterPrompt: renderStarterPrompt(ctx, ctx.scope),
|
|
563
|
+
generatedAt: ctx.generatedAt,
|
|
564
|
+
dryRun: true,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
for (const [name, contents] of pending) {
|
|
569
|
+
const written = writeGeneratedFile(ctx.root, path.join(".prismo", name), contents);
|
|
570
|
+
generated.push(written.path);
|
|
571
|
+
}
|
|
572
|
+
const report = renderOptimizeReport(ctx, [...generated, ".prismo/optimize-report.md"]);
|
|
573
|
+
const writtenReport = writeGeneratedFile(ctx.root, path.join(".prismo", "optimize-report.md"), report);
|
|
574
|
+
generated.push(writtenReport.path);
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
root: ctx.root,
|
|
578
|
+
scope: ctx.scope,
|
|
579
|
+
frameworks: ctx.frameworks,
|
|
580
|
+
generatedFiles: generated,
|
|
581
|
+
warnings: ctx.warnings,
|
|
582
|
+
riskScore: ctx.scan.score,
|
|
583
|
+
estimatedContextReduction: ctx.estimatedContextReduction,
|
|
584
|
+
optimizationSuggestions: ctx.suggestions,
|
|
585
|
+
starterPrompt: renderStarterPrompt(ctx, ctx.scope),
|
|
586
|
+
generatedAt: ctx.generatedAt,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function renderOptimizeTerminal(result) {
|
|
591
|
+
const lines = [];
|
|
592
|
+
lines.push("");
|
|
593
|
+
lines.push(color("Prismo Optimize", "bold"));
|
|
594
|
+
lines.push("");
|
|
595
|
+
lines.push("Detected:");
|
|
596
|
+
if (result.frameworks.length) result.frameworks.forEach((name) => lines.push(`- ${name}`));
|
|
597
|
+
else lines.push("- No common framework markers detected");
|
|
598
|
+
lines.push("");
|
|
599
|
+
lines.push("Generated:");
|
|
600
|
+
result.generatedFiles.forEach((file) => lines.push(`- [ok] ${file}`));
|
|
601
|
+
lines.push("");
|
|
602
|
+
lines.push("Optimization Opportunities:");
|
|
603
|
+
if (result.warnings.length) result.warnings.forEach((warning) => lines.push(`- ${warning}`));
|
|
604
|
+
else lines.push("- Use generated context packs to reduce repeated repo exploration");
|
|
605
|
+
result.optimizationSuggestions.slice(0, 4).forEach((suggestion) => lines.push(`- ${suggestion}`));
|
|
606
|
+
lines.push("");
|
|
607
|
+
lines.push(`Estimated Context Reduction: ${result.estimatedContextReduction}`);
|
|
608
|
+
lines.push("");
|
|
609
|
+
lines.push("Next Command:");
|
|
610
|
+
lines.push(`${NPX_COMMAND} context${result.scope ? ` ${result.scope}` : ""}`);
|
|
611
|
+
lines.push("");
|
|
612
|
+
lines.push("Starter Prompt:");
|
|
613
|
+
lines.push(result.starterPrompt);
|
|
614
|
+
lines.push("");
|
|
615
|
+
lines.push("Files are recommendations/templates only. No CLAUDE.md, AGENTS.md, .gitignore, .claudeignore, or .cursorignore files were overwritten.");
|
|
616
|
+
return lines.join("\n");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
createOptimizeContext,
|
|
622
|
+
detectFrameworks,
|
|
623
|
+
getContextFileForScope,
|
|
624
|
+
renderContextCommand,
|
|
625
|
+
renderOptimizeTerminal,
|
|
626
|
+
renderStarterPrompt,
|
|
627
|
+
runOptimize,
|
|
628
|
+
};
|
|
629
|
+
};
|