getprismo 0.1.20 → 0.1.22
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 +66 -0
- package/lib/prismo-dev/benchmark.js +122 -0
- package/lib/prismo-dev/report.js +79 -0
- package/lib/prismo-dev/scan.js +260 -1
- package/lib/prismo-dev-scan.js +78 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,69 @@ watch caught lockfiles entering context, a file being read 286 times, and tool o
|
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
149
|
+
## new: optimizer fit
|
|
150
|
+
|
|
151
|
+
not every token optimizer solves the same bottleneck. before stacking compression proxies, repo packers, code indexes, and MCP tools, run:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx getprismo scan --optimizer-fit
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
PrismoDev scores your actual repo/session signals and recommends the right path:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
Prismo Optimizer Fit
|
|
161
|
+
|
|
162
|
+
Primary bottleneck: Generated artifacts / ignore cleanup: HIGH
|
|
163
|
+
|
|
164
|
+
Bottlenecks
|
|
165
|
+
- Generated artifacts / ignore cleanup: High
|
|
166
|
+
.claudeignore is missing
|
|
167
|
+
- Oversized command/tool output: Medium
|
|
168
|
+
237k tool/output tokens found in local sessions
|
|
169
|
+
- Repeated source exploration: Low
|
|
170
|
+
Repo/source exploration does not look like the main bottleneck
|
|
171
|
+
|
|
172
|
+
Recommended Stack
|
|
173
|
+
1. Apply safe ignore/context fixes first.
|
|
174
|
+
Run: npx getprismo doctor --apply-suggestions --dry-run
|
|
175
|
+
Category: ignore cleanup (.claudeignore, .cursorignore)
|
|
176
|
+
2. Sandbox noisy command output before adding more code-indexing tools.
|
|
177
|
+
Run: npx getprismo shield -- <noisy command>
|
|
178
|
+
Category: output sandboxing (Prismo shield, context-mode, RTK, tokf, distill)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This makes PrismoDev the measure-first layer: it tells you whether you need ignore cleanup, output sandboxing, code indexing, repo packing, instruction trimming, session splitting, or MCP/tool hygiene.
|
|
182
|
+
|
|
183
|
+
For the short version:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npx getprismo scan --report-card
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
That prints the simplest decision:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
PrismoDev Report Card
|
|
193
|
+
|
|
194
|
+
Biggest waste: Generated artifacts / ignore cleanup: High
|
|
195
|
+
Start with: npx getprismo doctor --apply-suggestions --dry-run
|
|
196
|
+
Then: npx getprismo shield -- <noisy command>
|
|
197
|
+
Code index needed: not yet
|
|
198
|
+
Round-trip risk: Low
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To benchmark a noisy command:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx getprismo benchmark -- npm test
|
|
205
|
+
npx getprismo benchmark session
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`benchmark -- <command>` measures raw command output tokens versus the compact shield summary. `benchmark session` summarizes recent local Claude/Codex sessions, including round-trip context signals like tool calls, repeated commands, repeated source reads, and MCP/tool surface.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
149
212
|
## new: context shield
|
|
150
213
|
|
|
151
214
|
if you know a command may dump huge output, run it through prismo:
|
|
@@ -543,6 +606,9 @@ no install needed. npx runs it directly.
|
|
|
543
606
|
| `cc` | claude code cost breakdown |
|
|
544
607
|
| `cc timeline` | session reconstruction with events |
|
|
545
608
|
| `scan --usage` | full repo scan with local usage data |
|
|
609
|
+
| `scan --optimizer-fit` | recommend which token-optimization path fits your repo/session |
|
|
610
|
+
| `scan --report-card` | shortest decision-layer summary |
|
|
611
|
+
| `benchmark` | measure command-output reduction or recent session round-trip context |
|
|
546
612
|
| `scan --simple` | plain-english summary |
|
|
547
613
|
| `scan --fix` | create safe fix files |
|
|
548
614
|
| `scan --ci` | fail CI when token-risk gates fail |
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module.exports = function createBenchmark(deps) {
|
|
2
|
+
const {
|
|
3
|
+
NPX_COMMAND,
|
|
4
|
+
estimateTokens,
|
|
5
|
+
formatTokenCount,
|
|
6
|
+
getUsageSummary,
|
|
7
|
+
runShield,
|
|
8
|
+
scanRepo,
|
|
9
|
+
color,
|
|
10
|
+
} = deps;
|
|
11
|
+
|
|
12
|
+
function estimateSummaryTokens(shieldResult) {
|
|
13
|
+
const lines = shieldResult.output && Array.isArray(shieldResult.output.interestingLines)
|
|
14
|
+
? shieldResult.output.interestingLines.join("\n")
|
|
15
|
+
: "";
|
|
16
|
+
return estimateTokens([
|
|
17
|
+
`Command: ${shieldResult.command}`,
|
|
18
|
+
`Exit: ${shieldResult.exitCode}`,
|
|
19
|
+
`Duration: ${shieldResult.durationMs}ms`,
|
|
20
|
+
lines,
|
|
21
|
+
].join("\n"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runBenchmark(rootDir = process.cwd(), commandArgs = [], options = {}) {
|
|
25
|
+
if (options.sessionOnly || !commandArgs.length) {
|
|
26
|
+
const scan = scanRepo(rootDir, { includeUsage: true, usageLimit: options.limit || 5 });
|
|
27
|
+
const usage = getUsageSummary({ cwd: scan.root, limit: options.limit || 5, tool: "all" });
|
|
28
|
+
const fit = scan.optimizerFit;
|
|
29
|
+
return {
|
|
30
|
+
schemaVersion: 1,
|
|
31
|
+
mode: "session",
|
|
32
|
+
scannedPath: scan.root,
|
|
33
|
+
score: scan.score,
|
|
34
|
+
riskLevel: scan.risk,
|
|
35
|
+
sessions: usage.sessions.length,
|
|
36
|
+
tokens: usage.totals,
|
|
37
|
+
roundTripContext: fit.roundTripContext,
|
|
38
|
+
optimizerFit: fit.summary,
|
|
39
|
+
recommendedStack: fit.recommendedStack,
|
|
40
|
+
next: [`${NPX_COMMAND} scan --optimizer-fit`, `${NPX_COMMAND} watch --once`, `${NPX_COMMAND} cc timeline`],
|
|
41
|
+
generatedAt: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const shield = runShield(rootDir, commandArgs);
|
|
46
|
+
const rawTokens = shield.output.estimatedTokens || 0;
|
|
47
|
+
const summaryTokens = estimateSummaryTokens(shield);
|
|
48
|
+
const reductionPercent = rawTokens > 0 ? Math.max(0, Math.round(((rawTokens - summaryTokens) / rawTokens) * 100)) : 0;
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
mode: "command",
|
|
52
|
+
scannedPath: shield.cwd,
|
|
53
|
+
command: shield.command,
|
|
54
|
+
exitCode: shield.exitCode,
|
|
55
|
+
durationMs: shield.durationMs,
|
|
56
|
+
rawOutput: {
|
|
57
|
+
bytes: shield.output.totalBytes,
|
|
58
|
+
estimatedTokens: rawTokens,
|
|
59
|
+
},
|
|
60
|
+
shieldedSummary: {
|
|
61
|
+
estimatedTokens: summaryTokens,
|
|
62
|
+
interestingLines: shield.output.interestingLines,
|
|
63
|
+
},
|
|
64
|
+
estimatedTokenReductionPercent: reductionPercent,
|
|
65
|
+
stored: shield.stored,
|
|
66
|
+
next: [
|
|
67
|
+
"Give the shield summary to the agent first.",
|
|
68
|
+
`${NPX_COMMAND} shield search "<error text>"`,
|
|
69
|
+
`${NPX_COMMAND} scan --optimizer-fit`,
|
|
70
|
+
],
|
|
71
|
+
generatedAt: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderBenchmarkTerminal(result) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(color("Prismo Benchmark", "bold"));
|
|
79
|
+
lines.push("");
|
|
80
|
+
if (result.mode === "session") {
|
|
81
|
+
lines.push(`Mode: session`);
|
|
82
|
+
lines.push(`Score: ${result.score}/100 (${result.riskLevel} risk)`);
|
|
83
|
+
lines.push(`Sessions: ${result.sessions}`);
|
|
84
|
+
lines.push(`Tokens: ${formatTokenCount(result.tokens.displayTokens || 0)} (${result.tokens.exactTokens ? "exact-local-log" : "estimated/local"})`);
|
|
85
|
+
lines.push(`Optimizer fit: ${result.optimizerFit}`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push("Round-Trip Context:");
|
|
88
|
+
lines.push(`- Level: ${result.roundTripContext.level}`);
|
|
89
|
+
lines.push(`- Tool calls: ${result.roundTripContext.toolCalls}`);
|
|
90
|
+
lines.push(`- Repeated commands: ${result.roundTripContext.repeatedCommandMentions}`);
|
|
91
|
+
lines.push(`- Repeated source reads: ${result.roundTripContext.repeatedSourceReads}`);
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push("Next:");
|
|
94
|
+
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push(`Mode: command`);
|
|
99
|
+
lines.push(`Command: ${result.command}`);
|
|
100
|
+
lines.push(`Exit: ${result.exitCode}`);
|
|
101
|
+
lines.push(`Duration: ${result.durationMs}ms`);
|
|
102
|
+
lines.push(`Raw output: ${result.rawOutput.bytes.toLocaleString()} bytes (~${result.rawOutput.estimatedTokens.toLocaleString()} tokens)`);
|
|
103
|
+
lines.push(`Shield summary: ~${result.shieldedSummary.estimatedTokens.toLocaleString()} tokens`);
|
|
104
|
+
lines.push(`Estimated reduction: ${result.estimatedTokenReductionPercent}%`);
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push("Stored Output:");
|
|
107
|
+
lines.push(`- ${result.stored.stdout}`);
|
|
108
|
+
lines.push(`- ${result.stored.stderr}`);
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("Useful Lines:");
|
|
111
|
+
result.shieldedSummary.interestingLines.slice(0, 12).forEach((line) => lines.push(`- ${line}`));
|
|
112
|
+
lines.push("");
|
|
113
|
+
lines.push("Next:");
|
|
114
|
+
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
renderBenchmarkTerminal,
|
|
120
|
+
runBenchmark,
|
|
121
|
+
};
|
|
122
|
+
};
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -105,6 +105,83 @@ function renderTerminalReport(result, options = {}) {
|
|
|
105
105
|
return lines.join("\n");
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
function renderOptimizerFitTerminal(result, options = {}) {
|
|
109
|
+
const useColor = options.color !== false;
|
|
110
|
+
const fit = result.optimizerFit;
|
|
111
|
+
const tone = fit.summary.includes("High") ? "red" : fit.summary.includes("Medium") ? "yellow" : "green";
|
|
112
|
+
const lines = [];
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(color("Prismo Optimizer Fit", "bold", useColor));
|
|
115
|
+
lines.push("");
|
|
116
|
+
lines.push(`Primary bottleneck: ${color(fit.summary, tone, useColor)}`);
|
|
117
|
+
if (result.realUsage && result.realUsage.sessions.length) {
|
|
118
|
+
lines.push(`Local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
|
|
119
|
+
} else if (result.realUsage) {
|
|
120
|
+
lines.push("Local usage: no matching local Claude/Codex sessions found");
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(color("Bottlenecks", "bold", useColor));
|
|
124
|
+
fit.bottlenecks.forEach((item) => {
|
|
125
|
+
lines.push(`- ${item.label}: ${item.level}`);
|
|
126
|
+
item.evidence.slice(0, 2).forEach((evidence) => lines.push(` ${evidence}`));
|
|
127
|
+
});
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(color("Recommended Stack", "bold", useColor));
|
|
130
|
+
fit.recommendedStack.forEach((item) => {
|
|
131
|
+
lines.push(`${item.rank}. ${item.action}`);
|
|
132
|
+
lines.push(` Run: ${item.command}`);
|
|
133
|
+
lines.push(` Why: ${item.why}`);
|
|
134
|
+
lines.push(` Category: ${item.category} (${item.examples.join(", ")})`);
|
|
135
|
+
});
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push(color("Tool Fit", "bold", useColor));
|
|
138
|
+
fit.toolFit.forEach((item) => {
|
|
139
|
+
lines.push(`- ${item.category}: ${item.fit}`);
|
|
140
|
+
lines.push(` Examples: ${item.examples.join(", ")}`);
|
|
141
|
+
lines.push(` ${item.reason}`);
|
|
142
|
+
});
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push(color("Round-Trip Context", "bold", useColor));
|
|
145
|
+
lines.push(`- Level: ${fit.roundTripContext.level}`);
|
|
146
|
+
lines.push(`- Tool calls: ${fit.roundTripContext.toolCalls}`);
|
|
147
|
+
lines.push(`- Repeated command mentions: ${fit.roundTripContext.repeatedCommandMentions}`);
|
|
148
|
+
lines.push(`- Repeated source reads: ${fit.roundTripContext.repeatedSourceReads}`);
|
|
149
|
+
lines.push(`- MCP/tool servers: ${fit.roundTripContext.mcpServers}`);
|
|
150
|
+
lines.push(`- ${fit.roundTripContext.recommendation}`);
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push("Notes:");
|
|
153
|
+
fit.caveats.forEach((caveat) => lines.push(`- ${caveat}`));
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderReportCardTerminal(result, options = {}) {
|
|
158
|
+
const useColor = options.color !== false;
|
|
159
|
+
const fit = result.optimizerFit;
|
|
160
|
+
const top = fit.recommendedStack[0];
|
|
161
|
+
const second = fit.recommendedStack[1];
|
|
162
|
+
const lines = [];
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push(color("PrismoDev Report Card", "bold", useColor));
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`Score: ${result.score}/100 (${result.risk} risk)`);
|
|
167
|
+
lines.push(`Biggest waste: ${fit.summary}`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
if (top) lines.push(`Start with: ${top.command}`);
|
|
170
|
+
if (second) lines.push(`Then: ${second.command}`);
|
|
171
|
+
lines.push("");
|
|
172
|
+
const codeIndex = fit.bottlenecks.find((item) => item.id === "code-indexing");
|
|
173
|
+
const output = fit.bottlenecks.find((item) => item.id === "output-sandboxing");
|
|
174
|
+
const ignore = fit.bottlenecks.find((item) => item.id === "ignore-cleanup");
|
|
175
|
+
lines.push(`Code index needed: ${codeIndex && codeIndex.level === "High" ? "yes" : codeIndex && codeIndex.level === "Medium" ? "maybe" : "not yet"}`);
|
|
176
|
+
lines.push(`Output sandbox needed: ${output && output.level !== "Low" ? "yes" : "not yet"}`);
|
|
177
|
+
lines.push(`Ignore cleanup needed: ${ignore && ignore.level !== "Low" ? "yes" : "not urgent"}`);
|
|
178
|
+
lines.push(`Round-trip risk: ${fit.roundTripContext.level}`);
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push("Why:");
|
|
181
|
+
fit.bottlenecks.slice(0, 3).forEach((item) => lines.push(`- ${item.label}: ${item.evidence[0]}`));
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
108
185
|
function evaluateCi(result, options = {}) {
|
|
109
186
|
const minScore = Number(options.minScore || 80);
|
|
110
187
|
const failures = [];
|
|
@@ -392,6 +469,8 @@ function writeReport(result) {
|
|
|
392
469
|
evaluateCi,
|
|
393
470
|
renderCiReport,
|
|
394
471
|
renderMarkdownReport,
|
|
472
|
+
renderOptimizerFitTerminal,
|
|
473
|
+
renderReportCardTerminal,
|
|
395
474
|
renderSimpleScanReport,
|
|
396
475
|
renderTerminalReport,
|
|
397
476
|
writeReport,
|
package/lib/prismo-dev/scan.js
CHANGED
|
@@ -390,12 +390,21 @@ function detectOptimizationStack(root, claudeConfig, codexConfig) {
|
|
|
390
390
|
const projectHeadroom = fs.existsSync(path.join(root, ".headroom")) || fs.existsSync(path.join(os.homedir(), ".headroom"));
|
|
391
391
|
const projectDistill = fs.existsSync(path.join(os.homedir(), ".config", "distill")) || commandExists("distill");
|
|
392
392
|
const projectRtk = fs.existsSync(path.join(root, ".rtk")) || commandExists("rtk");
|
|
393
|
+
const packageText = readIfText(path.join(root, "package.json"), 512 * 1024) || "";
|
|
394
|
+
const readmeText = readIfText(path.join(root, "README.md"), 512 * 1024) || "";
|
|
395
|
+
const projectText = `${packageText}\n${readmeText}`.toLowerCase();
|
|
396
|
+
const hasText = (pattern) => pattern.test(projectText);
|
|
393
397
|
|
|
394
398
|
const tools = {
|
|
395
399
|
rtk: { detected: projectRtk, source: projectRtk ? "binary-or-project-config" : "not-detected" },
|
|
396
400
|
headroom: { detected: projectHeadroom || commandExists("headroom"), source: projectHeadroom ? "local-config" : commandExists("headroom") ? "binary" : "not-detected" },
|
|
397
401
|
distill: { detected: projectDistill, source: projectDistill ? "binary-or-user-config" : "not-detected" },
|
|
398
402
|
mana: { detected: projectMana || commandExists("mana"), source: projectMana ? "local-config" : commandExists("mana") ? "binary" : "not-detected" },
|
|
403
|
+
contextMode: { detected: commandExists("context-mode") || hasText(/context-mode/), source: commandExists("context-mode") ? "binary" : hasText(/context-mode/) ? "project-reference" : "not-detected" },
|
|
404
|
+
leanCtx: { detected: commandExists("lean-ctx") || hasText(/lean-ctx|lean ctx/), source: commandExists("lean-ctx") ? "binary" : hasText(/lean-ctx|lean ctx/) ? "project-reference" : "not-detected" },
|
|
405
|
+
repomix: { detected: commandExists("repomix") || hasText(/repomix/), source: commandExists("repomix") ? "binary" : hasText(/repomix/) ? "project-reference" : "not-detected" },
|
|
406
|
+
codegraph: { detected: commandExists("codegraph") || hasText(/codegraph|codebase-memory-mcp|jcodemunch|sigmap/), source: commandExists("codegraph") ? "binary" : hasText(/codegraph|codebase-memory-mcp|jcodemunch|sigmap/) ? "project-reference" : "not-detected" },
|
|
407
|
+
tokf: { detected: commandExists("tokf") || hasText(/tokf/), source: commandExists("tokf") ? "binary" : hasText(/tokf/) ? "project-reference" : "not-detected" },
|
|
399
408
|
};
|
|
400
409
|
|
|
401
410
|
return {
|
|
@@ -786,6 +795,253 @@ function buildRecommendations({ hasClaudeIgnore, gitignorePatterns, exposedHighR
|
|
|
786
795
|
return Array.from(new Set(recs));
|
|
787
796
|
}
|
|
788
797
|
|
|
798
|
+
function levelFromScore(score) {
|
|
799
|
+
if (score >= 70) return "High";
|
|
800
|
+
if (score >= 35) return "Medium";
|
|
801
|
+
return "Low";
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function addEvidence(evidence, text) {
|
|
805
|
+
if (text && !evidence.includes(text)) evidence.push(text);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function countRepeatedSourceReads(realUsage) {
|
|
809
|
+
if (!realUsage || !Array.isArray(realUsage.sessions)) return 0;
|
|
810
|
+
const generatedPattern = /(^|\/)(node_modules|dist|build|coverage|\.next|__pycache__|logs|test-results|playwright-report)\//;
|
|
811
|
+
return realUsage.sessions.reduce((sum, session) => {
|
|
812
|
+
return sum + (session.repeatedPathMentions || []).filter((item) => {
|
|
813
|
+
const value = String(item.value || "");
|
|
814
|
+
if (!value || generatedPattern.test(value)) return false;
|
|
815
|
+
return /\.(js|jsx|ts|tsx|py|go|rs|java|kt|swift|rb|php|cs|svelte|vue|astro|md|json|toml|yaml|yml)$/i.test(value);
|
|
816
|
+
}).reduce((inner, item) => inner + Number(item.count || 0), 0);
|
|
817
|
+
}, 0);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function buildOptimizerFit(result) {
|
|
821
|
+
const bottlenecks = [];
|
|
822
|
+
const realUsage = result.realUsage;
|
|
823
|
+
const toolTokens = realUsage ? Number(realUsage.totals.toolTokens || 0) : 0;
|
|
824
|
+
const displayTokens = realUsage ? Number(realUsage.totals.displayTokens || 0) : 0;
|
|
825
|
+
const highRiskSessions = realUsage ? realUsage.sessions.filter((session) => session.contextRisk === "High").length : 0;
|
|
826
|
+
const repeatedSourceReads = countRepeatedSourceReads(realUsage);
|
|
827
|
+
|
|
828
|
+
const ignoreEvidence = [];
|
|
829
|
+
let ignoreScore = 0;
|
|
830
|
+
if (!result.hasClaudeIgnore) {
|
|
831
|
+
ignoreScore += 35;
|
|
832
|
+
addEvidence(ignoreEvidence, ".claudeignore is missing");
|
|
833
|
+
}
|
|
834
|
+
if (!result.hasCursorIgnore) {
|
|
835
|
+
ignoreScore += 20;
|
|
836
|
+
addEvidence(ignoreEvidence, ".cursorignore is missing");
|
|
837
|
+
}
|
|
838
|
+
if (result.exposedHighRiskDirs.length) {
|
|
839
|
+
ignoreScore += Math.min(35, result.exposedHighRiskDirs.length * 8);
|
|
840
|
+
addEvidence(ignoreEvidence, `${result.exposedHighRiskDirs.length} generated/cache directories are exposed`);
|
|
841
|
+
}
|
|
842
|
+
if ((result.sessionIgnoreSuggestions || []).length) {
|
|
843
|
+
ignoreScore += 25;
|
|
844
|
+
addEvidence(ignoreEvidence, `${result.sessionIgnoreSuggestions.length} ignore rules came from actual session leaks`);
|
|
845
|
+
}
|
|
846
|
+
bottlenecks.push({
|
|
847
|
+
id: "ignore-cleanup",
|
|
848
|
+
label: "Generated artifacts / ignore cleanup",
|
|
849
|
+
score: Math.min(100, ignoreScore),
|
|
850
|
+
level: levelFromScore(ignoreScore),
|
|
851
|
+
evidence: ignoreEvidence.length ? ignoreEvidence : ["No major ignore-file leak detected"],
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
const outputEvidence = [];
|
|
855
|
+
let outputScore = result.toolOutputRisk.level === "High" ? 70 : result.toolOutputRisk.level === "Medium" ? 45 : 10;
|
|
856
|
+
if (toolTokens >= 150000) outputScore += 25;
|
|
857
|
+
else if (toolTokens >= 50000) outputScore += 15;
|
|
858
|
+
if (result.toolOutputRisk.exposedNoisyFiles.length) addEvidence(outputEvidence, `${result.toolOutputRisk.exposedNoisyFiles.length} noisy files are exposed`);
|
|
859
|
+
if (result.toolOutputRisk.exposedNoisyDirectories.length) addEvidence(outputEvidence, `${result.toolOutputRisk.exposedNoisyDirectories.length} noisy directories are exposed`);
|
|
860
|
+
if (toolTokens) addEvidence(outputEvidence, `${formatTokenCount(toolTokens)} tool/output tokens found in local sessions`);
|
|
861
|
+
bottlenecks.push({
|
|
862
|
+
id: "output-sandboxing",
|
|
863
|
+
label: "Oversized command/tool output",
|
|
864
|
+
score: Math.min(100, outputScore),
|
|
865
|
+
level: levelFromScore(outputScore),
|
|
866
|
+
evidence: outputEvidence.length ? outputEvidence : ["No dominant command-output flood detected"],
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
const indexEvidence = [];
|
|
870
|
+
let indexScore = 0;
|
|
871
|
+
if (result.stats.sourceFiles >= 1000) indexScore += 35;
|
|
872
|
+
else if (result.stats.sourceFiles >= 250) indexScore += 20;
|
|
873
|
+
if (result.stats.totalFiles >= 5000) indexScore += 25;
|
|
874
|
+
else if (result.stats.totalFiles >= 1000) indexScore += 15;
|
|
875
|
+
if (repeatedSourceReads >= 50) indexScore += 35;
|
|
876
|
+
else if (repeatedSourceReads >= 12) indexScore += 20;
|
|
877
|
+
if (result.stats.sourceFiles >= 250) addEvidence(indexEvidence, `${result.stats.sourceFiles.toLocaleString()} source files`);
|
|
878
|
+
if (repeatedSourceReads) addEvidence(indexEvidence, `${repeatedSourceReads} repeated source-file mentions in local sessions`);
|
|
879
|
+
bottlenecks.push({
|
|
880
|
+
id: "code-indexing",
|
|
881
|
+
label: "Repeated source exploration",
|
|
882
|
+
score: Math.min(100, indexScore),
|
|
883
|
+
level: levelFromScore(indexScore),
|
|
884
|
+
evidence: indexEvidence.length ? indexEvidence : ["Repo/source exploration does not look like the main bottleneck"],
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
const instructionEvidence = [];
|
|
888
|
+
const instructionTokens = result.instructionFiles.reduce((sum, file) => sum + Math.max(0, (file.tokens || 0) - 500), 0);
|
|
889
|
+
let instructionScore = instructionTokens >= 3000 ? 80 : instructionTokens >= 1000 ? 55 : instructionTokens > 0 ? 30 : 0;
|
|
890
|
+
result.instructionFiles
|
|
891
|
+
.filter((file) => file.tokens > 500)
|
|
892
|
+
.slice(0, 3)
|
|
893
|
+
.forEach((file) => addEvidence(instructionEvidence, `${file.path} is ~${(file.tokens || 0).toLocaleString()} tokens`));
|
|
894
|
+
bottlenecks.push({
|
|
895
|
+
id: "instruction-trim",
|
|
896
|
+
label: "Persistent instruction bloat",
|
|
897
|
+
score: Math.min(100, instructionScore),
|
|
898
|
+
level: levelFromScore(instructionScore),
|
|
899
|
+
evidence: instructionEvidence.length ? instructionEvidence : ["Persistent instruction files look manageable"],
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const sessionEvidence = [];
|
|
903
|
+
let sessionScore = 0;
|
|
904
|
+
if (displayTokens >= 2000000) sessionScore += 60;
|
|
905
|
+
else if (displayTokens >= 500000) sessionScore += 35;
|
|
906
|
+
if (highRiskSessions) sessionScore += Math.min(35, highRiskSessions * 18);
|
|
907
|
+
if (displayTokens) addEvidence(sessionEvidence, `${formatTokenCount(displayTokens)} tokens across recent local sessions`);
|
|
908
|
+
if (highRiskSessions) addEvidence(sessionEvidence, `${highRiskSessions} high-context-risk session${highRiskSessions === 1 ? "" : "s"}`);
|
|
909
|
+
bottlenecks.push({
|
|
910
|
+
id: "session-splitting",
|
|
911
|
+
label: "Long-session context buildup",
|
|
912
|
+
score: Math.min(100, sessionScore),
|
|
913
|
+
level: levelFromScore(sessionScore),
|
|
914
|
+
evidence: sessionEvidence.length ? sessionEvidence : ["No matching high-growth local sessions found"],
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const mcpEvidence = [];
|
|
918
|
+
let mcpScore = result.optimizationStack.mcpServerTotal >= 10 ? 70 : result.optimizationStack.mcpServerTotal >= 5 ? 45 : 10;
|
|
919
|
+
if (result.optimizationStack.mcpServerTotal) addEvidence(mcpEvidence, `${result.optimizationStack.mcpServerTotal} MCP/tool servers detected`);
|
|
920
|
+
const totalToolCalls = realUsage ? realUsage.sessions.reduce((sum, session) => sum + Number(session.toolCalls || 0), 0) : 0;
|
|
921
|
+
const repeatedCommands = realUsage ? realUsage.sessions.reduce((sum, session) => sum + (session.repeatedCommands || []).reduce((inner, item) => inner + Number(item.count || 0), 0), 0) : 0;
|
|
922
|
+
if (totalToolCalls >= 500) mcpScore += 30;
|
|
923
|
+
else if (totalToolCalls >= 100) mcpScore += 15;
|
|
924
|
+
if (repeatedCommands >= 20) mcpScore += 20;
|
|
925
|
+
if (totalToolCalls) addEvidence(mcpEvidence, `${totalToolCalls} tool calls in recent local sessions`);
|
|
926
|
+
if (repeatedCommands) addEvidence(mcpEvidence, `${repeatedCommands} repeated command/tool mentions`);
|
|
927
|
+
bottlenecks.push({
|
|
928
|
+
id: "tool-surface",
|
|
929
|
+
label: "Tool/MCP surface overhead",
|
|
930
|
+
score: Math.min(100, mcpScore),
|
|
931
|
+
level: levelFromScore(mcpScore),
|
|
932
|
+
evidence: mcpEvidence.length ? mcpEvidence : ["Tool surface does not look unusually large"],
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
const ranked = bottlenecks.sort((a, b) => b.score - a.score || a.label.localeCompare(b.label));
|
|
936
|
+
const primary = ranked[0];
|
|
937
|
+
const actionById = {
|
|
938
|
+
"ignore-cleanup": {
|
|
939
|
+
action: "Apply safe ignore/context fixes first.",
|
|
940
|
+
command: `${NPX_COMMAND} doctor --apply-suggestions --dry-run`,
|
|
941
|
+
category: "ignore cleanup",
|
|
942
|
+
examples: ["Prismo doctor", ".claudeignore", ".cursorignore"],
|
|
943
|
+
},
|
|
944
|
+
"output-sandboxing": {
|
|
945
|
+
action: "Sandbox noisy command output before adding more code-indexing tools.",
|
|
946
|
+
command: `${NPX_COMMAND} shield -- <noisy command>`,
|
|
947
|
+
category: "output sandboxing",
|
|
948
|
+
examples: ["Prismo shield", "context-mode", "RTK", "tokf", "distill"],
|
|
949
|
+
},
|
|
950
|
+
"code-indexing": {
|
|
951
|
+
action: "Use a code indexer if repeated source exploration keeps happening.",
|
|
952
|
+
command: `${NPX_COMMAND} context`,
|
|
953
|
+
category: "code indexing",
|
|
954
|
+
examples: ["codegraph", "jcodemunch", "codebase-memory-mcp", "sigmap"],
|
|
955
|
+
},
|
|
956
|
+
"instruction-trim": {
|
|
957
|
+
action: "Trim persistent instructions before adding runtime compression.",
|
|
958
|
+
command: `${NPX_COMMAND} doctor`,
|
|
959
|
+
category: "instruction quality",
|
|
960
|
+
examples: ["CLAUDE.md cleanup", "AGENTS.md cleanup", "caveman-style concise responses"],
|
|
961
|
+
},
|
|
962
|
+
"session-splitting": {
|
|
963
|
+
action: "Split long sessions and recover from context pressure while working.",
|
|
964
|
+
command: `${NPX_COMMAND} watch --auto`,
|
|
965
|
+
category: "session control",
|
|
966
|
+
examples: ["Prismo watch", "Prismo rescue", "fresh task sessions"],
|
|
967
|
+
},
|
|
968
|
+
"tool-surface": {
|
|
969
|
+
action: "Reduce unused MCP/tool surface for the current task.",
|
|
970
|
+
command: `${NPX_COMMAND} mcp doctor`,
|
|
971
|
+
category: "tool hygiene",
|
|
972
|
+
examples: ["disable unused MCP servers", "strict task-scoped tool config"],
|
|
973
|
+
},
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const recommendedStack = ranked
|
|
977
|
+
.filter((item) => item.level !== "Low")
|
|
978
|
+
.slice(0, 4)
|
|
979
|
+
.map((item, index) => ({ rank: index + 1, bottleneck: item.id, ...actionById[item.id], why: item.evidence[0] }));
|
|
980
|
+
|
|
981
|
+
if (!recommendedStack.length) {
|
|
982
|
+
recommendedStack.push({
|
|
983
|
+
rank: 1,
|
|
984
|
+
bottleneck: "baseline",
|
|
985
|
+
action: "Keep the stack simple; no major optimizer fit signal was detected.",
|
|
986
|
+
command: `${NPX_COMMAND} watch --once`,
|
|
987
|
+
category: "baseline monitoring",
|
|
988
|
+
examples: ["Prismo watch", "Prismo cc timeline"],
|
|
989
|
+
why: "Repo scan did not find a dominant token-waste source.",
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
schemaVersion: 1,
|
|
995
|
+
primaryBottleneck: primary.id,
|
|
996
|
+
summary: `${primary.label}: ${primary.level}`,
|
|
997
|
+
bottlenecks: ranked,
|
|
998
|
+
recommendedStack,
|
|
999
|
+
toolFit: [
|
|
1000
|
+
{
|
|
1001
|
+
category: "PrismoDev workflow",
|
|
1002
|
+
fit: "High",
|
|
1003
|
+
examples: ["doctor", "watch", "shield", "cc timeline"],
|
|
1004
|
+
reason: "Use this first to diagnose repo/session waste and verify before stacking optimizers.",
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
category: "Output compression/sandboxing",
|
|
1008
|
+
fit: ranked.find((item) => item.id === "output-sandboxing").level,
|
|
1009
|
+
examples: ["Prismo shield", "context-mode", "RTK", "tokf", "distill", "headroom"],
|
|
1010
|
+
reason: "Best when shell/test/log output is the dominant waste source.",
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
category: "Code indexing / AST graph",
|
|
1014
|
+
fit: ranked.find((item) => item.id === "code-indexing").level,
|
|
1015
|
+
examples: ["codegraph", "jcodemunch", "codebase-memory-mcp", "sigmap"],
|
|
1016
|
+
reason: "Best when the agent repeatedly greps/reads source files to orient itself.",
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
category: "Repo packing",
|
|
1020
|
+
fit: result.stats.sourceFiles && result.stats.sourceFiles <= 250 && result.toolOutputRisk.level === "Low" ? "Medium" : "Low",
|
|
1021
|
+
examples: ["repomix", "Prismo context packs"],
|
|
1022
|
+
reason: "Best for one-shot repo handoff, less ideal for long live coding sessions.",
|
|
1023
|
+
},
|
|
1024
|
+
],
|
|
1025
|
+
roundTripContext: {
|
|
1026
|
+
level: levelFromScore(Math.max(mcpScore, repeatedSourceReads >= 50 ? 60 : repeatedSourceReads >= 12 ? 35 : 0)),
|
|
1027
|
+
toolCalls: totalToolCalls,
|
|
1028
|
+
repeatedCommandMentions: repeatedCommands,
|
|
1029
|
+
repeatedSourceReads,
|
|
1030
|
+
mcpServers: result.optimizationStack.mcpServerTotal,
|
|
1031
|
+
summary: totalToolCalls || repeatedCommands || repeatedSourceReads || result.optimizationStack.mcpServerTotal
|
|
1032
|
+
? "Round-trip context risk includes tool calls, repeated commands, repeated source reads, and MCP/tool surface."
|
|
1033
|
+
: "No strong round-trip context signal found in local logs.",
|
|
1034
|
+
recommendation: "Measure workflow-level savings, not only compressed payload size. Fewer tool round trips can beat smaller individual responses.",
|
|
1035
|
+
},
|
|
1036
|
+
caveats: [
|
|
1037
|
+
"Do not stack optimizers blindly; measure one real workflow before and after.",
|
|
1038
|
+
"Payload reduction is not the same as workflow savings; repeated tool calls can erase compression wins.",
|
|
1039
|
+
"Token savings are only useful if the agent still finds the right files and produces accepted changes.",
|
|
1040
|
+
],
|
|
1041
|
+
nextCommands: recommendedStack.map((item) => item.command),
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
|
|
789
1045
|
function scoreScan(issues, stats, context = {}) {
|
|
790
1046
|
const issuePenalty = issues.reduce((sum, issue) => sum + severityWeight(issue.severity), 0);
|
|
791
1047
|
const repoPenalty =
|
|
@@ -897,6 +1153,7 @@ function toJsonPayload(result) {
|
|
|
897
1153
|
optimizationStack: result.optimizationStack,
|
|
898
1154
|
toolOutputRisk: result.toolOutputRisk,
|
|
899
1155
|
operationalNoise: result.operationalNoise,
|
|
1156
|
+
optimizerFit: result.optimizerFit,
|
|
900
1157
|
sessionIgnoreSuggestions: result.sessionIgnoreSuggestions || [],
|
|
901
1158
|
proxyTrackingReadiness: result.proxyTrackingReadiness,
|
|
902
1159
|
suggestedClaudeIgnore: result.recommendedClaudeIgnore,
|
|
@@ -1308,7 +1565,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1308
1565
|
});
|
|
1309
1566
|
buildRealUsageRecommendations(realUsage).forEach((rec) => recommendations.push(rec));
|
|
1310
1567
|
|
|
1311
|
-
|
|
1568
|
+
const scanResult = {
|
|
1312
1569
|
root,
|
|
1313
1570
|
score: score.score,
|
|
1314
1571
|
risk: score.risk,
|
|
@@ -1344,6 +1601,8 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1344
1601
|
topTokenLeaks: getTopTokenLeaks(issues),
|
|
1345
1602
|
generatedAt: new Date().toISOString(),
|
|
1346
1603
|
};
|
|
1604
|
+
scanResult.optimizerFit = buildOptimizerFit(scanResult);
|
|
1605
|
+
return scanResult;
|
|
1347
1606
|
}
|
|
1348
1607
|
|
|
1349
1608
|
return {
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -193,6 +193,8 @@ const {
|
|
|
193
193
|
evaluateCi,
|
|
194
194
|
renderCiReport,
|
|
195
195
|
renderMarkdownReport,
|
|
196
|
+
renderOptimizerFitTerminal,
|
|
197
|
+
renderReportCardTerminal,
|
|
196
198
|
renderSimpleScanReport,
|
|
197
199
|
renderTerminalReport,
|
|
198
200
|
writeReport,
|
|
@@ -284,6 +286,19 @@ const {
|
|
|
284
286
|
color,
|
|
285
287
|
});
|
|
286
288
|
|
|
289
|
+
const {
|
|
290
|
+
renderBenchmarkTerminal,
|
|
291
|
+
runBenchmark,
|
|
292
|
+
} = require("./prismo-dev/benchmark")({
|
|
293
|
+
NPX_COMMAND,
|
|
294
|
+
estimateTokens,
|
|
295
|
+
formatTokenCount,
|
|
296
|
+
getUsageSummary,
|
|
297
|
+
runShield,
|
|
298
|
+
scanRepo: (...args) => scanRepo(...args),
|
|
299
|
+
color,
|
|
300
|
+
});
|
|
301
|
+
|
|
287
302
|
const {
|
|
288
303
|
renderMcpDoctorTerminal,
|
|
289
304
|
runMcpDoctor,
|
|
@@ -299,13 +314,14 @@ Usage:
|
|
|
299
314
|
prismo init [--json] [--dry-run] [path]
|
|
300
315
|
prismo doctor [--json] [--dry-run] [--apply-ignores-only] [--apply-suggestions] [--no-context-packs] [--limit N] [path]
|
|
301
316
|
prismo firewall [task] [--json] [--dry-run] [path]
|
|
317
|
+
prismo benchmark [session] [--json] [--limit N] [path] [-- <command ...>]
|
|
302
318
|
prismo shield [--json] [path] -- <command ...>
|
|
303
319
|
prismo shield last [--json] [--limit N] [path]
|
|
304
320
|
prismo shield search <query> [--json] [--limit N] [path]
|
|
305
321
|
prismo mcp [path]
|
|
306
322
|
prismo mcp doctor [--json] [path]
|
|
307
323
|
prismo setup [--json] [--proxy-url URL] [path]
|
|
308
|
-
prismo scan [--fix] [--ci] [--json] [--usage] [--simple] [--no-report] [path]
|
|
324
|
+
prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
|
|
309
325
|
prismo optimize [scope] [--json] [path]
|
|
310
326
|
prismo context [scope] [--json] [path]
|
|
311
327
|
prismo cc [list|last N|all] [--json] [--limit N] [path]
|
|
@@ -318,6 +334,7 @@ Commands:
|
|
|
318
334
|
init Add local PrismoDev helper docs and npm scripts when package.json exists.
|
|
319
335
|
doctor Diagnose, safely optimize, re-scan, and show before/after payoff.
|
|
320
336
|
firewall Generate allowed/blocked context policy files for an AI coding task.
|
|
337
|
+
benchmark Measure command-output savings or recent session round-trip context.
|
|
321
338
|
shield Run a noisy command, store full output locally, and return a compact summary.
|
|
322
339
|
mcp Start a local MCP server exposing Prismo tools over stdio.
|
|
323
340
|
scan Run PrismoDev for Claude Code, Codex, Cursor, and AI coding workflows.
|
|
@@ -334,6 +351,8 @@ Options:
|
|
|
334
351
|
--ci Fail with exit code 1 when token-risk gates fail.
|
|
335
352
|
--json Output valid JSON only for CI or future dashboard ingestion.
|
|
336
353
|
--usage Include real local Codex/Claude Code session usage in scan diagnostics.
|
|
354
|
+
--optimizer-fit Recommend the right optimization path for this repo/session.
|
|
355
|
+
--report-card Print a short plain-English optimization report card.
|
|
337
356
|
--simple Print a plain-English scan summary for first-time or non-technical users.
|
|
338
357
|
--no-report Do not write .prismo/prismo-dev-report.md.
|
|
339
358
|
--limit N Number of recent local sessions to show.
|
|
@@ -373,18 +392,23 @@ function printCommandHelp(command) {
|
|
|
373
392
|
scan: `PrismoDev
|
|
374
393
|
|
|
375
394
|
Usage:
|
|
376
|
-
prismo scan [--fix] [--ci] [--json] [--usage] [--simple] [--no-report] [--limit N] [path]
|
|
395
|
+
prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [--limit N] [path]
|
|
377
396
|
|
|
378
397
|
Examples:
|
|
379
398
|
prismo scan
|
|
380
399
|
prismo scan --usage
|
|
400
|
+
prismo scan --optimizer-fit
|
|
401
|
+
prismo scan --report-card
|
|
381
402
|
prismo scan --simple
|
|
382
403
|
prismo scan --fix
|
|
383
404
|
prismo scan --ci
|
|
384
405
|
prismo scan --usage --json --no-report
|
|
406
|
+
prismo scan --optimizer-fit --json
|
|
385
407
|
|
|
386
408
|
Notes:
|
|
387
409
|
--usage reads local Codex/Claude Code logs when present.
|
|
410
|
+
--optimizer-fit explains whether ignore cleanup, output sandboxing, code indexing, repo packing, instruction trimming, or session splitting fits this repo best.
|
|
411
|
+
--report-card prints the shortest decision-layer summary.
|
|
388
412
|
--simple keeps the output short and does not write a report unless combined with --fix.
|
|
389
413
|
--fix creates safe recommendation files and never overwrites CLAUDE.md or AGENTS.md.`,
|
|
390
414
|
optimize: `Prismo Optimize
|
|
@@ -603,8 +627,8 @@ async function runCli(argv) {
|
|
|
603
627
|
printCommandHelp(command);
|
|
604
628
|
return;
|
|
605
629
|
}
|
|
606
|
-
if (!["dev", "init", "doctor", "firewall", "shield", "mcp", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
|
|
607
|
-
throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, or prismo usage`);
|
|
630
|
+
if (!["dev", "init", "doctor", "firewall", "benchmark", "shield", "mcp", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
|
|
631
|
+
throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, or prismo usage`);
|
|
608
632
|
}
|
|
609
633
|
|
|
610
634
|
if (command === "demo") {
|
|
@@ -687,6 +711,25 @@ async function runCli(argv) {
|
|
|
687
711
|
return;
|
|
688
712
|
}
|
|
689
713
|
|
|
714
|
+
if (command === "benchmark") {
|
|
715
|
+
const json = rest.includes("--json");
|
|
716
|
+
const limitIndex = rest.indexOf("--limit");
|
|
717
|
+
const separatorIndex = rest.indexOf("--");
|
|
718
|
+
const beforeSeparator = separatorIndex >= 0 ? rest.slice(0, separatorIndex) : rest;
|
|
719
|
+
const commandArgs = separatorIndex >= 0 ? rest.slice(separatorIndex + 1) : [];
|
|
720
|
+
const positional = getPositionals(beforeSeparator, new Set(["--limit"]));
|
|
721
|
+
const sessionOnly = positional[0] === "session" || commandArgs.length === 0;
|
|
722
|
+
const target = positional[0] === "session" ? positional[1] || process.cwd() : positional[0] || process.cwd();
|
|
723
|
+
const result = runBenchmark(target, commandArgs, {
|
|
724
|
+
sessionOnly,
|
|
725
|
+
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
|
|
726
|
+
});
|
|
727
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
728
|
+
else console.log(renderBenchmarkTerminal(result));
|
|
729
|
+
if (result.mode === "command") process.exitCode = result.exitCode;
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
690
733
|
if (command === "shield") {
|
|
691
734
|
const json = rest.includes("--json");
|
|
692
735
|
const limitIndex = rest.indexOf("--limit");
|
|
@@ -920,12 +963,14 @@ async function runCli(argv) {
|
|
|
920
963
|
const noReport = rest.includes("--no-report");
|
|
921
964
|
const json = rest.includes("--json");
|
|
922
965
|
const simple = rest.includes("--simple");
|
|
966
|
+
const optimizerFit = rest.includes("--optimizer-fit");
|
|
967
|
+
const reportCard = rest.includes("--report-card");
|
|
923
968
|
const ciMode = rest.includes("--ci");
|
|
924
|
-
const includeUsage = rest.includes("--usage");
|
|
969
|
+
const includeUsage = rest.includes("--usage") || optimizerFit || reportCard;
|
|
925
970
|
const limitIndex = rest.indexOf("--limit");
|
|
926
971
|
const usageToolIndex = rest.indexOf("--usage-tool");
|
|
927
972
|
const target = getPositionals(rest, new Set(["--limit", "--usage-tool"]))[0] || process.cwd();
|
|
928
|
-
const scanDone = printStep(includeUsage ? "Scanning repo and local usage" : "Scanning repo", json || simple);
|
|
973
|
+
const scanDone = printStep(includeUsage ? "Scanning repo and local usage" : "Scanning repo", json || simple || optimizerFit || reportCard);
|
|
929
974
|
const result = scanRepo(target, {
|
|
930
975
|
includeUsage,
|
|
931
976
|
usageLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
|
|
@@ -938,7 +983,7 @@ async function runCli(argv) {
|
|
|
938
983
|
let report = null;
|
|
939
984
|
if (fix) {
|
|
940
985
|
fixActions = applyFixes(result);
|
|
941
|
-
} else if (!noReport) {
|
|
986
|
+
} else if (!noReport && !optimizerFit) {
|
|
942
987
|
report = writeReport(result);
|
|
943
988
|
}
|
|
944
989
|
const payload = toJsonPayload(result);
|
|
@@ -946,13 +991,34 @@ async function runCli(argv) {
|
|
|
946
991
|
payload.ci = evaluateCi(result);
|
|
947
992
|
if (!payload.ci.passed) process.exitCode = 1;
|
|
948
993
|
}
|
|
994
|
+
if (optimizerFit || reportCard) {
|
|
995
|
+
console.log(JSON.stringify({
|
|
996
|
+
schemaVersion: 1,
|
|
997
|
+
scannedPath: result.root,
|
|
998
|
+
score: result.score,
|
|
999
|
+
riskLevel: result.risk,
|
|
1000
|
+
optimizerFit: result.optimizerFit,
|
|
1001
|
+
reportCard: reportCard ? {
|
|
1002
|
+
biggestWaste: result.optimizerFit.summary,
|
|
1003
|
+
startWith: result.optimizerFit.recommendedStack[0]?.command || null,
|
|
1004
|
+
then: result.optimizerFit.recommendedStack[1]?.command || null,
|
|
1005
|
+
roundTripRisk: result.optimizerFit.roundTripContext.level,
|
|
1006
|
+
} : undefined,
|
|
1007
|
+
generatedAt: result.generatedAt,
|
|
1008
|
+
}, null, 2));
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
949
1011
|
if (fixActions.length) payload.fixActions = fixActions;
|
|
950
1012
|
if (report) payload.reportPath = report.reportPath;
|
|
951
1013
|
console.log(JSON.stringify(payload, null, 2));
|
|
952
1014
|
return;
|
|
953
1015
|
}
|
|
954
1016
|
|
|
955
|
-
if (
|
|
1017
|
+
if (reportCard) {
|
|
1018
|
+
console.log(renderReportCardTerminal(result));
|
|
1019
|
+
} else if (optimizerFit) {
|
|
1020
|
+
console.log(renderOptimizerFitTerminal(result));
|
|
1021
|
+
} else if (simple) {
|
|
956
1022
|
console.log(renderSimpleScanReport(result));
|
|
957
1023
|
} else if (ciMode) {
|
|
958
1024
|
const ci = evaluateCi(result);
|
|
@@ -966,7 +1032,7 @@ async function runCli(argv) {
|
|
|
966
1032
|
const actions = applyFixes(result);
|
|
967
1033
|
console.log("\nFix Mode:");
|
|
968
1034
|
actions.forEach((action) => console.log(`- ${action}`));
|
|
969
|
-
} else if (!noReport && !simple) {
|
|
1035
|
+
} else if (!noReport && !simple && !optimizerFit && !reportCard) {
|
|
970
1036
|
const report = writeReport(result);
|
|
971
1037
|
if (report.backupPath) {
|
|
972
1038
|
console.log(`\nExisting report backed up to ${path.basename(report.backupPath)}.`);
|
|
@@ -987,10 +1053,13 @@ module.exports = {
|
|
|
987
1053
|
renderWatchTerminal,
|
|
988
1054
|
renderWatchReport,
|
|
989
1055
|
renderTerminalReport,
|
|
1056
|
+
renderOptimizerFitTerminal,
|
|
1057
|
+
renderReportCardTerminal,
|
|
990
1058
|
renderDoctorTerminal,
|
|
991
1059
|
renderInitTerminal,
|
|
992
1060
|
runSetup,
|
|
993
1061
|
runOptimize,
|
|
1062
|
+
runBenchmark,
|
|
994
1063
|
runDoctor,
|
|
995
1064
|
runInit,
|
|
996
1065
|
runCli,
|
package/package.json
CHANGED