getprismo 0.1.6 → 0.1.7

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
@@ -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,8 +384,8 @@ 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 optimized CLAUDE.md template
388
- .prismo/recommended-AGENTS.md optimized AGENTS.md template
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
@@ -600,8 +600,8 @@ no api keys. no intercepted prompts. no data uploaded.
600
600
  ├── frontend-summary.md
601
601
  ├── frontend-context.md
602
602
  ├── backend-context.md
603
- ├── recommended-CLAUDE.md
604
- ├── recommended-AGENTS.md
603
+ ├── recommended-CLAUDE.boilerplate.md
604
+ ├── recommended-AGENTS.boilerplate.md
605
605
  ├── recommended-.claudeignore
606
606
  ├── recommended-.cursorignore
607
607
  ├── recommended-.gitignore-additions
@@ -122,18 +122,49 @@ function isNonSourcePath(rel) {
122
122
  }
123
123
 
124
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);
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,60 @@ function mdList(items, empty = "None detected.") {
186
217
  return items.map((item) => `- \`${item}\``).join("\n");
187
218
  }
188
219
 
220
+ function topBySize(files, limit = 8) {
221
+ return [...(files || [])]
222
+ .filter((file) => !file.ignored && file.kind !== "binary")
223
+ .sort((a, b) => b.size - a.size)
224
+ .slice(0, limit)
225
+ .map((file) => `${file.path} (${formatBytes(file.size)})`);
226
+ }
227
+
228
+ function inferGaps(ctx) {
229
+ const gaps = [];
230
+ if (ctx.backendDetected && !ctx.backend.api.length) {
231
+ gaps.push("No conventional backend API layout detected; review root-level Python files or custom service modules for FastAPI/APIRouter usage.");
232
+ }
233
+ if (ctx.backendDetected && !ctx.backend.services.length) {
234
+ gaps.push("No conventional service directory detected; backend services may use flat files or project-specific naming.");
235
+ }
236
+ if (ctx.frontendDetected && !ctx.frontend.app.length) {
237
+ gaps.push("No conventional frontend routing surface detected; Vite/React routes may live in App.tsx or custom tab components.");
238
+ }
239
+ return gaps;
240
+ }
241
+
242
+ function summarizeRiskyDirectories(dirs) {
243
+ const groups = new Map();
244
+ for (const dir of dirs || []) {
245
+ const normalized = dir.path.replace(/\\/g, "/");
246
+ const parts = normalized.split("/");
247
+ let key = `${normalized}/`;
248
+ if (parts.includes("__pycache__")) {
249
+ const before = parts.slice(0, parts.indexOf("__pycache__"));
250
+ const prefix = before.length ? `${before[0]}/**` : "**";
251
+ key = `${prefix}/__pycache__/`;
252
+ } else if (parts.includes("node_modules")) {
253
+ key = `${parts.slice(0, parts.indexOf("node_modules") + 1).join("/")}/**`;
254
+ } else if (parts.length > 2) {
255
+ key = `${parts.slice(0, 2).join("/")}/**/${parts[parts.length - 1]}/`;
256
+ }
257
+ const existing = groups.get(key) || { key, count: 0, exposed: 0, ignored: 0, examples: [] };
258
+ existing.count += 1;
259
+ if (dir.exposed) existing.exposed += 1;
260
+ else existing.ignored += 1;
261
+ if (existing.examples.length < 2) existing.examples.push(`${normalized}/`);
262
+ groups.set(key, existing);
263
+ }
264
+ return Array.from(groups.values())
265
+ .sort((a, b) => b.exposed - a.exposed || b.count - a.count)
266
+ .slice(0, 25)
267
+ .map((group) => {
268
+ const state = group.exposed ? `${group.exposed} exposed` : `${group.ignored} ignored`;
269
+ const example = group.examples[0] && group.examples[0] !== group.key ? `; e.g. ${group.examples[0]}` : "";
270
+ return `${group.key} (${group.count} director${group.count === 1 ? "y" : "ies"}, ${state}${example})`;
271
+ });
272
+ }
273
+
189
274
  function proseList(items, empty = "none detected") {
190
275
  return items && items.length ? items.join(", ") : empty;
191
276
  }
