getprismo 0.1.21 → 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 CHANGED
@@ -180,6 +180,33 @@ Recommended Stack
180
180
 
181
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
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
+
183
210
  ---
184
211
 
185
212
  ## new: context shield
@@ -580,6 +607,8 @@ no install needed. npx runs it directly.
580
607
  | `cc timeline` | session reconstruction with events |
581
608
  | `scan --usage` | full repo scan with local usage data |
582
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 |
583
612
  | `scan --simple` | plain-english summary |
584
613
  | `scan --fix` | create safe fix files |
585
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
+ };
@@ -141,11 +141,47 @@ function renderOptimizerFitTerminal(result, options = {}) {
141
141
  lines.push(` ${item.reason}`);
142
142
  });
143
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("");
144
152
  lines.push("Notes:");
145
153
  fit.caveats.forEach((caveat) => lines.push(`- ${caveat}`));
146
154
  return lines.join("\n");
147
155
  }
148
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
+
149
185
  function evaluateCi(result, options = {}) {
150
186
  const minScore = Number(options.minScore || 80);
151
187
  const failures = [];
@@ -434,6 +470,7 @@ function writeReport(result) {
434
470
  renderCiReport,
435
471
  renderMarkdownReport,
436
472
  renderOptimizerFitTerminal,
473
+ renderReportCardTerminal,
437
474
  renderSimpleScanReport,
438
475
  renderTerminalReport,
439
476
  writeReport,
@@ -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 {
@@ -908,6 +917,13 @@ function buildOptimizerFit(result) {
908
917
  const mcpEvidence = [];
909
918
  let mcpScore = result.optimizationStack.mcpServerTotal >= 10 ? 70 : result.optimizationStack.mcpServerTotal >= 5 ? 45 : 10;
910
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`);
911
927
  bottlenecks.push({
912
928
  id: "tool-surface",
913
929
  label: "Tool/MCP surface overhead",
@@ -1006,6 +1022,17 @@ function buildOptimizerFit(result) {
1006
1022
  reason: "Best for one-shot repo handoff, less ideal for long live coding sessions.",
1007
1023
  },
1008
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
+ },
1009
1036
  caveats: [
1010
1037
  "Do not stack optimizers blindly; measure one real workflow before and after.",
1011
1038
  "Payload reduction is not the same as workflow savings; repeated tool calls can erase compression wins.",
@@ -194,6 +194,7 @@ const {
194
194
  renderCiReport,
195
195
  renderMarkdownReport,
196
196
  renderOptimizerFitTerminal,
197
+ renderReportCardTerminal,
197
198
  renderSimpleScanReport,
198
199
  renderTerminalReport,
199
200
  writeReport,
@@ -285,6 +286,19 @@ const {
285
286
  color,
286
287
  });
287
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
+
288
302
  const {
289
303
  renderMcpDoctorTerminal,
290
304
  runMcpDoctor,
@@ -300,13 +314,14 @@ Usage:
300
314
  prismo init [--json] [--dry-run] [path]
301
315
  prismo doctor [--json] [--dry-run] [--apply-ignores-only] [--apply-suggestions] [--no-context-packs] [--limit N] [path]
302
316
  prismo firewall [task] [--json] [--dry-run] [path]
317
+ prismo benchmark [session] [--json] [--limit N] [path] [-- <command ...>]
303
318
  prismo shield [--json] [path] -- <command ...>
304
319
  prismo shield last [--json] [--limit N] [path]
305
320
  prismo shield search <query> [--json] [--limit N] [path]
306
321
  prismo mcp [path]
307
322
  prismo mcp doctor [--json] [path]
308
323
  prismo setup [--json] [--proxy-url URL] [path]
309
- prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--simple] [--no-report] [path]
324
+ prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
310
325
  prismo optimize [scope] [--json] [path]
311
326
  prismo context [scope] [--json] [path]
312
327
  prismo cc [list|last N|all] [--json] [--limit N] [path]
@@ -319,6 +334,7 @@ Commands:
319
334
  init Add local PrismoDev helper docs and npm scripts when package.json exists.
320
335
  doctor Diagnose, safely optimize, re-scan, and show before/after payoff.
321
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.
322
338
  shield Run a noisy command, store full output locally, and return a compact summary.
323
339
  mcp Start a local MCP server exposing Prismo tools over stdio.
324
340
  scan Run PrismoDev for Claude Code, Codex, Cursor, and AI coding workflows.
@@ -336,6 +352,7 @@ Options:
336
352
  --json Output valid JSON only for CI or future dashboard ingestion.
337
353
  --usage Include real local Codex/Claude Code session usage in scan diagnostics.
338
354
  --optimizer-fit Recommend the right optimization path for this repo/session.
355
+ --report-card Print a short plain-English optimization report card.
339
356
  --simple Print a plain-English scan summary for first-time or non-technical users.
340
357
  --no-report Do not write .prismo/prismo-dev-report.md.
341
358
  --limit N Number of recent local sessions to show.
@@ -375,12 +392,13 @@ function printCommandHelp(command) {
375
392
  scan: `PrismoDev
376
393
 
377
394
  Usage:
378
- prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--simple] [--no-report] [--limit N] [path]
395
+ prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [--limit N] [path]
379
396
 
380
397
  Examples:
381
398
  prismo scan
382
399
  prismo scan --usage
383
400
  prismo scan --optimizer-fit
401
+ prismo scan --report-card
384
402
  prismo scan --simple
385
403
  prismo scan --fix
386
404
  prismo scan --ci
@@ -390,6 +408,7 @@ Examples:
390
408
  Notes:
391
409
  --usage reads local Codex/Claude Code logs when present.
392
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.
393
412
  --simple keeps the output short and does not write a report unless combined with --fix.
394
413
  --fix creates safe recommendation files and never overwrites CLAUDE.md or AGENTS.md.`,
395
414
  optimize: `Prismo Optimize
@@ -608,8 +627,8 @@ async function runCli(argv) {
608
627
  printCommandHelp(command);
609
628
  return;
610
629
  }
611
- if (!["dev", "init", "doctor", "firewall", "shield", "mcp", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
612
- 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`);
613
632
  }
614
633
 
615
634
  if (command === "demo") {
@@ -692,6 +711,25 @@ async function runCli(argv) {
692
711
  return;
693
712
  }
694
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
+
695
733
  if (command === "shield") {
696
734
  const json = rest.includes("--json");
697
735
  const limitIndex = rest.indexOf("--limit");
@@ -926,12 +964,13 @@ async function runCli(argv) {
926
964
  const json = rest.includes("--json");
927
965
  const simple = rest.includes("--simple");
928
966
  const optimizerFit = rest.includes("--optimizer-fit");
967
+ const reportCard = rest.includes("--report-card");
929
968
  const ciMode = rest.includes("--ci");
930
- const includeUsage = rest.includes("--usage") || optimizerFit;
969
+ const includeUsage = rest.includes("--usage") || optimizerFit || reportCard;
931
970
  const limitIndex = rest.indexOf("--limit");
932
971
  const usageToolIndex = rest.indexOf("--usage-tool");
933
972
  const target = getPositionals(rest, new Set(["--limit", "--usage-tool"]))[0] || process.cwd();
934
- const scanDone = printStep(includeUsage ? "Scanning repo and local usage" : "Scanning repo", json || simple || optimizerFit);
973
+ const scanDone = printStep(includeUsage ? "Scanning repo and local usage" : "Scanning repo", json || simple || optimizerFit || reportCard);
935
974
  const result = scanRepo(target, {
936
975
  includeUsage,
937
976
  usageLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
@@ -952,13 +991,19 @@ async function runCli(argv) {
952
991
  payload.ci = evaluateCi(result);
953
992
  if (!payload.ci.passed) process.exitCode = 1;
954
993
  }
955
- if (optimizerFit) {
994
+ if (optimizerFit || reportCard) {
956
995
  console.log(JSON.stringify({
957
996
  schemaVersion: 1,
958
997
  scannedPath: result.root,
959
998
  score: result.score,
960
999
  riskLevel: result.risk,
961
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,
962
1007
  generatedAt: result.generatedAt,
963
1008
  }, null, 2));
964
1009
  return;
@@ -969,7 +1014,9 @@ async function runCli(argv) {
969
1014
  return;
970
1015
  }
971
1016
 
972
- if (optimizerFit) {
1017
+ if (reportCard) {
1018
+ console.log(renderReportCardTerminal(result));
1019
+ } else if (optimizerFit) {
973
1020
  console.log(renderOptimizerFitTerminal(result));
974
1021
  } else if (simple) {
975
1022
  console.log(renderSimpleScanReport(result));
@@ -985,7 +1032,7 @@ async function runCli(argv) {
985
1032
  const actions = applyFixes(result);
986
1033
  console.log("\nFix Mode:");
987
1034
  actions.forEach((action) => console.log(`- ${action}`));
988
- } else if (!noReport && !simple && !optimizerFit) {
1035
+ } else if (!noReport && !simple && !optimizerFit && !reportCard) {
989
1036
  const report = writeReport(result);
990
1037
  if (report.backupPath) {
991
1038
  console.log(`\nExisting report backed up to ${path.basename(report.backupPath)}.`);
@@ -1006,10 +1053,13 @@ module.exports = {
1006
1053
  renderWatchTerminal,
1007
1054
  renderWatchReport,
1008
1055
  renderTerminalReport,
1056
+ renderOptimizerFitTerminal,
1057
+ renderReportCardTerminal,
1009
1058
  renderDoctorTerminal,
1010
1059
  renderInitTerminal,
1011
1060
  runSetup,
1012
1061
  runOptimize,
1062
+ runBenchmark,
1013
1063
  runDoctor,
1014
1064
  runInit,
1015
1065
  runCli,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
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",