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 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 topBySize(files, limit = 8) {
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
- .sort((a, b) => b.size - a.size)
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 = topBySize(ctx.scan.files.filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)), 8);
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 = topBySize(ctx.scan.files.filter((file) => /\.(tsx?|jsx?)$/.test(file.path) && /(^|\/)(frontend|src|app|components|hooks)\//.test(file.path)), 8);
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
  "",
@@ -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
- if (!options.dryRun) fs.writeFileSync(suggestedClaudeIgnorePath, `${result.recommendedClaudeIgnore.join("\n")}\n`, "utf8");
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
- if (!options.dryRun) fs.writeFileSync(suggestedCursorIgnorePath, `${result.recommendedCursorIgnore.join("\n")}\n`, "utf8");
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
  }
@@ -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");
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",