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.
@@ -0,0 +1,109 @@
1
+ module.exports = function createFixes(deps) {
2
+ const { fs, path, backupIfExists, writeReport } = deps;
3
+
4
+ function renderClaudeTemplate(result) {
5
+ const claudeFile = result.instructionFiles.find((file) => file.isClaude);
6
+ return [
7
+ "# Prismo Optimized CLAUDE.md Template",
8
+ "",
9
+ "Use this as a concise replacement draft. Review manually before changing your real CLAUDE.md.",
10
+ "",
11
+ "## Project Rules",
12
+ "",
13
+ "- Keep changes scoped to the requested task.",
14
+ "- Prefer existing project patterns and tests.",
15
+ "- Do not load generated files, logs, coverage reports, or build artifacts unless explicitly needed.",
16
+ "- Reference long docs only when the task requires them.",
17
+ "",
18
+ "## Token Hygiene",
19
+ "",
20
+ "- Keep persistent instructions under roughly 500 tokens.",
21
+ "- Move long implementation notes into separate docs.",
22
+ "- Start a fresh session for unrelated work.",
23
+ "",
24
+ claudeFile ? `Original CLAUDE.md estimate: ~${claudeFile.tokens.toLocaleString()} tokens.` : "",
25
+ "",
26
+ ].join("\n");
27
+ }
28
+
29
+ function renderAgentsRecommendations(result) {
30
+ const codexIssues = result.issues.filter((issue) => issue.category === "codex_config" || issue.title.includes("AGENTS.md"));
31
+ return [
32
+ "# Prismo AGENTS.md / Codex Recommendations",
33
+ "",
34
+ "Review these suggestions manually before changing AGENTS.md or .codex configuration.",
35
+ "",
36
+ "## Findings",
37
+ "",
38
+ ...(codexIssues.length
39
+ ? codexIssues.map((issue) => `- ${issue.title}: ${issue.recommendation}`)
40
+ : ["- No major Codex-specific risks detected, but keeping persistent instructions concise is still recommended."]),
41
+ "",
42
+ "## Suggested Practices",
43
+ "",
44
+ "- Keep AGENTS.md focused on durable project rules.",
45
+ "- Move task-specific context into separate docs.",
46
+ "- Avoid pasting giant logs, generated JSON, lockfiles, and coverage reports into Codex sessions.",
47
+ "- Scope MCP/tool configuration to the current project.",
48
+ "",
49
+ ].join("\n");
50
+ }
51
+
52
+ function applyFixes(result, options = {}) {
53
+ const actions = [];
54
+ const dryRunPrefix = options.dryRun ? "Would create" : "Created";
55
+ const ignoresOnly = Boolean(options.ignoresOnly);
56
+ const claudeIgnorePath = path.join(result.root, ".claudeignore");
57
+ const suggestedClaudeIgnorePath = path.join(result.root, ".claudeignore.prismo-suggested");
58
+ const cursorIgnorePath = path.join(result.root, ".cursorignore");
59
+ const suggestedCursorIgnorePath = path.join(result.root, ".cursorignore.prismo-suggested");
60
+ if (!result.hasClaudeIgnore) {
61
+ if (!options.dryRun) fs.writeFileSync(claudeIgnorePath, `${result.recommendedClaudeIgnore.join("\n")}\n`, "utf8");
62
+ actions.push(`${dryRunPrefix} .claudeignore`);
63
+ } else {
64
+ const backupPath = options.dryRun ? null : backupIfExists(suggestedClaudeIgnorePath);
65
+ if (!options.dryRun) fs.writeFileSync(suggestedClaudeIgnorePath, `${result.recommendedClaudeIgnore.join("\n")}\n`, "utf8");
66
+ actions.push(`${dryRunPrefix} .claudeignore.prismo-suggested because .claudeignore already exists`);
67
+ if (backupPath) actions.push(`Backed up existing .claudeignore.prismo-suggested to ${path.basename(backupPath)}`);
68
+ }
69
+ if (!result.hasCursorIgnore) {
70
+ if (!options.dryRun) fs.writeFileSync(cursorIgnorePath, `${result.recommendedCursorIgnore.join("\n")}\n`, "utf8");
71
+ actions.push(`${dryRunPrefix} .cursorignore`);
72
+ } else {
73
+ const backupPath = options.dryRun ? null : backupIfExists(suggestedCursorIgnorePath);
74
+ if (!options.dryRun) fs.writeFileSync(suggestedCursorIgnorePath, `${result.recommendedCursorIgnore.join("\n")}\n`, "utf8");
75
+ actions.push(`${dryRunPrefix} .cursorignore.prismo-suggested because .cursorignore already exists`);
76
+ if (backupPath) actions.push(`Backed up existing .cursorignore.prismo-suggested to ${path.basename(backupPath)}`);
77
+ }
78
+ if (ignoresOnly) return actions;
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)}`);
82
+ if (report.backupPath) actions.push(`Backed up existing report to ${path.basename(report.backupPath)}`);
83
+
84
+ const claudeFile = result.instructionFiles.find((file) => file.isClaude);
85
+ if (claudeFile && claudeFile.tokens > 500) {
86
+ const templatePath = path.join(result.root, "prismo-optimized-CLAUDE.template.md");
87
+ const backupPath = options.dryRun ? null : backupIfExists(templatePath);
88
+ if (!options.dryRun) fs.writeFileSync(templatePath, renderClaudeTemplate(result), "utf8");
89
+ actions.push(`${options.dryRun ? "Would generate" : "Generated"} prismo-optimized-CLAUDE.template.md`);
90
+ if (backupPath) actions.push(`Backed up existing CLAUDE template to ${path.basename(backupPath)}`);
91
+ }
92
+
93
+ const hasCodexRisk = result.issues.some((issue) => issue.category === "codex_config");
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");
96
+ const backupPath = options.dryRun ? null : backupIfExists(codexPath);
97
+ if (!options.dryRun) fs.writeFileSync(codexPath, renderAgentsRecommendations(result), "utf8");
98
+ actions.push(`${options.dryRun ? "Would generate" : "Generated"} prismo-AGENTS-recommendations.md`);
99
+ if (backupPath) actions.push(`Backed up existing AGENTS recommendations to ${path.basename(backupPath)}`);
100
+ }
101
+ return actions;
102
+ }
103
+
104
+ return {
105
+ applyFixes,
106
+ renderAgentsRecommendations,
107
+ renderClaudeTemplate,
108
+ };
109
+ };
@@ -0,0 +1,360 @@
1
+ module.exports = function createReport(deps) {
2
+ const {
3
+ fs,
4
+ path,
5
+ NPX_COMMAND,
6
+ color,
7
+ severityIcon,
8
+ severityColor,
9
+ estimateTokens,
10
+ formatBytes,
11
+ formatTokenCount,
12
+ getNextCommands,
13
+ } = deps;
14
+
15
+ function renderTerminalReport(result, options = {}) {
16
+ const reportEnabled = options.reportEnabled !== false;
17
+ const useColor = options.color !== false;
18
+ const riskTone = result.risk === "High" ? "red" : result.risk === "Medium" ? "yellow" : "green";
19
+ const lines = [];
20
+ lines.push("");
21
+ lines.push(color("PrismoDev", "bold", useColor));
22
+ lines.push("");
23
+ lines.push(`Score: ${color(`${result.score}/100`, riskTone, useColor)} | Risk: ${color(result.risk, riskTone, useColor)} | Token leaks: ${result.issues.length}`);
24
+ lines.push(`Estimated avoidable waste: ${result.avoidableWaste}`);
25
+ lines.push("");
26
+ lines.push(color("Top Token Leaks", "bold", useColor));
27
+ if (!result.topTokenLeaks.length) {
28
+ lines.push("1. [ok] No major token leaks detected");
29
+ } else {
30
+ result.topTokenLeaks.forEach((leak, index) => lines.push(`${index + 1}. ${leak}`));
31
+ }
32
+ lines.push("");
33
+ const nextCommands = getNextCommands(result, options.scope);
34
+ lines.push(color("Top Fix", "bold", useColor));
35
+ lines.push(`Run: ${nextCommands[0]}`);
36
+ if (nextCommands[1]) lines.push(`Then: ${nextCommands[1]}`);
37
+ if (nextCommands[2]) lines.push(`Then: ${nextCommands[2]}`);
38
+ lines.push("");
39
+ lines.push(color("Scan Context", "bold", useColor));
40
+ lines.push(`- Files scanned: ${result.stats.totalFiles.toLocaleString()}`);
41
+ lines.push(`- Source files: ${result.stats.sourceFiles.toLocaleString()}`);
42
+ lines.push(`- Large files: ${result.stats.largeFiles.toLocaleString()} (${result.stats.exposedLargeFiles} exposed)`);
43
+ lines.push(`- Risky directories: ${result.stats.highRiskDirs} (${result.stats.exposedHighRiskDirs} exposed)`);
44
+ lines.push(`- Repo detected: ${result.repoDetected ? "yes" : "no"}`);
45
+ if (result.realUsage && result.realUsage.sessions.length) {
46
+ lines.push(`- Real local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
47
+ lines.push(`- Usage confidence: ${result.realUsage.confidence}`);
48
+ } else if (result.realUsage) {
49
+ lines.push("- Real local usage: no matching local Codex/Claude Code sessions found for this repo");
50
+ }
51
+ lines.push("");
52
+ lines.push(color("Coding Agent Readiness", "bold", useColor));
53
+ lines.push(`- Claude Code: ${result.agentReadiness.claudeCode.detected ? "detected" : "not detected"}; logs: ${result.agentReadiness.claudeCode.localLogsFound ? "found" : "not found"}; MCP: ${result.agentReadiness.claudeCode.mcpServers}; hooks: ${result.agentReadiness.claudeCode.hooks}`);
54
+ lines.push(`- Codex: ${result.agentReadiness.codex.detected ? "detected" : "not detected"}; logs: ${result.agentReadiness.codex.localLogsFound ? "found" : "not found"}; MCP: ${result.agentReadiness.codex.mcpServers}`);
55
+ lines.push(`- Cursor: ${result.agentReadiness.cursor.detected ? "detected" : "not detected"}`);
56
+ lines.push("");
57
+ lines.push(color("Optimization Stack", "bold", useColor));
58
+ const stack = result.optimizationStack;
59
+ lines.push(`- RTK: ${stack.tools.rtk.detected ? "detected" : "not detected"}`);
60
+ lines.push(`- Headroom: ${stack.tools.headroom.detected ? "detected" : "not detected"}`);
61
+ lines.push(`- Distill: ${stack.tools.distill.detected ? "detected" : "not detected"}`);
62
+ lines.push(`- Mana: ${stack.tools.mana.detected ? "detected" : "not detected"}`);
63
+ lines.push(`- Claude hooks: ${stack.claudeHooks}; MCP servers: ${stack.mcpServerTotal}`);
64
+ lines.push("");
65
+ lines.push(color("Tool Output Risk", "bold", useColor));
66
+ lines.push(`- Level: ${result.toolOutputRisk.level}`);
67
+ lines.push(`- ${result.toolOutputRisk.summary}`);
68
+ if (result.toolOutputRisk.exposedNoisyDirectories.length) lines.push(`- Exposed noisy dirs: ${result.toolOutputRisk.exposedNoisyDirectories.slice(0, 6).join(", ")}`);
69
+ if (result.toolOutputRisk.exposedNoisyFiles.length) lines.push(`- Exposed noisy files: ${result.toolOutputRisk.exposedNoisyFiles.slice(0, 4).map((file) => file.path).join(", ")}`);
70
+ lines.push("");
71
+ lines.push(color("Prismo Proxy Tracking", "bold", useColor));
72
+ lines.push("- Exact API tracking: available when traffic uses the Prismo OpenAI/Anthropic base URL");
73
+ lines.push(`- Codex API/base-url mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.codex}`);
74
+ lines.push(`- Claude Code subscription mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.claudeCode}`);
75
+ lines.push("- Subscription sessions: local-log visibility when available, not guaranteed billing accuracy");
76
+ lines.push("");
77
+ lines.push(color("Issues", "bold", useColor));
78
+ if (!result.issues.length) {
79
+ lines.push("- [ok] No major token-waste risks detected.");
80
+ } else {
81
+ for (const issue of result.issues.slice(0, 8)) {
82
+ const icon = color(severityIcon(issue.severity), severityColor(issue.severity), useColor);
83
+ lines.push(`- ${icon} ${issue.title}. ${issue.description}`);
84
+ if (issue.estimatedTokenImpact) lines.push(` ${issue.estimatedTokenImpact}`);
85
+ }
86
+ }
87
+ lines.push("");
88
+ lines.push(color("Recommended Fixes", "bold", useColor));
89
+ result.recommendations.forEach((rec, index) => lines.push(`${index + 1}. ${rec}`));
90
+ lines.push("");
91
+ lines.push(result.realUsage && result.realUsage.sessions.length
92
+ ? "Usage findings come from local Codex/Claude Code logs when exact token fields are available; repo-risk estimates remain heuristic."
93
+ : result.realUsage
94
+ ? "No matching local usage sessions were found; repo-risk estimates remain heuristic."
95
+ : "Potential savings estimates are heuristic and local-only, not provider billing data.");
96
+ lines.push("");
97
+ lines.push(reportEnabled ? "Report: prismo-dev-report.md" : "Report: skipped (--no-report)");
98
+ return lines.join("\n");
99
+ }
100
+
101
+ function evaluateCi(result, options = {}) {
102
+ const minScore = Number(options.minScore || 80);
103
+ const failures = [];
104
+ if (result.score < minScore) failures.push(`Score ${result.score}/100 is below CI minimum ${minScore}/100.`);
105
+ if (result.risk === "High") failures.push("Token risk is High.");
106
+ if (!result.hasClaudeIgnore) failures.push(".claudeignore is missing.");
107
+ if (!result.hasCursorIgnore) failures.push(".cursorignore is missing.");
108
+ if (result.exposedHighRiskDirs.length) failures.push(`${result.exposedHighRiskDirs.length} generated/cache director${result.exposedHighRiskDirs.length === 1 ? "y is" : "ies are"} exposed.`);
109
+ if (result.exposedLargeFiles.length) failures.push(`${result.exposedLargeFiles.length} large file${result.exposedLargeFiles.length === 1 ? " is" : "s are"} exposed.`);
110
+ return {
111
+ passed: failures.length === 0,
112
+ minScore,
113
+ failures,
114
+ };
115
+ }
116
+
117
+ function renderCiReport(result, ci) {
118
+ const lines = [];
119
+ lines.push("");
120
+ lines.push("Prismo CI");
121
+ lines.push("");
122
+ lines.push(`Status: ${ci.passed ? "PASS" : "FAIL"}`);
123
+ lines.push(`Score: ${result.score}/100`);
124
+ lines.push(`Risk: ${result.risk}`);
125
+ lines.push("");
126
+ if (ci.failures.length) {
127
+ lines.push("Failures:");
128
+ ci.failures.forEach((failure) => lines.push(`- ${failure}`));
129
+ } else {
130
+ lines.push("No CI token-risk failures detected.");
131
+ }
132
+ lines.push("");
133
+ lines.push(`Next: ${NPX_COMMAND} doctor`);
134
+ return lines.join("\n");
135
+ }
136
+
137
+ function renderSimpleScanReport(result) {
138
+ const lines = [];
139
+ const topLeaks = result.topTokenLeaks.length ? result.topTokenLeaks.slice(0, 4) : ["No major token-waste risks detected."];
140
+ const nextCommands = getNextCommands(result);
141
+ lines.push("");
142
+ lines.push("PrismoDev Simple Scan");
143
+ lines.push("");
144
+ lines.push(`Result: ${result.risk} token-waste risk (${result.score}/100)`);
145
+ lines.push(`What it means: Prismo found ${result.issues.length} thing${result.issues.length === 1 ? "" : "s"} that could make Claude Code, Codex, or Cursor use extra context.`);
146
+ if (result.realUsage && result.realUsage.sessions.length) {
147
+ lines.push(`Recent local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens from local coding-agent logs.`);
148
+ } else if (result.realUsage) {
149
+ lines.push("Recent local usage: no matching Codex or Claude Code session logs found for this repo.");
150
+ }
151
+ lines.push("");
152
+ lines.push("Main things to fix:");
153
+ topLeaks.forEach((leak, index) => lines.push(`${index + 1}. ${leak}`));
154
+ lines.push("");
155
+ lines.push("Best next step:");
156
+ lines.push(`${nextCommands[0]}`);
157
+ if (nextCommands[1]) lines.push(`After that: ${nextCommands[1]}`);
158
+ lines.push("");
159
+ lines.push("Plain English:");
160
+ lines.push("PrismoDev checks your project locally. It does not need API keys, does not connect to OpenAI or Anthropic, and does not change your code unless you run --fix.");
161
+ lines.push("");
162
+ return lines.join("\n");
163
+ }
164
+
165
+ function renderMarkdownReport(result) {
166
+ const lines = [];
167
+ lines.push("# PrismoDev Report");
168
+ lines.push("");
169
+ lines.push("## Executive Summary");
170
+ lines.push("");
171
+ lines.push(`- **Score:** ${result.score}/100`);
172
+ lines.push(`- **Risk Level:** ${result.risk}`);
173
+ lines.push(`- **Token Leaks Found:** ${result.issues.length}`);
174
+ lines.push(`- **Estimated Avoidable Waste:** ${result.avoidableWaste}`);
175
+ lines.push(`- **Repo:** \`${result.root}\``);
176
+ lines.push(`- **Generated At:** ${result.generatedAt}`);
177
+ if (result.realUsage) {
178
+ lines.push(`- **Real Local Usage:** ${result.realUsage.totals.displayTokens.toLocaleString()} tokens across ${result.realUsage.sessions.length} session(s)`);
179
+ lines.push(`- **Usage Confidence:** ${result.realUsage.confidence}`);
180
+ }
181
+ lines.push("");
182
+ lines.push("Estimates are based on local file-size and configuration heuristics. They are not provider billing data and are not guaranteed savings.");
183
+ lines.push("");
184
+ lines.push("## Top Token Leaks");
185
+ lines.push("");
186
+ if (!result.topTokenLeaks.length) {
187
+ lines.push("1. No major token leaks detected.");
188
+ } else {
189
+ result.topTokenLeaks.forEach((leak, index) => lines.push(`${index + 1}. ${leak}`));
190
+ }
191
+ lines.push("");
192
+ lines.push("## Repo Context");
193
+ lines.push("");
194
+ lines.push(`- Total files scanned: ${result.stats.totalFiles}`);
195
+ lines.push(`- Source files: ${result.stats.sourceFiles}`);
196
+ lines.push(`- Large files: ${result.stats.largeFiles}`);
197
+ lines.push(`- Exposed large files: ${result.stats.exposedLargeFiles}`);
198
+ lines.push(`- Token-bloat directories: ${result.stats.highRiskDirs}`);
199
+ lines.push(`- Exposed token-bloat directories: ${result.stats.exposedHighRiskDirs}`);
200
+ lines.push("");
201
+ lines.push("## Coding Agent Readiness");
202
+ lines.push("");
203
+ lines.push(`- Claude Code detected: ${result.agentReadiness.claudeCode.detected ? "yes" : "no"}`);
204
+ lines.push(` - Local logs found: ${result.agentReadiness.claudeCode.localLogsFound ? "yes" : "no"}`);
205
+ lines.push(` - MCP servers: ${result.agentReadiness.claudeCode.mcpServers}; hooks: ${result.agentReadiness.claudeCode.hooks}`);
206
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.claudeCode.exactProxyTracking}`);
207
+ lines.push(`- Codex detected: ${result.agentReadiness.codex.detected ? "yes" : "no"}`);
208
+ lines.push(` - Local logs found: ${result.agentReadiness.codex.localLogsFound ? "yes" : "no"}`);
209
+ lines.push(` - MCP servers: ${result.agentReadiness.codex.mcpServers}`);
210
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.codex.exactProxyTracking}`);
211
+ lines.push(`- Cursor detected: ${result.agentReadiness.cursor.detected ? "yes" : "no"}`);
212
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.cursor.exactProxyTracking}`);
213
+ lines.push("");
214
+ lines.push("## Optimization Stack");
215
+ lines.push("");
216
+ lines.push(`- RTK: ${result.optimizationStack.tools.rtk.detected ? "detected" : "not detected"}`);
217
+ lines.push(`- Headroom: ${result.optimizationStack.tools.headroom.detected ? "detected" : "not detected"}`);
218
+ lines.push(`- Distill: ${result.optimizationStack.tools.distill.detected ? "detected" : "not detected"}`);
219
+ lines.push(`- Mana: ${result.optimizationStack.tools.mana.detected ? "detected" : "not detected"}`);
220
+ lines.push(`- Claude hooks: ${result.optimizationStack.claudeHooks}`);
221
+ lines.push(`- Total MCP/tool servers: ${result.optimizationStack.mcpServerTotal}`);
222
+ lines.push("");
223
+ lines.push("## Tool Output Risk");
224
+ lines.push("");
225
+ lines.push(`- Level: ${result.toolOutputRisk.level}`);
226
+ lines.push(`- ${result.toolOutputRisk.summary}`);
227
+ if (result.toolOutputRisk.exposedNoisyDirectories.length) {
228
+ lines.push(`- Exposed noisy directories: ${result.toolOutputRisk.exposedNoisyDirectories.map((dir) => `\`${dir}/\``).join(", ")}`);
229
+ }
230
+ if (result.toolOutputRisk.exposedNoisyFiles.length) {
231
+ result.toolOutputRisk.exposedNoisyFiles.slice(0, 20).forEach((file) => {
232
+ lines.push(`- \`${file.path}\` - ${formatBytes(file.sizeBytes)} - ~${file.estimatedTokensIfRead.toLocaleString()} tokens if read`);
233
+ });
234
+ }
235
+ lines.push("");
236
+ lines.push("## Prismo Proxy Tracking Readiness");
237
+ lines.push("");
238
+ lines.push("- Exact API tracking: available when app/tool traffic uses the Prismo OpenAI/Anthropic base URL.");
239
+ lines.push(`- Codex API/base-url mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.codex}.`);
240
+ lines.push(`- Claude Code subscription mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.claudeCode}.`);
241
+ lines.push("- Local estimate tracking: available from local Codex/Claude Code logs when those logs exist.");
242
+ lines.push("- Unsupported: exact billing for hidden subscription sessions without provider traffic, API keys, or local token fields.");
243
+ lines.push("");
244
+ if (result.realUsage) {
245
+ lines.push("## Real Local Usage");
246
+ lines.push("");
247
+ lines.push(`- Tool scope: ${result.realUsage.tool}`);
248
+ lines.push(`- Displayed tokens: ${result.realUsage.totals.displayTokens.toLocaleString()}`);
249
+ lines.push(`- Exact local-log tokens: ${result.realUsage.totals.exactTokens.toLocaleString()}`);
250
+ lines.push(`- Estimated tool/output tokens: ${result.realUsage.totals.toolTokens.toLocaleString()}`);
251
+ lines.push(`- Confidence: ${result.realUsage.confidence}`);
252
+ lines.push("");
253
+ result.realUsage.sessions.slice(0, 5).forEach((session, index) => {
254
+ lines.push(`${index + 1}. ${session.tool} - ${session.title || session.sessionId}`);
255
+ lines.push(` - Tokens: ${session.displayTokens.toLocaleString()} (${session.confidence})`);
256
+ lines.push(` - Risk: ${session.contextRisk}; turns: ${session.turns}; tools: ${session.toolCalls}`);
257
+ if (session.cwd) lines.push(` - CWD: \`${session.cwd}\``);
258
+ });
259
+ lines.push("");
260
+ }
261
+ lines.push("## Issues");
262
+ lines.push("");
263
+ if (!result.issues.length) {
264
+ lines.push("- No major token-waste risks detected.");
265
+ } else {
266
+ for (const issue of result.issues) {
267
+ lines.push(`- **${issue.severity.toUpperCase()}** / \`${issue.category}\`: ${issue.title}`);
268
+ lines.push(` - ${issue.description}`);
269
+ lines.push(` - Fix: ${issue.recommendation}`);
270
+ if (issue.estimatedTokenImpact) lines.push(` - ${issue.estimatedTokenImpact}`);
271
+ }
272
+ }
273
+ lines.push("");
274
+ lines.push("## Claude Code Findings");
275
+ lines.push("");
276
+ lines.push(`- CLAUDE.md found: ${result.instructionFiles.some((file) => file.isClaude) ? "yes" : "no"}`);
277
+ lines.push(`- .claudeignore found: ${result.hasClaudeIgnore ? "yes" : "no"}`);
278
+ lines.push(`- Claude config files found: ${result.claudeConfig.files.length ? result.claudeConfig.files.map((file) => `\`${file}\``).join(", ") : "none"}.`);
279
+ lines.push(`- MCP servers detected: ${result.claudeConfig.mcpServers}. Hooks detected: ${result.claudeConfig.hooks}. Plugin/skill references: ${result.claudeConfig.pluginRefs}.`);
280
+ lines.push("- Keep `CLAUDE.md` under 500 tokens when possible.");
281
+ lines.push("- Move long implementation notes into separate docs and reference them only when needed.");
282
+ lines.push("- Create `.claudeignore` to hide generated files, caches, logs, coverage, and lock files.");
283
+ lines.push("");
284
+ lines.push("## OpenAI/Codex Findings");
285
+ lines.push("");
286
+ lines.push(`- AGENTS.md found: ${result.instructionFiles.some((file) => file.path === "AGENTS.md") ? "yes" : "no"}`);
287
+ lines.push(`- Codex config files found: ${result.codexConfig.files.length ? result.codexConfig.files.map((file) => `\`${file}\``).join(", ") : "none"}.`);
288
+ lines.push(`- Codex MCP/tool references detected: ${result.codexConfig.mcpServers}.`);
289
+ lines.push("- Keep `AGENTS.md` and `.codex/` instructions concise and task-oriented.");
290
+ lines.push("- Avoid pasting giant logs or generated JSON into Codex sessions.");
291
+ lines.push("- Use cheaper/faster models for mechanical edits and low-risk refactors.");
292
+ lines.push("");
293
+ lines.push("## Large Files");
294
+ lines.push("");
295
+ if (!result.largeFiles.length) {
296
+ lines.push("- No large text-like files over 500 KB detected.");
297
+ } else {
298
+ result.largeFiles.slice(0, 40).forEach((file) => {
299
+ lines.push(`- \`${file.path}\` - ${formatBytes(file.size)} - ${file.kind}${file.ignored ? " - ignored" : ""}`);
300
+ });
301
+ }
302
+ lines.push("");
303
+ lines.push("## Risky Directories");
304
+ lines.push("");
305
+ if (!result.highRiskDirs.length) {
306
+ lines.push("- No common token-bloat directories detected.");
307
+ } else {
308
+ result.highRiskDirs.forEach((dir) => {
309
+ lines.push(`- \`${dir.path}/\`${dir.exposed ? " - exposed" : " - ignored"}`);
310
+ });
311
+ }
312
+ lines.push("");
313
+ lines.push("## Recommended .claudeignore");
314
+ lines.push("");
315
+ lines.push("```gitignore");
316
+ result.recommendedClaudeIgnore.forEach((line) => lines.push(line));
317
+ lines.push("```");
318
+ lines.push("");
319
+ lines.push("## Recommended .cursorignore");
320
+ lines.push("");
321
+ lines.push("```gitignore");
322
+ result.recommendedCursorIgnore.forEach((line) => lines.push(line));
323
+ lines.push("```");
324
+ lines.push("");
325
+ lines.push("## Recommended Next Steps");
326
+ lines.push("");
327
+ result.recommendations.forEach((rec, index) => lines.push(`${index + 1}. ${rec}`));
328
+ lines.push("");
329
+ lines.push("## Disclaimer");
330
+ lines.push("");
331
+ lines.push("PrismoDev is a fast local scanner. It does not connect to Anthropic, OpenAI, Claude Code, Codex, Cursor, or billing accounts. Token and savings estimates are heuristic and should be treated as directional diagnostics only.");
332
+ lines.push("");
333
+ return lines.join("\n");
334
+ }
335
+
336
+ function backupIfExists(filePath) {
337
+ if (!fs.existsSync(filePath)) return null;
338
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
339
+ const backupPath = `${filePath}.${stamp}.bak`;
340
+ fs.copyFileSync(filePath, backupPath);
341
+ return backupPath;
342
+ }
343
+
344
+ function writeReport(result) {
345
+ const reportPath = path.join(result.root, "prismo-dev-report.md");
346
+ const backupPath = backupIfExists(reportPath);
347
+ fs.writeFileSync(reportPath, renderMarkdownReport(result), "utf8");
348
+ return { reportPath, backupPath };
349
+ }
350
+
351
+ return {
352
+ backupIfExists,
353
+ evaluateCi,
354
+ renderCiReport,
355
+ renderMarkdownReport,
356
+ renderSimpleScanReport,
357
+ renderTerminalReport,
358
+ writeReport,
359
+ };
360
+ };