@@ -194,6 +279,7 @@ function renderArchitectureSummary(ctx) {
194
279
  const apiLayer = ctx.backend.api.slice(0, 6);
195
280
  const dbLayer = ctx.backend.db.slice(0, 6);
196
281
  const frontendLayer = ctx.frontend.app.slice(0, 6);
282
+ const gaps = inferGaps(ctx);
197
283
  const readOrder = [
198
284
  "- Start here.",
199
285
  ctx.backendDetected ? "- For backend work, read `.prismo/backend-summary.md` next." : null,
@@ -234,6 +320,10 @@ function renderArchitectureSummary(ctx) {
234
320
  "",
235
321
  ctx.warnings.length ? ctx.warnings.map((warning) => `- ${warning}`).join("\n") : "- No major local context risks detected.",
236
322
  "",
323
+ "## Detection Gaps",
324
+ "",
325
+ gaps.length ? gaps.map((gap) => `- ${gap}`).join("\n") : "- No major architecture-detection gaps surfaced.",
326
+ "",
237
327
  "## AI Workflow Notes",
238
328
  "",
239
329
  "- Prefer this summary before broad repo reads.",
@@ -244,6 +334,7 @@ function renderArchitectureSummary(ctx) {
244
334
  }
245
335
 
246
336
  function renderBackendSummary(ctx) {
337
+ const backendCandidates = topBySize(ctx.scan.files.filter((file) => file.path.endsWith(".py") && !isNonSourcePath(file.path)), 8);
247
338
  return [
248
339
  "# Backend Summary",
249
340
  "",
@@ -273,10 +364,21 @@ function renderBackendSummary(ctx) {
273
364
  "",
274
365
  mdList(ctx.backend.config),
275
366
  "",
367
+ "## Load-Bearing Candidates",
368
+ "",
369
+ mdList(backendCandidates, "No Python source candidates detected."),
370
+ "",
371
+ "## Detection Notes",
372
+ "",
373
+ ctx.backend.api.length || ctx.backend.services.length
374
+ ? "- Backend paths were inferred from conventional directories plus root-level Python/FastAPI/service filename signals."
375
+ : "- No conventional backend paths were detected; inspect root-level Python modules and custom service naming manually.",
376
+ "",
276
377
  ].join("\n");
277
378
  }
278
379
 
279
380
  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);
280
382
  return [
281
383
  "# Frontend Summary",
282
384
  "",
@@ -302,6 +404,10 @@ function renderFrontendSummary(ctx) {
302
404
  "",
303
405
  mdList(ctx.frontend.styling),
304
406
  "",
407
+ "## Load-Bearing Candidates",
408
+ "",
409
+ mdList(frontendCandidates, "No frontend source candidates detected."),
410
+ "",
305
411
  ].join("\n");
306
412
  }
307
413
 
@@ -319,7 +425,9 @@ function renderRecommendedClaude(ctx) {
319
425
  if (hasPath(ctx.scan, (rel) => rel === "frontend/package.json")) commands.push("cd frontend && npm run test");
320
426
  if (hasPath(ctx.scan, (rel) => rel === "backend/pytest.ini" || rel.startsWith("backend/tests/"))) commands.push("cd backend && pytest");
321
427
  return [
322
- "# CLAUDE.md",
428
+ "# CLAUDE.md Boilerplate",
429
+ "",
430
+ "Do not overwrite an existing curated CLAUDE.md with this file. Use it as a diff/reference for missing compact-context guidance only.",
323
431
  "",
324
432
  "Keep context small. Start with `.prismo/architecture-summary.md`; use scoped `.prismo/*-summary.md` files only when relevant.",
325
433
  "",
@@ -351,7 +459,9 @@ function renderRecommendedClaude(ctx) {
351
459
 
352
460
  function renderRecommendedAgents(ctx) {
353
461
  return [
354
- "# AGENTS.md",
462
+ "# AGENTS.md Boilerplate",
463
+ "",
464
+ "Do not overwrite an existing curated AGENTS.md with this file. Use it as a diff/reference for missing compact-context guidance only.",
355
465
  "",
356
466
  "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
467
  "",
@@ -413,7 +523,7 @@ function renderOptimizeReport(ctx, generatedFiles) {
413
523
  "",
414
524
  "## Token-Heavy Directories",
415
525
  "",
416
- mdList(ctx.scan.highRiskDirs.map((dir) => `${dir.path}/${dir.exposed ? " (exposed)" : " (ignored)"}`)),
526
+ mdList(summarizeRiskyDirectories(ctx.scan.highRiskDirs)),
417
527
  "",
418
528
  "## Optimization Suggestions",
419
529
  "",
@@ -530,8 +640,8 @@ function renderContextCommand(ctx, scope = null) {
530
640
  function getOptimizePendingFiles(ctx) {
531
641
  const pending = [
532
642
  ["architecture-summary.md", renderArchitectureSummary(ctx)],
533
- ["recommended-CLAUDE.md", renderRecommendedClaude(ctx)],
534
- ["recommended-AGENTS.md", renderRecommendedAgents(ctx)],
643
+ ["recommended-CLAUDE.boilerplate.md", renderRecommendedClaude(ctx)],
644
+ ["recommended-AGENTS.boilerplate.md", renderRecommendedAgents(ctx)],
535
645
  ["recommended-.claudeignore", `${ctx.scan.recommendedClaudeIgnore.join("\n")}\n`],
536
646
  ["recommended-.cursorignore", `${ctx.scan.recommendedCursorIgnore.join("\n")}\n`],
537
647
  ["recommended-.gitignore-additions", renderGitignoreAdditions(ctx)],
@@ -77,8 +77,8 @@ function applyFixes(result, options = {}) {
77
77
  }
78
78
  if (ignoresOnly) return actions;
79
79
 
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"} ${path.basename(report.reportPath)}`);
80
+ const report = options.dryRun ? { reportPath: path.join(result.root, ".prismo", "prismo-dev-report.md"), backupPath: null } : writeReport(result);
81
+ actions.push(`${options.dryRun ? "Would generate" : "Generated"} .prismo/${path.basename(report.reportPath)}`);
82
82
  if (report.backupPath) actions.push(`Backed up existing report to ${path.basename(report.backupPath)}`);
83
83
 
84
84
  const claudeFile = result.instructionFiles.find((file) => file.isClaude);
@@ -92,10 +92,12 @@ function applyFixes(result, options = {}) {
92
92
 
93
93
  const hasCodexRisk = result.issues.some((issue) => issue.category === "codex_config");
94
94
  if (hasCodexRisk || result.instructionFiles.some((file) => file.path === "AGENTS.md" || file.path.startsWith(".codex/"))) {
95
- const codexPath = path.join(result.root, "prismo-AGENTS-recommendations.md");
95
+ const prismoDir = path.join(result.root, ".prismo");
96
+ if (!options.dryRun) fs.mkdirSync(prismoDir, { recursive: true });
97
+ const codexPath = path.join(prismoDir, "prismo-AGENTS-recommendations.md");
96
98
  const backupPath = options.dryRun ? null : backupIfExists(codexPath);
97
99
  if (!options.dryRun) fs.writeFileSync(codexPath, renderAgentsRecommendations(result), "utf8");
98
- actions.push(`${options.dryRun ? "Would generate" : "Generated"} prismo-AGENTS-recommendations.md`);
100
+ actions.push(`${options.dryRun ? "Would generate" : "Generated"} .prismo/prismo-AGENTS-recommendations.md`);
99
101
  if (backupPath) actions.push(`Backed up existing AGENTS recommendations to ${path.basename(backupPath)}`);
100
102
  }
101
103
  return actions;
@@ -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
 
@@ -342,7 +342,9 @@ function backupIfExists(filePath) {
342
342
  }
343
343
 
344
344
  function writeReport(result) {
345
- const reportPath = path.join(result.root, "prismo-dev-report.md");
345
+ const prismoDir = path.join(result.root, ".prismo");
346
+ fs.mkdirSync(prismoDir, { recursive: true });
347
+ const reportPath = path.join(prismoDir, "prismo-dev-report.md");
346
348
  const backupPath = backupIfExists(reportPath);
347
349
  fs.writeFileSync(reportPath, renderMarkdownReport(result), "utf8");
348
350
  return { reportPath, backupPath };
@@ -1060,9 +1060,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
1060
1060
  const recommendedCursorIgnore = Array.from(new Set([
1061
1061
  ...recommendedClaudeIgnore,
1062
1062
  ".prismo/",
1063
- "prismo-dev-report.md",
1064
1063
  "prismo-optimized-CLAUDE.template.md",
1065
- "prismo-AGENTS-recommendations.md",
1066
1064
  ]));
1067
1065
  const recommendations = buildRecommendations({
1068
1066
  hasClaudeIgnore,
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",