getprismo 0.1.7 → 0.1.9
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 +4 -0
- package/lib/prismo-dev/constants.js +13 -0
- package/lib/prismo-dev/context-optimize.js +28 -7
- package/lib/prismo-dev/fixes.js +8 -2
- package/lib/prismo-dev/report.js +10 -2
- package/lib/prismo-dev/scan.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -392,6 +392,10 @@ what doctor creates:
|
|
|
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`
|
|
@@ -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";
|
|
@@ -59,6 +59,15 @@ function detectFrameworks(root, result) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (pyprojectFiles.length && !frameworks.has("Python")) frameworks.add("Python");
|
|
62
|
+
const pythonFiles = [...textFiles.values()].filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)).slice(0, 80);
|
|
63
|
+
for (const file of pythonFiles) {
|
|
64
|
+
const text = readIfText(path.join(root, file.path), 128 * 1024) || "";
|
|
65
|
+
if (/FastAPI\s*\(|from\s+fastapi\s+import|APIRouter\s*\(/.test(text)) frameworks.add("FastAPI");
|
|
66
|
+
if (/from\s+django|import\s+django|DJANGO_SETTINGS_MODULE/.test(text)) frameworks.add("Django");
|
|
67
|
+
if (/Flask\s*\(|from\s+flask\s+import/.test(text)) frameworks.add("Flask");
|
|
68
|
+
if (/sqlalchemy|create_engine|SessionLocal/i.test(text)) frameworks.add("SQLAlchemy");
|
|
69
|
+
}
|
|
70
|
+
if (pythonFiles.length) frameworks.add("Python");
|
|
62
71
|
if ([...textFiles.keys()].some((rel) => rel.endsWith("Cargo.toml"))) frameworks.add("Rust");
|
|
63
72
|
if ([...textFiles.keys()].some((rel) => rel.endsWith("go.mod"))) frameworks.add("Go");
|
|
64
73
|
if ([...textFiles.keys()].some((rel) => rel.endsWith("docker-compose.yml") || rel.endsWith("docker-compose.yaml"))) frameworks.add("Docker");
|
|
@@ -178,10 +187,14 @@ function createOptimizeContext(rootDir = process.cwd(), scope = null) {
|
|
|
178
187
|
const frameworks = detectFrameworks(root, scan);
|
|
179
188
|
const folders = topLevelDirectories(root);
|
|
180
189
|
const entrypoints = detectEntrypoints(scan);
|
|
181
|
-
const backendDetected = folders.includes("backend") || frameworks.some((name) => ["FastAPI", "Django", "Flask"].includes(name));
|
|
182
|
-
const frontendDetected = folders.includes("frontend") || frameworks.some((name) => ["Next.js", "React", "Vite"].includes(name));
|
|
183
190
|
const backend = detectBackendPaths(scan);
|
|
184
191
|
const frontend = detectFrontendPaths(scan);
|
|
192
|
+
const backendDetected = folders.includes("backend") ||
|
|
193
|
+
frameworks.some((name) => ["FastAPI", "Django", "Flask"].includes(name)) ||
|
|
194
|
+
Boolean(backend.api.length || backend.services.length || backend.db.length || backend.auth.length);
|
|
195
|
+
const frontendDetected = folders.includes("frontend") ||
|
|
196
|
+
frameworks.some((name) => ["Next.js", "React", "Vite"].includes(name)) ||
|
|
197
|
+
Boolean(frontend.app.length || frontend.components.length || frontend.apiClient.length || frontend.state.length);
|
|
185
198
|
const warnings = [];
|
|
186
199
|
if (scan.exposedLargeFiles.length) warnings.push(`${scan.exposedLargeFiles.length} exposed large file(s) may bloat AI context.`);
|
|
187
200
|
if (!scan.hasClaudeIgnore) warnings.push(".claudeignore is missing.");
|
|
@@ -217,12 +230,20 @@ function mdList(items, empty = "None detected.") {
|
|
|
217
230
|
return items.map((item) => `- \`${item}\``).join("\n");
|
|
218
231
|
}
|
|
219
232
|
|
|
220
|
-
function
|
|
233
|
+
function topLoadBearing(root, files, allFiles, limit = 8) {
|
|
234
|
+
const textFiles = (allFiles || []).filter((file) => !file.ignored && file.kind !== "binary" && /\.(py|tsx?|jsx?)$/.test(file.path));
|
|
235
|
+
const corpus = textFiles.map((file) => `${file.path}\n${readIfText(path.join(root, file.path)) || ""}`);
|
|
221
236
|
return [...(files || [])]
|
|
222
237
|
.filter((file) => !file.ignored && file.kind !== "binary")
|
|
223
|
-
.
|
|
238
|
+
.map((file) => {
|
|
239
|
+
const base = path.basename(file.path).replace(/\.[^.]+$/, "");
|
|
240
|
+
const importName = base.replace(/[-.]/g, "_");
|
|
241
|
+
const refs = corpus.reduce((sum, text) => sum + (text.includes(base) || text.includes(importName) ? 1 : 0), 0);
|
|
242
|
+
return { file, refs };
|
|
243
|
+
})
|
|
244
|
+
.sort((a, b) => b.refs - a.refs || b.file.size - a.file.size)
|
|
224
245
|
.slice(0, limit)
|
|
225
|
-
.map((file) => `${file.path} (${formatBytes(file.size)})`);
|
|
246
|
+
.map(({ file, refs }) => `${file.path} (${refs} reference signal${refs === 1 ? "" : "s"}, ${formatBytes(file.size)})`);
|
|
226
247
|
}
|
|
227
248
|
|
|
228
249
|
function inferGaps(ctx) {
|
|
@@ -334,7 +355,7 @@ function renderArchitectureSummary(ctx) {
|
|
|
334
355
|
}
|
|
335
356
|
|
|
336
357
|
function renderBackendSummary(ctx) {
|
|
337
|
-
const backendCandidates =
|
|
358
|
+
const backendCandidates = topLoadBearing(ctx.root, ctx.scan.files.filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)), ctx.scan.files, 8);
|
|
338
359
|
return [
|
|
339
360
|
"# Backend Summary",
|
|
340
361
|
"",
|
|
@@ -378,7 +399,7 @@ function renderBackendSummary(ctx) {
|
|
|
378
399
|
}
|
|
379
400
|
|
|
380
401
|
function renderFrontendSummary(ctx) {
|
|
381
|
-
const frontendCandidates =
|
|
402
|
+
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);
|
|
382
403
|
return [
|
|
383
404
|
"# Frontend Summary",
|
|
384
405
|
"",
|
package/lib/prismo-dev/fixes.js
CHANGED
|
@@ -60,18 +60,24 @@ 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
|
}
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -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");
|
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,
|
|
@@ -1062,6 +1084,8 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1062
1084
|
".prismo/",
|
|
1063
1085
|
"prismo-optimized-CLAUDE.template.md",
|
|
1064
1086
|
]));
|
|
1087
|
+
const missingClaudeIgnoreSuggestions = hasClaudeIgnore ? missingIgnoreSuggestions(recommendedClaudeIgnore, claudeIgnorePatterns) : recommendedClaudeIgnore;
|
|
1088
|
+
const missingCursorIgnoreSuggestions = hasCursorIgnore ? missingIgnoreSuggestions(recommendedCursorIgnore, cursorIgnorePatterns) : recommendedCursorIgnore;
|
|
1065
1089
|
const recommendations = buildRecommendations({
|
|
1066
1090
|
hasClaudeIgnore,
|
|
1067
1091
|
gitignorePatterns,
|
|
@@ -1103,6 +1127,8 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1103
1127
|
repoDetected,
|
|
1104
1128
|
recommendedClaudeIgnore,
|
|
1105
1129
|
recommendedCursorIgnore,
|
|
1130
|
+
missingClaudeIgnoreSuggestions,
|
|
1131
|
+
missingCursorIgnoreSuggestions,
|
|
1106
1132
|
topTokenLeaks: getTopTokenLeaks(issues),
|
|
1107
1133
|
generatedAt: new Date().toISOString(),
|
|
1108
1134
|
};
|
package/package.json
CHANGED