getprismo 0.1.6 → 0.1.8
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 +10 -6
- package/lib/prismo-dev/constants.js +13 -0
- package/lib/prismo-dev/context-optimize.js +130 -12
- package/lib/prismo-dev/fixes.js +14 -6
- package/lib/prismo-dev/report.js +14 -4
- package/lib/prismo-dev/scan.js +26 -2
- package/lib/prismo-dev-scan.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,8 +68,8 @@ Fixed:
|
|
|
68
68
|
- Created .cursorignore
|
|
69
69
|
- Generated prismo-dev-report.md
|
|
70
70
|
- Generated .prismo/architecture-summary.md
|
|
71
|
-
- Generated .prismo/recommended-CLAUDE.md
|
|
72
|
-
- Generated .prismo/recommended-AGENTS.md
|
|
71
|
+
- Generated .prismo/recommended-CLAUDE.boilerplate.md
|
|
72
|
+
- Generated .prismo/recommended-AGENTS.boilerplate.md
|
|
73
73
|
- Generated .prismo/recommended-.claudeignore
|
|
74
74
|
- Generated .prismo/recommended-.cursorignore
|
|
75
75
|
- Generated .prismo/recommended-.gitignore-additions
|
|
@@ -384,14 +384,18 @@ what doctor creates:
|
|
|
384
384
|
.prismo/architecture-summary.md compact project overview for agents
|
|
385
385
|
.prismo/backend-summary.md backend-specific context
|
|
386
386
|
.prismo/frontend-summary.md frontend-specific context
|
|
387
|
-
.prismo/recommended-CLAUDE.md
|
|
388
|
-
.prismo/recommended-AGENTS.md
|
|
387
|
+
.prismo/recommended-CLAUDE.boilerplate.md CLAUDE.md boilerplate reference; do not overwrite curated files
|
|
388
|
+
.prismo/recommended-AGENTS.boilerplate.md AGENTS.md boilerplate reference; do not overwrite curated files
|
|
389
389
|
.prismo/recommended-.claudeignore full recommended ignore list
|
|
390
390
|
.prismo/recommended-.cursorignore full recommended ignore list
|
|
391
391
|
.prismo/recommended-.gitignore-additions things your gitignore might be missing
|
|
392
392
|
prismo-dev-report.md full diagnostic report
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
+
if an existing `.claudeignore` or `.cursorignore` already covers prismo's recommendations, doctor skips the suggested ignore file instead of creating redundant noise. the default recommendations include common project state, local db, export, credential, and token patterns such as `*_state.json`, `*_tokens.json`, `*_export.json`, `*.sqlite`, `models/`, and `state-backups/`.
|
|
396
|
+
|
|
397
|
+
backend and frontend summaries include load-bearing candidates ranked by lightweight reference signals plus file size, not just directory listings.
|
|
398
|
+
|
|
395
399
|
what doctor never touches:
|
|
396
400
|
|
|
397
401
|
- your real `CLAUDE.md`
|
|
@@ -600,8 +604,8 @@ no api keys. no intercepted prompts. no data uploaded.
|
|
|
600
604
|
├── frontend-summary.md
|
|
601
605
|
├── frontend-context.md
|
|
602
606
|
├── backend-context.md
|
|
603
|
-
├── recommended-CLAUDE.md
|
|
604
|
-
├── recommended-AGENTS.md
|
|
607
|
+
├── recommended-CLAUDE.boilerplate.md
|
|
608
|
+
├── recommended-AGENTS.boilerplate.md
|
|
605
609
|
├── recommended-.claudeignore
|
|
606
610
|
├── recommended-.cursorignore
|
|
607
611
|
├── recommended-.gitignore-additions
|
|
@@ -122,6 +122,19 @@ const DEFAULT_CLAUDEIGNORE = [
|
|
|
122
122
|
"pnpm-lock.yaml",
|
|
123
123
|
"test-results/",
|
|
124
124
|
"playwright-report/",
|
|
125
|
+
"models/",
|
|
126
|
+
"state-backups/",
|
|
127
|
+
"backups/",
|
|
128
|
+
"*.sqlite",
|
|
129
|
+
"*.sqlite3",
|
|
130
|
+
"*.db",
|
|
131
|
+
"*_state.json",
|
|
132
|
+
"*_tokens.json",
|
|
133
|
+
"*_export.json",
|
|
134
|
+
"*secret*.json",
|
|
135
|
+
"*credential*.json",
|
|
136
|
+
".env",
|
|
137
|
+
".env.*",
|
|
125
138
|
];
|
|
126
139
|
|
|
127
140
|
const NPX_COMMAND = "npx getprismo";
|
|
@@ -122,18 +122,49 @@ function isNonSourcePath(rel) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function detectBackendPaths(result) {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
125
|
+
const pythonText = (file) => readIfText(path.join(result.root, file.path), 256 * 1024) || "";
|
|
126
|
+
const isRootPython = (rel) => /^[^/]+\.py$/.test(rel);
|
|
127
|
+
const isPython = (rel) => rel.endsWith(".py");
|
|
128
|
+
const hasFastApiSignal = (file) => /FastAPI\s*\(|APIRouter\s*\(|@app\.(get|post|put|patch|delete)|@router\.(get|post|put|patch|delete)/.test(pythonText(file));
|
|
129
|
+
const api = findRepoFiles(result, (rel, file) => !isNonSourcePath(rel) && (
|
|
130
|
+
/(backend|app|src)\/.*(router|routes|api)\//.test(rel) ||
|
|
131
|
+
/(backend|app|src)\/.*(router|routes).*\.py$/.test(rel) ||
|
|
132
|
+
/\/(routing|routers?)\.py$/.test(rel) ||
|
|
133
|
+
(isRootPython(rel) && hasFastApiSignal(file))
|
|
134
|
+
), 30).map((f) => f.path);
|
|
135
|
+
const services = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
|
|
136
|
+
/(backend|app|src)\/.*(service|services|application)/.test(rel) ||
|
|
137
|
+
/\/applications?\.py$/.test(rel) ||
|
|
138
|
+
(isRootPython(rel) && /(service|worker|manager|client|heartbeat|memory|chat|tool|orchestrator|pipeline)/i.test(rel))
|
|
139
|
+
), 40).map((f) => f.path);
|
|
140
|
+
const models = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
|
|
141
|
+
/(backend|app|src)\/.*models\.py$/.test(rel) ||
|
|
142
|
+
/(backend|app|src)\/.*schema/.test(rel) ||
|
|
143
|
+
/\/models\.py$/.test(rel) ||
|
|
144
|
+
(isRootPython(rel) && /(model|schema|entity|types?)/i.test(rel))
|
|
145
|
+
), 30).map((f) => f.path);
|
|
146
|
+
const db = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
|
|
147
|
+
/(backend|app|src)\/.*\/(db|database|alembic|migrations)[/.]/.test(rel) ||
|
|
148
|
+
(isPython(rel) && /(sqlite|qdrant|neo4j|database|storage|repository|vector|graph|migration)/i.test(rel))
|
|
149
|
+
), 30).map((f) => f.path);
|
|
150
|
+
const config = findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (
|
|
151
|
+
/(backend|app|src)\/.*(config|settings|env).*\.py$/.test(rel) ||
|
|
152
|
+
(isRootPython(rel) && /(config|settings|env)/i.test(rel)) ||
|
|
153
|
+
rel.endsWith("requirements.txt") ||
|
|
154
|
+
rel === "pyproject.toml"
|
|
155
|
+
), 20).map((f) => f.path);
|
|
156
|
+
const auth = findRepoFiles(result, (rel, file) => !isNonSourcePath(rel) && (
|
|
157
|
+
/(backend|app|src)\/.*auth/.test(rel) ||
|
|
158
|
+
/\/security\.py$/.test(rel) ||
|
|
159
|
+
(isPython(rel) && /(auth|security|permission|token|session|oauth|jwt|middleware)/i.test(rel)) ||
|
|
160
|
+
(isRootPython(rel) && /(Depends|HTTPBearer|OAuth2|JWT|Authorization)/.test(pythonText(file)))
|
|
161
|
+
), 30).map((f) => f.path);
|
|
131
162
|
return { api, services, models, db, config, auth };
|
|
132
163
|
}
|
|
133
164
|
|
|
134
165
|
function detectFrontendPaths(result) {
|
|
135
166
|
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),
|
|
167
|
+
app: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/frontend\/src\/app\//.test(rel) || /frontend\/src\/App\.[jt]sx?$/.test(rel) || /src\/App\.[jt]sx?$/.test(rel) || /src\/app\//.test(rel) || /apps\/[^/]+\/app\//.test(rel) || /apps\/[^/]+\/src\/app\//.test(rel)), 24).map((f) => f.path),
|
|
137
168
|
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
169
|
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
170
|
styling: findRepoFiles(result, (rel) => !isNonSourcePath(rel) && (/tailwind\.config|globals\.css|\.module\.css|frontend\/src\/app\/globals/.test(rel)), 20).map((f) => f.path),
|
|
@@ -186,6 +217,68 @@ function mdList(items, empty = "None detected.") {
|
|
|
186
217
|
return items.map((item) => `- \`${item}\``).join("\n");
|
|
187
218
|
}
|
|
188
219
|
|
|
220
|
+
function topLoadBearing(root, files, allFiles, limit = 8) {
|
|
221
|
+
const textFiles = (allFiles || []).filter((file) => !file.ignored && file.kind !== "binary" && /\.(py|tsx?|jsx?)$/.test(file.path));
|
|
222
|
+
const corpus = textFiles.map((file) => `${file.path}\n${readIfText(path.join(root, file.path)) || ""}`);
|
|
223
|
+
return [...(files || [])]
|
|
224
|
+
.filter((file) => !file.ignored && file.kind !== "binary")
|
|
225
|
+
.map((file) => {
|
|
226
|
+
const base = path.basename(file.path).replace(/\.[^.]+$/, "");
|
|
227
|
+
const importName = base.replace(/[-.]/g, "_");
|
|
228
|
+
const refs = corpus.reduce((sum, text) => sum + (text.includes(base) || text.includes(importName) ? 1 : 0), 0);
|
|
229
|
+
return { file, refs };
|
|
230
|
+
})
|
|
231
|
+
.sort((a, b) => b.refs - a.refs || b.file.size - a.file.size)
|
|
232
|
+
.slice(0, limit)
|
|
233
|
+
.map(({ file, refs }) => `${file.path} (${refs} reference signal${refs === 1 ? "" : "s"}, ${formatBytes(file.size)})`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function inferGaps(ctx) {
|
|
237
|
+
const gaps = [];
|
|
238
|
+
if (ctx.backendDetected && !ctx.backend.api.length) {
|
|
239
|
+
gaps.push("No conventional backend API layout detected; review root-level Python files or custom service modules for FastAPI/APIRouter usage.");
|
|
240
|
+
}
|
|
241
|
+
if (ctx.backendDetected && !ctx.backend.services.length) {
|
|
242
|
+
gaps.push("No conventional service directory detected; backend services may use flat files or project-specific naming.");
|
|
243
|
+
}
|
|
244
|
+
if (ctx.frontendDetected && !ctx.frontend.app.length) {
|
|
245
|
+
gaps.push("No conventional frontend routing surface detected; Vite/React routes may live in App.tsx or custom tab components.");
|
|
246
|
+
}
|
|
247
|
+
return gaps;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function summarizeRiskyDirectories(dirs) {
|
|
251
|
+
const groups = new Map();
|
|
252
|
+
for (const dir of dirs || []) {
|
|
253
|
+
const normalized = dir.path.replace(/\\/g, "/");
|
|
254
|
+
const parts = normalized.split("/");
|
|
255
|
+
let key = `${normalized}/`;
|
|
256
|
+
if (parts.includes("__pycache__")) {
|
|
257
|
+
const before = parts.slice(0, parts.indexOf("__pycache__"));
|
|
258
|
+
const prefix = before.length ? `${before[0]}/**` : "**";
|
|
259
|
+
key = `${prefix}/__pycache__/`;
|
|
260
|
+
} else if (parts.includes("node_modules")) {
|
|
261
|
+
key = `${parts.slice(0, parts.indexOf("node_modules") + 1).join("/")}/**`;
|
|
262
|
+
} else if (parts.length > 2) {
|
|
263
|
+
key = `${parts.slice(0, 2).join("/")}/**/${parts[parts.length - 1]}/`;
|
|
264
|
+
}
|
|
265
|
+
const existing = groups.get(key) || { key, count: 0, exposed: 0, ignored: 0, examples: [] };
|
|
266
|
+
existing.count += 1;
|
|
267
|
+
if (dir.exposed) existing.exposed += 1;
|
|
268
|
+
else existing.ignored += 1;
|
|
269
|
+
if (existing.examples.length < 2) existing.examples.push(`${normalized}/`);
|
|
270
|
+
groups.set(key, existing);
|
|
271
|
+
}
|
|
272
|
+
return Array.from(groups.values())
|
|
273
|
+
.sort((a, b) => b.exposed - a.exposed || b.count - a.count)
|
|
274
|
+
.slice(0, 25)
|
|
275
|
+
.map((group) => {
|
|
276
|
+
const state = group.exposed ? `${group.exposed} exposed` : `${group.ignored} ignored`;
|
|
277
|
+
const example = group.examples[0] && group.examples[0] !== group.key ? `; e.g. ${group.examples[0]}` : "";
|
|
278
|
+
return `${group.key} (${group.count} director${group.count === 1 ? "y" : "ies"}, ${state}${example})`;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
189
282
|
function proseList(items, empty = "none detected") {
|
|
190
283
|
return items && items.length ? items.join(", ") : empty;
|
|
191
284
|
}
|
|
@@ -194,6 +287,7 @@ function renderArchitectureSummary(ctx) {
|
|
|
194
287
|
const apiLayer = ctx.backend.api.slice(0, 6);
|
|
195
288
|
const dbLayer = ctx.backend.db.slice(0, 6);
|
|
196
289
|
const frontendLayer = ctx.frontend.app.slice(0, 6);
|
|
290
|
+
const gaps = inferGaps(ctx);
|
|
197
291
|
const readOrder = [
|
|
198
292
|
"- Start here.",
|
|
199
293
|
ctx.backendDetected ? "- For backend work, read `.prismo/backend-summary.md` next." : null,
|
|
@@ -234,6 +328,10 @@ function renderArchitectureSummary(ctx) {
|
|
|
234
328
|
"",
|
|
235
329
|
ctx.warnings.length ? ctx.warnings.map((warning) => `- ${warning}`).join("\n") : "- No major local context risks detected.",
|
|
236
330
|
"",
|
|
331
|
+
"## Detection Gaps",
|
|
332
|
+
"",
|
|
333
|
+
gaps.length ? gaps.map((gap) => `- ${gap}`).join("\n") : "- No major architecture-detection gaps surfaced.",
|
|
334
|
+
"",
|
|
237
335
|
"## AI Workflow Notes",
|
|
238
336
|
"",
|
|
239
337
|
"- Prefer this summary before broad repo reads.",
|
|
@@ -244,6 +342,7 @@ function renderArchitectureSummary(ctx) {
|
|
|
244
342
|
}
|
|
245
343
|
|
|
246
344
|
function renderBackendSummary(ctx) {
|
|
345
|
+
const backendCandidates = topLoadBearing(ctx.root, ctx.scan.files.filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)), ctx.scan.files, 8);
|
|
247
346
|
return [
|
|
248
347
|
"# Backend Summary",
|
|
249
348
|
"",
|
|
@@ -273,10 +372,21 @@ function renderBackendSummary(ctx) {
|
|
|
273
372
|
"",
|
|
274
373
|
mdList(ctx.backend.config),
|
|
275
374
|
"",
|
|
375
|
+
"## Load-Bearing Candidates",
|
|
376
|
+
"",
|
|
377
|
+
mdList(backendCandidates, "No Python source candidates detected."),
|
|
378
|
+
"",
|
|
379
|
+
"## Detection Notes",
|
|
380
|
+
"",
|
|
381
|
+
ctx.backend.api.length || ctx.backend.services.length
|
|
382
|
+
? "- Backend paths were inferred from conventional directories plus root-level Python/FastAPI/service filename signals."
|
|
383
|
+
: "- No conventional backend paths were detected; inspect root-level Python modules and custom service naming manually.",
|
|
384
|
+
"",
|
|
276
385
|
].join("\n");
|
|
277
386
|
}
|
|
278
387
|
|
|
279
388
|
function renderFrontendSummary(ctx) {
|
|
389
|
+
const frontendCandidates = topLoadBearing(ctx.root, ctx.scan.files.filter((file) => /\.(tsx?|jsx?)$/.test(file.path) && /(^|\/)(frontend|src|app|components|hooks)\//.test(file.path)), ctx.scan.files, 8);
|
|
280
390
|
return [
|
|
281
391
|
"# Frontend Summary",
|
|
282
392
|
"",
|
|
@@ -302,6 +412,10 @@ function renderFrontendSummary(ctx) {
|
|
|
302
412
|
"",
|
|
303
413
|
mdList(ctx.frontend.styling),
|
|
304
414
|
"",
|
|
415
|
+
"## Load-Bearing Candidates",
|
|
416
|
+
"",
|
|
417
|
+
mdList(frontendCandidates, "No frontend source candidates detected."),
|
|
418
|
+
"",
|
|
305
419
|
].join("\n");
|
|
306
420
|
}
|
|
307
421
|
|
|
@@ -319,7 +433,9 @@ function renderRecommendedClaude(ctx) {
|
|
|
319
433
|
if (hasPath(ctx.scan, (rel) => rel === "frontend/package.json")) commands.push("cd frontend && npm run test");
|
|
320
434
|
if (hasPath(ctx.scan, (rel) => rel === "backend/pytest.ini" || rel.startsWith("backend/tests/"))) commands.push("cd backend && pytest");
|
|
321
435
|
return [
|
|
322
|
-
"# CLAUDE.md",
|
|
436
|
+
"# CLAUDE.md Boilerplate",
|
|
437
|
+
"",
|
|
438
|
+
"Do not overwrite an existing curated CLAUDE.md with this file. Use it as a diff/reference for missing compact-context guidance only.",
|
|
323
439
|
"",
|
|
324
440
|
"Keep context small. Start with `.prismo/architecture-summary.md`; use scoped `.prismo/*-summary.md` files only when relevant.",
|
|
325
441
|
"",
|
|
@@ -351,7 +467,9 @@ function renderRecommendedClaude(ctx) {
|
|
|
351
467
|
|
|
352
468
|
function renderRecommendedAgents(ctx) {
|
|
353
469
|
return [
|
|
354
|
-
"# AGENTS.md",
|
|
470
|
+
"# AGENTS.md Boilerplate",
|
|
471
|
+
"",
|
|
472
|
+
"Do not overwrite an existing curated AGENTS.md with this file. Use it as a diff/reference for missing compact-context guidance only.",
|
|
355
473
|
"",
|
|
356
474
|
"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
475
|
"",
|
|
@@ -413,7 +531,7 @@ function renderOptimizeReport(ctx, generatedFiles) {
|
|
|
413
531
|
"",
|
|
414
532
|
"## Token-Heavy Directories",
|
|
415
533
|
"",
|
|
416
|
-
mdList(ctx.scan.highRiskDirs
|
|
534
|
+
mdList(summarizeRiskyDirectories(ctx.scan.highRiskDirs)),
|
|
417
535
|
"",
|
|
418
536
|
"## Optimization Suggestions",
|
|
419
537
|
"",
|
|
@@ -530,8 +648,8 @@ function renderContextCommand(ctx, scope = null) {
|
|
|
530
648
|
function getOptimizePendingFiles(ctx) {
|
|
531
649
|
const pending = [
|
|
532
650
|
["architecture-summary.md", renderArchitectureSummary(ctx)],
|
|
533
|
-
["recommended-CLAUDE.md", renderRecommendedClaude(ctx)],
|
|
534
|
-
["recommended-AGENTS.md", renderRecommendedAgents(ctx)],
|
|
651
|
+
["recommended-CLAUDE.boilerplate.md", renderRecommendedClaude(ctx)],
|
|
652
|
+
["recommended-AGENTS.boilerplate.md", renderRecommendedAgents(ctx)],
|
|
535
653
|
["recommended-.claudeignore", `${ctx.scan.recommendedClaudeIgnore.join("\n")}\n`],
|
|
536
654
|
["recommended-.cursorignore", `${ctx.scan.recommendedCursorIgnore.join("\n")}\n`],
|
|
537
655
|
["recommended-.gitignore-additions", renderGitignoreAdditions(ctx)],
|
package/lib/prismo-dev/fixes.js
CHANGED
|
@@ -60,25 +60,31 @@ function applyFixes(result, options = {}) {
|
|
|
60
60
|
if (!result.hasClaudeIgnore) {
|
|
61
61
|
if (!options.dryRun) fs.writeFileSync(claudeIgnorePath, `${result.recommendedClaudeIgnore.join("\n")}\n`, "utf8");
|
|
62
62
|
actions.push(`${dryRunPrefix} .claudeignore`);
|
|
63
|
+
} else if (result.missingClaudeIgnoreSuggestions && result.missingClaudeIgnoreSuggestions.length === 0) {
|
|
64
|
+
actions.push("Skipped .claudeignore.prismo-suggested because existing .claudeignore already covers Prismo recommendations");
|
|
63
65
|
} else {
|
|
64
66
|
const backupPath = options.dryRun ? null : backupIfExists(suggestedClaudeIgnorePath);
|
|
65
|
-
|
|
67
|
+
const suggestions = result.missingClaudeIgnoreSuggestions || result.recommendedClaudeIgnore;
|
|
68
|
+
if (!options.dryRun) fs.writeFileSync(suggestedClaudeIgnorePath, `${suggestions.join("\n")}\n`, "utf8");
|
|
66
69
|
actions.push(`${dryRunPrefix} .claudeignore.prismo-suggested because .claudeignore already exists`);
|
|
67
70
|
if (backupPath) actions.push(`Backed up existing .claudeignore.prismo-suggested to ${path.basename(backupPath)}`);
|
|
68
71
|
}
|
|
69
72
|
if (!result.hasCursorIgnore) {
|
|
70
73
|
if (!options.dryRun) fs.writeFileSync(cursorIgnorePath, `${result.recommendedCursorIgnore.join("\n")}\n`, "utf8");
|
|
71
74
|
actions.push(`${dryRunPrefix} .cursorignore`);
|
|
75
|
+
} else if (result.missingCursorIgnoreSuggestions && result.missingCursorIgnoreSuggestions.length === 0) {
|
|
76
|
+
actions.push("Skipped .cursorignore.prismo-suggested because existing .cursorignore already covers Prismo recommendations");
|
|
72
77
|
} else {
|
|
73
78
|
const backupPath = options.dryRun ? null : backupIfExists(suggestedCursorIgnorePath);
|
|
74
|
-
|
|
79
|
+
const suggestions = result.missingCursorIgnoreSuggestions || result.recommendedCursorIgnore;
|
|
80
|
+
if (!options.dryRun) fs.writeFileSync(suggestedCursorIgnorePath, `${suggestions.join("\n")}\n`, "utf8");
|
|
75
81
|
actions.push(`${dryRunPrefix} .cursorignore.prismo-suggested because .cursorignore already exists`);
|
|
76
82
|
if (backupPath) actions.push(`Backed up existing .cursorignore.prismo-suggested to ${path.basename(backupPath)}`);
|
|
77
83
|
}
|
|
78
84
|
if (ignoresOnly) return actions;
|
|
79
85
|
|
|
80
|
-
const report = options.dryRun ? { reportPath: path.join(result.root, "prismo-dev-report.md"), backupPath: null } : writeReport(result);
|
|
81
|
-
actions.push(`${options.dryRun ? "Would generate" : "Generated"}
|
|
86
|
+
const report = options.dryRun ? { reportPath: path.join(result.root, ".prismo", "prismo-dev-report.md"), backupPath: null } : writeReport(result);
|
|
87
|
+
actions.push(`${options.dryRun ? "Would generate" : "Generated"} .prismo/${path.basename(report.reportPath)}`);
|
|
82
88
|
if (report.backupPath) actions.push(`Backed up existing report to ${path.basename(report.backupPath)}`);
|
|
83
89
|
|
|
84
90
|
const claudeFile = result.instructionFiles.find((file) => file.isClaude);
|
|
@@ -92,10 +98,12 @@ function applyFixes(result, options = {}) {
|
|
|
92
98
|
|
|
93
99
|
const hasCodexRisk = result.issues.some((issue) => issue.category === "codex_config");
|
|
94
100
|
if (hasCodexRisk || result.instructionFiles.some((file) => file.path === "AGENTS.md" || file.path.startsWith(".codex/"))) {
|
|
95
|
-
const
|
|
101
|
+
const prismoDir = path.join(result.root, ".prismo");
|
|
102
|
+
if (!options.dryRun) fs.mkdirSync(prismoDir, { recursive: true });
|
|
103
|
+
const codexPath = path.join(prismoDir, "prismo-AGENTS-recommendations.md");
|
|
96
104
|
const backupPath = options.dryRun ? null : backupIfExists(codexPath);
|
|
97
105
|
if (!options.dryRun) fs.writeFileSync(codexPath, renderAgentsRecommendations(result), "utf8");
|
|
98
|
-
actions.push(`${options.dryRun ? "Would generate" : "Generated"} prismo-AGENTS-recommendations.md`);
|
|
106
|
+
actions.push(`${options.dryRun ? "Would generate" : "Generated"} .prismo/prismo-AGENTS-recommendations.md`);
|
|
99
107
|
if (backupPath) actions.push(`Backed up existing AGENTS recommendations to ${path.basename(backupPath)}`);
|
|
100
108
|
}
|
|
101
109
|
return actions;
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -94,7 +94,7 @@ function renderTerminalReport(result, options = {}) {
|
|
|
94
94
|
? "No matching local usage sessions were found; repo-risk estimates remain heuristic."
|
|
95
95
|
: "Potential savings estimates are heuristic and local-only, not provider billing data.");
|
|
96
96
|
lines.push("");
|
|
97
|
-
lines.push(reportEnabled ? "Report: prismo-dev-report.md" : "Report: skipped (--no-report)");
|
|
97
|
+
lines.push(reportEnabled ? "Report: .prismo/prismo-dev-report.md" : "Report: skipped (--no-report)");
|
|
98
98
|
return lines.join("\n");
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -312,14 +312,22 @@ function renderMarkdownReport(result) {
|
|
|
312
312
|
lines.push("");
|
|
313
313
|
lines.push("## Recommended .claudeignore");
|
|
314
314
|
lines.push("");
|
|
315
|
+
if (result.hasClaudeIgnore && result.missingClaudeIgnoreSuggestions && !result.missingClaudeIgnoreSuggestions.length) {
|
|
316
|
+
lines.push("Existing .claudeignore already covers Prismo recommendations.");
|
|
317
|
+
lines.push("");
|
|
318
|
+
}
|
|
315
319
|
lines.push("```gitignore");
|
|
316
|
-
result.recommendedClaudeIgnore.forEach((line) => lines.push(line));
|
|
320
|
+
(result.hasClaudeIgnore && result.missingClaudeIgnoreSuggestions ? result.missingClaudeIgnoreSuggestions : result.recommendedClaudeIgnore).forEach((line) => lines.push(line));
|
|
317
321
|
lines.push("```");
|
|
318
322
|
lines.push("");
|
|
319
323
|
lines.push("## Recommended .cursorignore");
|
|
320
324
|
lines.push("");
|
|
325
|
+
if (result.hasCursorIgnore && result.missingCursorIgnoreSuggestions && !result.missingCursorIgnoreSuggestions.length) {
|
|
326
|
+
lines.push("Existing .cursorignore already covers Prismo recommendations.");
|
|
327
|
+
lines.push("");
|
|
328
|
+
}
|
|
321
329
|
lines.push("```gitignore");
|
|
322
|
-
result.recommendedCursorIgnore.forEach((line) => lines.push(line));
|
|
330
|
+
(result.hasCursorIgnore && result.missingCursorIgnoreSuggestions ? result.missingCursorIgnoreSuggestions : result.recommendedCursorIgnore).forEach((line) => lines.push(line));
|
|
323
331
|
lines.push("```");
|
|
324
332
|
lines.push("");
|
|
325
333
|
lines.push("## Recommended Next Steps");
|
|
@@ -342,7 +350,9 @@ function backupIfExists(filePath) {
|
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
function writeReport(result) {
|
|
345
|
-
const
|
|
353
|
+
const prismoDir = path.join(result.root, ".prismo");
|
|
354
|
+
fs.mkdirSync(prismoDir, { recursive: true });
|
|
355
|
+
const reportPath = path.join(prismoDir, "prismo-dev-report.md");
|
|
346
356
|
const backupPath = backupIfExists(reportPath);
|
|
347
357
|
fs.writeFileSync(reportPath, renderMarkdownReport(result), "utf8");
|
|
348
358
|
return { reportPath, backupPath };
|
package/lib/prismo-dev/scan.js
CHANGED
|
@@ -77,6 +77,26 @@ function isIgnored(relPath, patterns, isDir = false) {
|
|
|
77
77
|
return patterns.some((pattern) => patternMatches(pattern, relPath, isDir));
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function ignoreSuggestionCovered(pattern, existingPatterns) {
|
|
81
|
+
if (!pattern) return true;
|
|
82
|
+
if (existingPatterns.includes(pattern)) return true;
|
|
83
|
+
const sample = pattern
|
|
84
|
+
.replace(/^\*\//, "")
|
|
85
|
+
.replace(/^\*\*/, "sample")
|
|
86
|
+
.replace(/\*/g, "sample")
|
|
87
|
+
.replace(/\/$/, "");
|
|
88
|
+
const isDir = pattern.endsWith("/") || pattern.endsWith("/**");
|
|
89
|
+
return existingPatterns.some((existing) => {
|
|
90
|
+
if (existing === pattern) return true;
|
|
91
|
+
if (existing.endsWith("/") && pattern.startsWith(existing)) return true;
|
|
92
|
+
return patternMatches(existing, sample, isDir);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function missingIgnoreSuggestions(recommended, existingPatterns) {
|
|
97
|
+
return recommended.filter((pattern) => !ignoreSuggestionCovered(pattern, existingPatterns));
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
function getFileKind(filePath) {
|
|
81
101
|
const ext = path.extname(filePath).toLowerCase();
|
|
82
102
|
const name = path.basename(filePath).toLowerCase();
|
|
@@ -703,6 +723,8 @@ function toJsonPayload(result) {
|
|
|
703
723
|
proxyTrackingReadiness: result.proxyTrackingReadiness,
|
|
704
724
|
suggestedClaudeIgnore: result.recommendedClaudeIgnore,
|
|
705
725
|
suggestedCursorIgnore: result.recommendedCursorIgnore,
|
|
726
|
+
missingClaudeIgnoreSuggestions: result.missingClaudeIgnoreSuggestions || [],
|
|
727
|
+
missingCursorIgnoreSuggestions: result.missingCursorIgnoreSuggestions || [],
|
|
706
728
|
nextCommands: getNextCommands(result),
|
|
707
729
|
generatedAt: result.generatedAt,
|
|
708
730
|
scannedPath: result.root,
|
|
@@ -1060,10 +1082,10 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1060
1082
|
const recommendedCursorIgnore = Array.from(new Set([
|
|
1061
1083
|
...recommendedClaudeIgnore,
|
|
1062
1084
|
".prismo/",
|
|
1063
|
-
"prismo-dev-report.md",
|
|
1064
1085
|
"prismo-optimized-CLAUDE.template.md",
|
|
1065
|
-
"prismo-AGENTS-recommendations.md",
|
|
1066
1086
|
]));
|
|
1087
|
+
const missingClaudeIgnoreSuggestions = hasClaudeIgnore ? missingIgnoreSuggestions(recommendedClaudeIgnore, claudeIgnorePatterns) : recommendedClaudeIgnore;
|
|
1088
|
+
const missingCursorIgnoreSuggestions = hasCursorIgnore ? missingIgnoreSuggestions(recommendedCursorIgnore, cursorIgnorePatterns) : recommendedCursorIgnore;
|
|
1067
1089
|
const recommendations = buildRecommendations({
|
|
1068
1090
|
hasClaudeIgnore,
|
|
1069
1091
|
gitignorePatterns,
|
|
@@ -1105,6 +1127,8 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1105
1127
|
repoDetected,
|
|
1106
1128
|
recommendedClaudeIgnore,
|
|
1107
1129
|
recommendedCursorIgnore,
|
|
1130
|
+
missingClaudeIgnoreSuggestions,
|
|
1131
|
+
missingCursorIgnoreSuggestions,
|
|
1108
1132
|
topTokenLeaks: getTopTokenLeaks(issues),
|
|
1109
1133
|
generatedAt: new Date().toISOString(),
|
|
1110
1134
|
};
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -304,7 +304,7 @@ Options:
|
|
|
304
304
|
--json Output valid JSON only for CI or future dashboard ingestion.
|
|
305
305
|
--usage Include real local Codex/Claude Code session usage in scan diagnostics.
|
|
306
306
|
--simple Print a plain-English scan summary for first-time or non-technical users.
|
|
307
|
-
--no-report Do not write prismo-dev-report.md.
|
|
307
|
+
--no-report Do not write .prismo/prismo-dev-report.md.
|
|
308
308
|
--limit N Number of recent local sessions to show.
|
|
309
309
|
--interval N Refresh interval in seconds for watch mode.
|
|
310
310
|
--dry-run Preview doctor/fix actions without writing files.
|
package/package.json
CHANGED