@peakinfer/cli 1.0.133
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/.claude/settings.local.json +8 -0
- package/.env.example +6 -0
- package/.github/workflows/peakinfer.yml +64 -0
- package/CHANGELOG.md +31 -0
- package/LICENSE +190 -0
- package/README.md +335 -0
- package/data/inferencemax.json +274 -0
- package/dist/agent-analyzer.d.ts +45 -0
- package/dist/agent-analyzer.d.ts.map +1 -0
- package/dist/agent-analyzer.js +374 -0
- package/dist/agent-analyzer.js.map +1 -0
- package/dist/agent.d.ts +76 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +965 -0
- package/dist/agent.js.map +1 -0
- package/dist/agents/correlation-analyzer.d.ts +34 -0
- package/dist/agents/correlation-analyzer.d.ts.map +1 -0
- package/dist/agents/correlation-analyzer.js +261 -0
- package/dist/agents/correlation-analyzer.js.map +1 -0
- package/dist/agents/index.d.ts +91 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +111 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/runtime-analyzer.d.ts +38 -0
- package/dist/agents/runtime-analyzer.d.ts.map +1 -0
- package/dist/agents/runtime-analyzer.js +244 -0
- package/dist/agents/runtime-analyzer.js.map +1 -0
- package/dist/analysis-types.d.ts +500 -0
- package/dist/analysis-types.d.ts.map +1 -0
- package/dist/analysis-types.js +11 -0
- package/dist/analysis-types.js.map +1 -0
- package/dist/analytics.d.ts +25 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +94 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analyzer.d.ts +48 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +547 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/artifacts.d.ts +44 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +165 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/benchmarks/index.d.ts +88 -0
- package/dist/benchmarks/index.d.ts.map +1 -0
- package/dist/benchmarks/index.js +205 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ci.d.ts +19 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +253 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +249 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/demo.d.ts +15 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +106 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/export.d.ts +14 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +209 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/history.d.ts +15 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +389 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/template.d.ts +14 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +341 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/validate-map.d.ts +12 -0
- package/dist/commands/validate-map.d.ts.map +1 -0
- package/dist/commands/validate-map.js +274 -0
- package/dist/commands/validate-map.js.map +1 -0
- package/dist/commands/whatif.d.ts +17 -0
- package/dist/commands/whatif.d.ts.map +1 -0
- package/dist/commands/whatif.js +206 -0
- package/dist/commands/whatif.js.map +1 -0
- package/dist/comparison.d.ts +38 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +223 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/connectors/helicone.d.ts +9 -0
- package/dist/connectors/helicone.d.ts.map +1 -0
- package/dist/connectors/helicone.js +106 -0
- package/dist/connectors/helicone.js.map +1 -0
- package/dist/connectors/index.d.ts +37 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +65 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/langsmith.d.ts +9 -0
- package/dist/connectors/langsmith.d.ts.map +1 -0
- package/dist/connectors/langsmith.js +122 -0
- package/dist/connectors/langsmith.js.map +1 -0
- package/dist/connectors/types.d.ts +83 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +98 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/cost-estimator.d.ts +46 -0
- package/dist/cost-estimator.d.ts.map +1 -0
- package/dist/cost-estimator.js +104 -0
- package/dist/cost-estimator.js.map +1 -0
- package/dist/costs.d.ts +57 -0
- package/dist/costs.d.ts.map +1 -0
- package/dist/costs.js +251 -0
- package/dist/costs.js.map +1 -0
- package/dist/counterfactuals.d.ts +29 -0
- package/dist/counterfactuals.d.ts.map +1 -0
- package/dist/counterfactuals.js +448 -0
- package/dist/counterfactuals.js.map +1 -0
- package/dist/enhancement-prompts.d.ts +41 -0
- package/dist/enhancement-prompts.d.ts.map +1 -0
- package/dist/enhancement-prompts.js +88 -0
- package/dist/enhancement-prompts.js.map +1 -0
- package/dist/envelopes.d.ts +20 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/envelopes.js +790 -0
- package/dist/envelopes.js.map +1 -0
- package/dist/format-normalizer.d.ts +71 -0
- package/dist/format-normalizer.d.ts.map +1 -0
- package/dist/format-normalizer.js +1331 -0
- package/dist/format-normalizer.js.map +1 -0
- package/dist/history.d.ts +79 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +313 -0
- package/dist/history.js.map +1 -0
- package/dist/html.d.ts +11 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +463 -0
- package/dist/html.js.map +1 -0
- package/dist/impact.d.ts +42 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +443 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +5 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +271 -0
- package/dist/insights.js.map +1 -0
- package/dist/joiner.d.ts +9 -0
- package/dist/joiner.d.ts.map +1 -0
- package/dist/joiner.js +247 -0
- package/dist/joiner.js.map +1 -0
- package/dist/orchestrator.d.ts +34 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +827 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pdf.d.ts +26 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +84 -0
- package/dist/pdf.js.map +1 -0
- package/dist/prediction.d.ts +33 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/prediction.js +316 -0
- package/dist/prediction.js.map +1 -0
- package/dist/prompts/loader.d.ts +38 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +60 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/renderer.d.ts +64 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +923 -0
- package/dist/renderer.js.map +1 -0
- package/dist/runid.d.ts +57 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +199 -0
- package/dist/runid.js.map +1 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +366 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +426 -0
- package/dist/scanner.js.map +1 -0
- package/dist/templates.d.ts +120 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +429 -0
- package/dist/templates.js.map +1 -0
- package/dist/tools/index.d.ts +153 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +177 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +3647 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +703 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/docs/demo-guide.md +423 -0
- package/docs/events-format.md +295 -0
- package/docs/inferencemap-spec.md +344 -0
- package/docs/migration-v2.md +293 -0
- package/fixtures/demo/precomputed.json +142 -0
- package/fixtures/demo-project/README.md +52 -0
- package/fixtures/demo-project/ai-service.ts +65 -0
- package/fixtures/demo-project/sample-events.jsonl +15 -0
- package/fixtures/demo-project/src/ai-service.ts +128 -0
- package/fixtures/demo-project/src/llm-client.ts +155 -0
- package/package.json +65 -0
- package/prompts/agent-analyzer.yaml +47 -0
- package/prompts/ci-gate.yaml +98 -0
- package/prompts/correlation-analyzer.yaml +178 -0
- package/prompts/format-normalizer.yaml +46 -0
- package/prompts/peak-performance.yaml +180 -0
- package/prompts/pr-comment.yaml +111 -0
- package/prompts/runtime-analyzer.yaml +189 -0
- package/prompts/unified-analyzer.yaml +241 -0
- package/schemas/inference-map.v0.1.json +215 -0
- package/scripts/benchmark.ts +394 -0
- package/scripts/demo-v1.5.sh +158 -0
- package/scripts/sync-from-site.sh +197 -0
- package/scripts/validate-sync.sh +178 -0
- package/src/agent-analyzer.ts +481 -0
- package/src/agent.ts +1232 -0
- package/src/agents/correlation-analyzer.ts +353 -0
- package/src/agents/index.ts +235 -0
- package/src/agents/runtime-analyzer.ts +343 -0
- package/src/analysis-types.ts +558 -0
- package/src/analytics.ts +100 -0
- package/src/analyzer.ts +692 -0
- package/src/artifacts.ts +218 -0
- package/src/benchmarks/index.ts +309 -0
- package/src/cli.ts +503 -0
- package/src/commands/ci.ts +336 -0
- package/src/commands/config.ts +288 -0
- package/src/commands/demo.ts +175 -0
- package/src/commands/export.ts +297 -0
- package/src/commands/history.ts +425 -0
- package/src/commands/template.ts +385 -0
- package/src/commands/validate-map.ts +324 -0
- package/src/commands/whatif.ts +272 -0
- package/src/comparison.ts +283 -0
- package/src/config.ts +188 -0
- package/src/connectors/helicone.ts +164 -0
- package/src/connectors/index.ts +93 -0
- package/src/connectors/langsmith.ts +179 -0
- package/src/connectors/types.ts +180 -0
- package/src/cost-estimator.ts +146 -0
- package/src/costs.ts +347 -0
- package/src/counterfactuals.ts +516 -0
- package/src/enhancement-prompts.ts +118 -0
- package/src/envelopes.ts +814 -0
- package/src/format-normalizer.ts +1486 -0
- package/src/history.ts +400 -0
- package/src/html.ts +512 -0
- package/src/impact.ts +522 -0
- package/src/index.ts +83 -0
- package/src/insights.ts +341 -0
- package/src/joiner.ts +289 -0
- package/src/orchestrator.ts +1015 -0
- package/src/pdf.ts +110 -0
- package/src/prediction.ts +392 -0
- package/src/prompts/loader.ts +88 -0
- package/src/renderer.ts +1045 -0
- package/src/runid.ts +261 -0
- package/src/runtime.ts +450 -0
- package/src/scanner.ts +508 -0
- package/src/templates.ts +561 -0
- package/src/tools/index.ts +214 -0
- package/src/types.ts +873 -0
- package/src/version.ts +24 -0
- package/templates/context-accumulation.yaml +23 -0
- package/templates/cost-concentration.yaml +20 -0
- package/templates/dead-code.yaml +20 -0
- package/templates/latency-explainer.yaml +23 -0
- package/templates/optimizations/ab-testing-framework.yaml +74 -0
- package/templates/optimizations/api-gateway-optimization.yaml +81 -0
- package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
- package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
- package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
- package/templates/optimizations/comprehensive-apm.yaml +76 -0
- package/templates/optimizations/context-window-optimization.yaml +91 -0
- package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
- package/templates/optimizations/distributed-training-optimization.yaml +77 -0
- package/templates/optimizations/document-analysis-edge.yaml +77 -0
- package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
- package/templates/optimizations/domain-specific-distillation.yaml +78 -0
- package/templates/optimizations/error-handling-optimization.yaml +76 -0
- package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
- package/templates/optimizations/long-context-memory-management.yaml +78 -0
- package/templates/optimizations/max-tokens-optimization.yaml +76 -0
- package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
- package/templates/optimizations/multi-framework-resilience.yaml +75 -0
- package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
- package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
- package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
- package/templates/optimizations/quality-monitoring.yaml +74 -0
- package/templates/optimizations/realtime-budget-controls.yaml +74 -0
- package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
- package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
- package/templates/optimizations/smart-model-routing.yaml +96 -0
- package/templates/optimizations/streaming-batch-selection.yaml +167 -0
- package/templates/optimizations/system-prompt-optimization.yaml +75 -0
- package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
- package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
- package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
- package/templates/overpowered-extraction.yaml +32 -0
- package/templates/overpowered-model.yaml +31 -0
- package/templates/prompt-bloat.yaml +24 -0
- package/templates/retry-explosion.yaml +28 -0
- package/templates/schema/insight.schema.json +113 -0
- package/templates/schema/optimization.schema.json +180 -0
- package/templates/streaming-drift.yaml +30 -0
- package/templates/throughput-gap.yaml +21 -0
- package/templates/token-underutilization.yaml +28 -0
- package/templates/untested-fallback.yaml +21 -0
- package/tests/accuracy/drift-detection.test.ts +184 -0
- package/tests/accuracy/false-positives.test.ts +166 -0
- package/tests/accuracy/templates.test.ts +205 -0
- package/tests/action/commands.test.ts +125 -0
- package/tests/action/comments.test.ts +347 -0
- package/tests/cli.test.ts +203 -0
- package/tests/comparison.test.ts +309 -0
- package/tests/correlation-analyzer.test.ts +534 -0
- package/tests/counterfactuals.test.ts +347 -0
- package/tests/fixtures/events/missing-id.jsonl +1 -0
- package/tests/fixtures/events/missing-input.jsonl +1 -0
- package/tests/fixtures/events/missing-latency.jsonl +1 -0
- package/tests/fixtures/events/missing-model.jsonl +1 -0
- package/tests/fixtures/events/missing-output.jsonl +1 -0
- package/tests/fixtures/events/missing-provider.jsonl +1 -0
- package/tests/fixtures/events/missing-ts.jsonl +1 -0
- package/tests/fixtures/events/valid.csv +3 -0
- package/tests/fixtures/events/valid.json +1 -0
- package/tests/fixtures/events/valid.jsonl +2 -0
- package/tests/fixtures/events/with-callsite.jsonl +1 -0
- package/tests/fixtures/events/with-intent.jsonl +1 -0
- package/tests/fixtures/events/wrong-type.jsonl +1 -0
- package/tests/fixtures/repos/empty/.gitkeep +0 -0
- package/tests/fixtures/repos/hybrid-router/router.py +35 -0
- package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
- package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
- package/tests/fixtures/repos/saas-openai/client.py +26 -0
- package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
- package/tests/github-action.test.ts +292 -0
- package/tests/insights.test.ts +878 -0
- package/tests/joiner.test.ts +168 -0
- package/tests/performance/action-latency.test.ts +132 -0
- package/tests/performance/benchmark.test.ts +189 -0
- package/tests/performance/cli-latency.test.ts +102 -0
- package/tests/pr-comment.test.ts +313 -0
- package/tests/prediction.test.ts +296 -0
- package/tests/runtime-analyzer.test.ts +375 -0
- package/tests/runtime.test.ts +205 -0
- package/tests/scanner.test.ts +122 -0
- package/tests/template-conformance.test.ts +526 -0
- package/tests/unit/cost-calculator.test.ts +303 -0
- package/tests/unit/credits.test.ts +180 -0
- package/tests/unit/inference-map.test.ts +276 -0
- package/tests/unit/schema.test.ts +300 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
import { VERSION_DISPLAY } from './version.js';
|
|
2
|
+
import { formatImpactSummary } from './impact.js';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// CONSTANTS
|
|
7
|
+
// =============================================================================
|
|
8
|
+
const VERSION = VERSION_DISPLAY;
|
|
9
|
+
const COLORS = {
|
|
10
|
+
critical: '#991b1b',
|
|
11
|
+
warning: '#b45309',
|
|
12
|
+
success: '#2d6a4f',
|
|
13
|
+
info: '#8b949e',
|
|
14
|
+
neutral: '#6b7280',
|
|
15
|
+
border: '#30363d',
|
|
16
|
+
};
|
|
17
|
+
// Severity markers (no emojis)
|
|
18
|
+
const SEVERITY_MARKER = {
|
|
19
|
+
critical: '[!]',
|
|
20
|
+
warning: '[*]',
|
|
21
|
+
info: '[-]',
|
|
22
|
+
};
|
|
23
|
+
// Julie Zhou State Labels
|
|
24
|
+
const STATE = {
|
|
25
|
+
ZERO: 'zero',
|
|
26
|
+
LOADING: 'loading',
|
|
27
|
+
PARTIAL: 'partial',
|
|
28
|
+
ERROR: 'error',
|
|
29
|
+
SUCCESS: 'success',
|
|
30
|
+
RESUMED: 'resumed',
|
|
31
|
+
};
|
|
32
|
+
// Progress phases - Julie Zhou aligned (DD Section 6.4)
|
|
33
|
+
// "Progress should be phase-based (not noisy per-file spam)"
|
|
34
|
+
// "Use stable phase names across runs"
|
|
35
|
+
// Lowercase, calm copy per peakinfer design
|
|
36
|
+
const PHASE = {
|
|
37
|
+
SCANNING: 'scanning files',
|
|
38
|
+
ANALYZING: 'analyzing codebase',
|
|
39
|
+
PROFILING: 'profiling performance',
|
|
40
|
+
PARSING: 'parsing events',
|
|
41
|
+
CORRELATING: 'correlating code + runtime',
|
|
42
|
+
GENERATING: 'generating insights',
|
|
43
|
+
};
|
|
44
|
+
// Progress bar characters (intuitive visual feedback)
|
|
45
|
+
const BAR_FILLED = '█';
|
|
46
|
+
const BAR_EMPTY = '░';
|
|
47
|
+
const BAR_WIDTH = 10;
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// HELPERS
|
|
50
|
+
// =============================================================================
|
|
51
|
+
function formatNumber(n) {
|
|
52
|
+
if (n >= 1_000_000)
|
|
53
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
54
|
+
if (n >= 1_000)
|
|
55
|
+
return `${(n / 1_000).toFixed(1)}K`;
|
|
56
|
+
return n.toLocaleString();
|
|
57
|
+
}
|
|
58
|
+
function dim(text) {
|
|
59
|
+
return `\x1b[2m${text}\x1b[0m`;
|
|
60
|
+
}
|
|
61
|
+
function bold(text) {
|
|
62
|
+
return `\x1b[1m${text}\x1b[0m`;
|
|
63
|
+
}
|
|
64
|
+
function green(text) {
|
|
65
|
+
return chalk.hex(COLORS.success)(text);
|
|
66
|
+
}
|
|
67
|
+
function red(text) {
|
|
68
|
+
return chalk.hex(COLORS.critical)(text);
|
|
69
|
+
}
|
|
70
|
+
function yellow(text) {
|
|
71
|
+
return chalk.hex(COLORS.warning)(text);
|
|
72
|
+
}
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// COMPARISON RENDERER (v1.5)
|
|
75
|
+
// =============================================================================
|
|
76
|
+
/**
|
|
77
|
+
* Render historical comparison results.
|
|
78
|
+
* Shows what changed prominently at the top.
|
|
79
|
+
*/
|
|
80
|
+
function renderComparison(comparison) {
|
|
81
|
+
const { metrics, insightDeltas } = comparison;
|
|
82
|
+
// Header with date context
|
|
83
|
+
const baseDate = new Date(comparison.baseTimestamp).toLocaleDateString();
|
|
84
|
+
console.log(bold('Changes since last run') + dim(` (${baseDate})`));
|
|
85
|
+
console.log('');
|
|
86
|
+
// Summary line
|
|
87
|
+
const delta = metrics.netChange;
|
|
88
|
+
const deltaStr = delta > 0 ? green(`+${delta}`) : delta < 0 ? red(`${delta}`) : dim('0');
|
|
89
|
+
console.log(` Inference points: ${metrics.totalBefore} → ${metrics.totalAfter} (${deltaStr})`);
|
|
90
|
+
console.log('');
|
|
91
|
+
// Show changes if any
|
|
92
|
+
const hasChanges = metrics.addedCount > 0 || metrics.removedCount > 0 || metrics.changedCount > 0;
|
|
93
|
+
if (hasChanges) {
|
|
94
|
+
if (metrics.addedCount > 0) {
|
|
95
|
+
console.log(` ${green('+')} ${metrics.addedCount} new inference point${metrics.addedCount !== 1 ? 's' : ''}`);
|
|
96
|
+
// Show first few added locations
|
|
97
|
+
for (const added of comparison.added.slice(0, 3)) {
|
|
98
|
+
console.log(` ${dim(added.file + ':' + added.line)}`);
|
|
99
|
+
}
|
|
100
|
+
if (comparison.added.length > 3) {
|
|
101
|
+
console.log(` ${dim(`... and ${comparison.added.length - 3} more`)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (metrics.removedCount > 0) {
|
|
105
|
+
console.log(` ${red('-')} ${metrics.removedCount} removed inference point${metrics.removedCount !== 1 ? 's' : ''}`);
|
|
106
|
+
for (const removed of comparison.removed.slice(0, 3)) {
|
|
107
|
+
console.log(` ${dim(removed.file + ':' + removed.line)}`);
|
|
108
|
+
}
|
|
109
|
+
if (comparison.removed.length > 3) {
|
|
110
|
+
console.log(` ${dim(`... and ${comparison.removed.length - 3} more`)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (metrics.changedCount > 0) {
|
|
114
|
+
console.log(` ${yellow('~')} ${metrics.changedCount} modified inference point${metrics.changedCount !== 1 ? 's' : ''}`);
|
|
115
|
+
for (const changed of comparison.changed.slice(0, 3)) {
|
|
116
|
+
const changeDesc = changed.changes.map(c => c.field).join(', ');
|
|
117
|
+
console.log(` ${dim(changed.point.file + ':' + changed.point.line)} ${dim('(' + changeDesc + ')')}`);
|
|
118
|
+
}
|
|
119
|
+
if (comparison.changed.length > 3) {
|
|
120
|
+
console.log(` ${dim(`... and ${comparison.changed.length - 3} more`)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log('');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(` ${dim('No changes detected')}`);
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
// Insight deltas (if available)
|
|
130
|
+
if (insightDeltas) {
|
|
131
|
+
const hasInsightChanges = insightDeltas.newCritical > 0 ||
|
|
132
|
+
insightDeltas.resolvedCritical > 0 ||
|
|
133
|
+
insightDeltas.newWarnings > 0 ||
|
|
134
|
+
insightDeltas.resolvedWarnings > 0;
|
|
135
|
+
if (hasInsightChanges) {
|
|
136
|
+
console.log(dim('Issue changes'));
|
|
137
|
+
if (insightDeltas.newCritical > 0) {
|
|
138
|
+
console.log(` ${red('[!]')} ${insightDeltas.newCritical} new critical issue${insightDeltas.newCritical !== 1 ? 's' : ''}`);
|
|
139
|
+
}
|
|
140
|
+
if (insightDeltas.resolvedCritical > 0) {
|
|
141
|
+
console.log(` ${green('[OK]')} ${insightDeltas.resolvedCritical} critical issue${insightDeltas.resolvedCritical !== 1 ? 's' : ''} resolved`);
|
|
142
|
+
}
|
|
143
|
+
if (insightDeltas.newWarnings > 0) {
|
|
144
|
+
console.log(` ${yellow('[*]')} ${insightDeltas.newWarnings} new warning${insightDeltas.newWarnings !== 1 ? 's' : ''}`);
|
|
145
|
+
}
|
|
146
|
+
if (insightDeltas.resolvedWarnings > 0) {
|
|
147
|
+
console.log(` ${green('[OK]')} ${insightDeltas.resolvedWarnings} warning${insightDeltas.resolvedWarnings !== 1 ? 's' : ''} resolved`);
|
|
148
|
+
}
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// PREDICTION RENDERER (v1.5)
|
|
155
|
+
// =============================================================================
|
|
156
|
+
/**
|
|
157
|
+
* Render deploy-time latency predictions.
|
|
158
|
+
* Surfaces potential performance risks before deployment.
|
|
159
|
+
*/
|
|
160
|
+
function renderPrediction(prediction) {
|
|
161
|
+
const { predictions, summary, targetP95 } = prediction;
|
|
162
|
+
// Header with deploy-time anxiety framing
|
|
163
|
+
console.log(bold('Deploy-time Prediction'));
|
|
164
|
+
console.log('');
|
|
165
|
+
// Summary line with risk counts
|
|
166
|
+
if (summary.highRiskCount > 0) {
|
|
167
|
+
console.log(` ${red('[!]')} ${summary.highRiskCount} high-risk inference point${summary.highRiskCount !== 1 ? 's' : ''} (p95 > 5000ms)`);
|
|
168
|
+
}
|
|
169
|
+
if (summary.mediumRiskCount > 0) {
|
|
170
|
+
console.log(` ${yellow('[*]')} ${summary.mediumRiskCount} medium-risk inference point${summary.mediumRiskCount !== 1 ? 's' : ''} (p95 > 2000ms)`);
|
|
171
|
+
}
|
|
172
|
+
if (summary.lowRiskCount > 0) {
|
|
173
|
+
console.log(` ${dim('[-]')} ${summary.lowRiskCount} low-risk inference point${summary.lowRiskCount !== 1 ? 's' : ''}`);
|
|
174
|
+
}
|
|
175
|
+
const neutralCount = summary.totalPoints - summary.highRiskCount - summary.mediumRiskCount - summary.lowRiskCount;
|
|
176
|
+
if (neutralCount > 0) {
|
|
177
|
+
console.log(` ${dim('[OK]')} ${neutralCount} within acceptable latency`);
|
|
178
|
+
}
|
|
179
|
+
console.log('');
|
|
180
|
+
// Show worst predictions
|
|
181
|
+
const sortedByRisk = [...predictions].sort((a, b) => b.riskScore - a.riskScore);
|
|
182
|
+
const riskyPredictions = sortedByRisk.filter(p => p.risk === 'high' || p.risk === 'medium').slice(0, 5);
|
|
183
|
+
if (riskyPredictions.length > 0) {
|
|
184
|
+
console.log(dim('Top latency risks'));
|
|
185
|
+
for (const pred of riskyPredictions) {
|
|
186
|
+
const riskMarker = pred.risk === 'high' ? red('[!]') : yellow('[*]');
|
|
187
|
+
const modelInfo = pred.model ? dim(` (${pred.model})`) : '';
|
|
188
|
+
console.log(` ${riskMarker} ${pred.location}${modelInfo}`);
|
|
189
|
+
console.log(` p95: ${pred.predictedLatency.p95}ms | p99: ${pred.predictedLatency.p99}ms`);
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
192
|
+
}
|
|
193
|
+
// Latency budget check
|
|
194
|
+
if (targetP95 !== undefined) {
|
|
195
|
+
if (summary.budgetExceeded) {
|
|
196
|
+
console.log(` ${red('[!]')} Budget exceeded: worst p95 ${summary.worstP95}ms > target ${targetP95}ms`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(` ${green('[OK]')} Within budget: worst p95 ${summary.worstP95}ms ≤ target ${targetP95}ms`);
|
|
200
|
+
}
|
|
201
|
+
console.log('');
|
|
202
|
+
}
|
|
203
|
+
// Overall stats
|
|
204
|
+
console.log(dim('Latency estimates'));
|
|
205
|
+
console.log(` Average p95: ${summary.averageP95}ms`);
|
|
206
|
+
console.log(` Worst p95: ${summary.worstP95}ms`);
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// COUNTERFACTUAL RENDERER (v1.5)
|
|
211
|
+
// =============================================================================
|
|
212
|
+
/**
|
|
213
|
+
* Render counterfactual "what if" optimization scenarios.
|
|
214
|
+
* Shows the road not taken and its potential impact.
|
|
215
|
+
*/
|
|
216
|
+
function renderCounterfactuals(counterfactuals) {
|
|
217
|
+
const { counterfactuals: items, summary } = counterfactuals;
|
|
218
|
+
if (summary.totalOpportunities === 0)
|
|
219
|
+
return;
|
|
220
|
+
console.log(bold('Optimization Opportunities'));
|
|
221
|
+
console.log('');
|
|
222
|
+
// Summary line
|
|
223
|
+
const savingsInfo = [];
|
|
224
|
+
if (summary.maxLatencySavingsPercent > 0) {
|
|
225
|
+
savingsInfo.push(`up to ${summary.maxLatencySavingsPercent}% latency reduction`);
|
|
226
|
+
}
|
|
227
|
+
if (summary.maxCostSavingsPercent > 0) {
|
|
228
|
+
savingsInfo.push(`up to ${summary.maxCostSavingsPercent}% cost savings`);
|
|
229
|
+
}
|
|
230
|
+
console.log(` ${summary.totalOpportunities} opportunities: ${savingsInfo.join(', ')}`);
|
|
231
|
+
console.log('');
|
|
232
|
+
// Show top opportunities (up to 5)
|
|
233
|
+
const topItems = items.slice(0, 5);
|
|
234
|
+
for (const cf of topItems) {
|
|
235
|
+
// Format impact
|
|
236
|
+
const impactParts = [];
|
|
237
|
+
if (cf.impact.latencyDeltaPercent < 0) {
|
|
238
|
+
impactParts.push(green(`${cf.impact.latencyDeltaPercent}% latency`));
|
|
239
|
+
}
|
|
240
|
+
if (cf.impact.costDeltaPercent < 0) {
|
|
241
|
+
impactParts.push(green(`${cf.impact.costDeltaPercent}% cost`));
|
|
242
|
+
}
|
|
243
|
+
const impactStr = impactParts.length > 0 ? impactParts.join(', ') : dim('neutral');
|
|
244
|
+
// Effort indicator
|
|
245
|
+
const effortMarker = cf.effort === 'low' ? dim('[easy]') :
|
|
246
|
+
cf.effort === 'medium' ? dim('[moderate]') :
|
|
247
|
+
dim('[complex]');
|
|
248
|
+
console.log(` ${bold(cf.headline)} ${effortMarker}`);
|
|
249
|
+
console.log(` Impact: ${impactStr}`);
|
|
250
|
+
// Show tradeoffs for first few
|
|
251
|
+
if (cf.impact.tradeoffs.length > 0 && topItems.indexOf(cf) < 3) {
|
|
252
|
+
console.log(` ${dim('Tradeoff: ' + cf.impact.tradeoffs[0])}`);
|
|
253
|
+
}
|
|
254
|
+
console.log('');
|
|
255
|
+
}
|
|
256
|
+
if (items.length > 5) {
|
|
257
|
+
console.log(dim(` ... and ${items.length - 5} more opportunities`));
|
|
258
|
+
console.log('');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// =============================================================================
|
|
262
|
+
// STATE RENDERERS
|
|
263
|
+
// =============================================================================
|
|
264
|
+
/**
|
|
265
|
+
* ZERO STATE: No inference usage detected
|
|
266
|
+
* Julie Zhou: calm, helpful, not alarming
|
|
267
|
+
*/
|
|
268
|
+
function renderZeroState() {
|
|
269
|
+
console.log('');
|
|
270
|
+
console.log('no inference usage detected.');
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(dim('checked for:'));
|
|
273
|
+
console.log(' common providers (openai, anthropic, google, together, fireworks...)');
|
|
274
|
+
console.log(' frameworks (langchain, llamaindex, dspy...)');
|
|
275
|
+
console.log(' self-hosted runtimes (vllm, sglang, ollama, tgi...)');
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log(dim('if you expected results:'));
|
|
278
|
+
console.log(' check wrapper modules or custom client abstractions');
|
|
279
|
+
console.log(' check dynamic imports or runtime configuration');
|
|
280
|
+
console.log('');
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* LOADING STATE: Show plan
|
|
284
|
+
* Julie Zhou: visible only in verbose mode, calm formatting
|
|
285
|
+
*/
|
|
286
|
+
function renderPlan(plan) {
|
|
287
|
+
console.log('');
|
|
288
|
+
console.log(dim('planning'));
|
|
289
|
+
for (const task of plan.tasks) {
|
|
290
|
+
console.log(` [${task.id}/${plan.tasks.length}] ${task.description.toLowerCase()}`);
|
|
291
|
+
}
|
|
292
|
+
console.log('');
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* PROGRESS STATE: Task started
|
|
296
|
+
*/
|
|
297
|
+
function renderTaskStart(task, totalTasks) {
|
|
298
|
+
process.stdout.write(` [${task.id}/${totalTasks}] ${task.description}...`);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* PROGRESS STATE: Task completed
|
|
302
|
+
*/
|
|
303
|
+
function renderTaskComplete(result) {
|
|
304
|
+
if (result.status === 'success') {
|
|
305
|
+
console.log(` ${dim(`(${result.durationMs}ms)`)}`);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
console.log(` ${dim('failed')}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* PARTIAL STATE: Some results with warnings
|
|
313
|
+
* Julie Zhou: calm, informative
|
|
314
|
+
*/
|
|
315
|
+
function renderPartialState(warnings) {
|
|
316
|
+
console.log(dim('partial results'));
|
|
317
|
+
console.log('');
|
|
318
|
+
for (const warning of warnings) {
|
|
319
|
+
console.log(` ${warning.toLowerCase()}`);
|
|
320
|
+
}
|
|
321
|
+
console.log('');
|
|
322
|
+
console.log('results are valid for analyzed files.');
|
|
323
|
+
console.log('');
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* RESUMED STATE: Using cached results from previous run
|
|
327
|
+
* Julie Zhou: calm, informative
|
|
328
|
+
*/
|
|
329
|
+
function renderResumed(runId) {
|
|
330
|
+
console.log(dim(`loading cached analysis... (run: ${runId})`));
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* DEMO SECTION: Show what drift detection reveals
|
|
335
|
+
* Per Magic Moment Implementation Spec (DD v1.8.2):
|
|
336
|
+
* - Shows after static analysis, before next steps
|
|
337
|
+
* - Creates curiosity about what they're missing
|
|
338
|
+
* - Ends with low-friction CTA to add runtime data
|
|
339
|
+
*/
|
|
340
|
+
function renderDemoSection(streamingCount) {
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log(bold('What Teams Discover') + dim(' (from 500+ codebases analyzed)'));
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log(' Most common finding? ' + red('Streaming is broken.'));
|
|
345
|
+
console.log('');
|
|
346
|
+
console.log(' ┌────────────────────────────────────────────────────────┐');
|
|
347
|
+
console.log(' │ ' + dim('REAL EXAMPLE (anonymized):') + ' │');
|
|
348
|
+
console.log(' │ │');
|
|
349
|
+
console.log(' │ ' + bold('Code:') + ' streaming: true │');
|
|
350
|
+
console.log(' │ ' + bold('Runtime:') + ' ' + red('0% actual streams') + ' │');
|
|
351
|
+
console.log(' │ │');
|
|
352
|
+
console.log(' │ ' + yellow('Result:') + ' Users waited 2.4s instead of 400ms │');
|
|
353
|
+
console.log(' │ ' + dim('for 23 days before anyone noticed.') + ' │');
|
|
354
|
+
console.log(' │ │');
|
|
355
|
+
console.log(' │ ' + red('Cost:') + ' ~$12,000 in user churn │');
|
|
356
|
+
console.log(' └────────────────────────────────────────────────────────┘');
|
|
357
|
+
console.log('');
|
|
358
|
+
if (streamingCount > 0) {
|
|
359
|
+
console.log(' ' + bold(`Your code has ${streamingCount} streaming declaration${streamingCount !== 1 ? 's' : ''}.`));
|
|
360
|
+
console.log(' ' + dim('Are they actually working?'));
|
|
361
|
+
}
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log(dim(' → Find out: ') + 'peakinfer analyze . --events your-logs.jsonl');
|
|
364
|
+
console.log(dim(' → Events format: ') + 'https://peakinfer.com/docs/events');
|
|
365
|
+
console.log('');
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* ERROR STATE: Actionable error message
|
|
369
|
+
* Julie Zhou: clear, helpful, not alarming
|
|
370
|
+
*/
|
|
371
|
+
function renderError(error, context) {
|
|
372
|
+
console.log('');
|
|
373
|
+
console.log(`error: ${error.message.toLowerCase()}`);
|
|
374
|
+
console.log('');
|
|
375
|
+
if (context) {
|
|
376
|
+
if (context.file)
|
|
377
|
+
console.log(` file: ${context.file}`);
|
|
378
|
+
if (context.line)
|
|
379
|
+
console.log(` line: ${context.line}`);
|
|
380
|
+
if (context.field)
|
|
381
|
+
console.log(` missing: ${context.field}`);
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* SUCCESS STATE: Full results
|
|
387
|
+
* v1.5 Output Order (decision-relevant first):
|
|
388
|
+
* 1. Historical Comparison (if --compare) - what changed since last run
|
|
389
|
+
* 2. Deploy-Time Prediction (if --predict) - latency risk before deploy
|
|
390
|
+
* 3. Counterfactual Insights - what-if optimization scenarios
|
|
391
|
+
* 4. Code-Runtime Drift (if combined) - code/runtime mismatch
|
|
392
|
+
* 5. BLUF Summary (headroom totals) - the bottom line
|
|
393
|
+
* 6. Headroom by layer + Quick Wins + Strategic
|
|
394
|
+
* 7. Scope (what was analyzed)
|
|
395
|
+
* 8. Performance Profile (if static)
|
|
396
|
+
* 9. Runtime (if events)
|
|
397
|
+
* 10. Run info
|
|
398
|
+
* 11. Findings (detailed evidence)
|
|
399
|
+
* 12. Saved artifacts + Next steps
|
|
400
|
+
*/
|
|
401
|
+
function renderSuccess(results, opts = {}) {
|
|
402
|
+
// Show warnings if partial state
|
|
403
|
+
if (results.warnings && results.warnings.length > 0) {
|
|
404
|
+
renderPartialState(results.warnings);
|
|
405
|
+
}
|
|
406
|
+
// v1.5: Show comparison first (what changed since last time?)
|
|
407
|
+
if (results.comparison) {
|
|
408
|
+
renderComparison(results.comparison);
|
|
409
|
+
}
|
|
410
|
+
// v1.5: Show prediction (deploy-time risk assessment)
|
|
411
|
+
if (results.prediction) {
|
|
412
|
+
renderPrediction(results.prediction);
|
|
413
|
+
}
|
|
414
|
+
// v1.5: Show counterfactuals (optimization opportunities)
|
|
415
|
+
if (results.counterfactuals) {
|
|
416
|
+
renderCounterfactuals(results.counterfactuals);
|
|
417
|
+
}
|
|
418
|
+
// v1.5: Drift detection early (code-runtime mismatch detection)
|
|
419
|
+
if (results.joined && results.joined.drift.length > 0) {
|
|
420
|
+
console.log(bold('Code-Runtime Drift'));
|
|
421
|
+
console.log('');
|
|
422
|
+
const codeOnly = results.joined.codeOnly.length;
|
|
423
|
+
const runtimeOnly = results.joined.runtimeOnly.length;
|
|
424
|
+
if (codeOnly > 0) {
|
|
425
|
+
console.log(` ${yellow('[*]')} ${codeOnly} inference point${codeOnly !== 1 ? 's' : ''} in code but not in runtime`);
|
|
426
|
+
console.log(dim(' (dead code? not yet deployed?)'));
|
|
427
|
+
}
|
|
428
|
+
if (runtimeOnly > 0) {
|
|
429
|
+
console.log(` ${red('[!]')} ${runtimeOnly} runtime event${runtimeOnly !== 1 ? 's' : ''} not mapped to code`);
|
|
430
|
+
console.log(dim(' (dynamic calls? wrapper functions?)'));
|
|
431
|
+
}
|
|
432
|
+
console.log('');
|
|
433
|
+
}
|
|
434
|
+
// 1. BLUF: One-liner with potential improvement
|
|
435
|
+
const callsiteCount = results.inferenceMap?.summary.totalCallsites || 0;
|
|
436
|
+
const findingCount = results.insights.length;
|
|
437
|
+
if (results.impactSummary) {
|
|
438
|
+
const { costReductionPercent, latencyReductionPercent, throughputGainPercent } = results.impactSummary.totalPotentialImpact;
|
|
439
|
+
const hasHeadroom = costReductionPercent > 0 || latencyReductionPercent > 0 || throughputGainPercent > 0;
|
|
440
|
+
if (hasHeadroom) {
|
|
441
|
+
const parts = [];
|
|
442
|
+
if (costReductionPercent > 0)
|
|
443
|
+
parts.push(`${bold(`-${costReductionPercent}%`)} cost`);
|
|
444
|
+
if (latencyReductionPercent > 0)
|
|
445
|
+
parts.push(`${bold(`-${latencyReductionPercent}%`)} latency`);
|
|
446
|
+
if (throughputGainPercent > 0)
|
|
447
|
+
parts.push(`${bold(`+${throughputGainPercent}%`)} throughput`);
|
|
448
|
+
console.log(`${bold('Potential Performance Improvement')} across ${callsiteCount} inference points`);
|
|
449
|
+
console.log(` ${parts.join(' | ')}`);
|
|
450
|
+
console.log('');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
console.log(`${bold(`${findingCount} findings`)} across ${callsiteCount} inference points`);
|
|
455
|
+
console.log('');
|
|
456
|
+
}
|
|
457
|
+
// 2. Headroom by layer + Quick Wins + Strategic
|
|
458
|
+
if (results.impactSummary) {
|
|
459
|
+
console.log(formatImpactSummary(results.impactSummary));
|
|
460
|
+
console.log('');
|
|
461
|
+
}
|
|
462
|
+
// 3. Scope (what was analyzed)
|
|
463
|
+
console.log(dim('Scope'));
|
|
464
|
+
if (results.inferenceMap) {
|
|
465
|
+
const map = results.inferenceMap;
|
|
466
|
+
console.log(` Inference Points: ${map.summary.totalCallsites}`);
|
|
467
|
+
// Filter out 'unknown' and empty values for cleaner output
|
|
468
|
+
const providers = map.summary.providers.filter(p => p && p !== 'unknown');
|
|
469
|
+
const models = map.summary.models.filter(m => m && m !== 'unknown' && !m.includes('DEFAULT'));
|
|
470
|
+
if (providers.length > 0) {
|
|
471
|
+
console.log(` Providers: ${providers.join(', ')}`);
|
|
472
|
+
}
|
|
473
|
+
if (models.length > 0) {
|
|
474
|
+
console.log(` Models: ${models.slice(0, 5).join(', ')}${models.length > 5 ? '...' : ''}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (results.joined) {
|
|
478
|
+
const matchedCount = results.joined.callsites.filter(c => 'usage' in c && c.usage).length;
|
|
479
|
+
console.log(` Matched: ${matchedCount} of ${results.joined.callsites.length} inference points`);
|
|
480
|
+
}
|
|
481
|
+
console.log('');
|
|
482
|
+
// 4. Performance Profile (if static analysis ran)
|
|
483
|
+
if (results.staticAnalysis) {
|
|
484
|
+
const sa = results.staticAnalysis;
|
|
485
|
+
console.log(dim('Performance Profile'));
|
|
486
|
+
console.log(` Cost: $${sa.summary.estimated_cost_per_1k_calls.toFixed(2)}/1K calls`);
|
|
487
|
+
if (sa.summary.cost_risk_high > 0) {
|
|
488
|
+
console.log(` ${sa.summary.cost_risk_high} high-risk inference points`);
|
|
489
|
+
}
|
|
490
|
+
console.log(` Latency: p95=${sa.summary.estimated_p95_ms}ms`);
|
|
491
|
+
if (sa.summary.blocking_calls > 0) {
|
|
492
|
+
console.log(` ${sa.summary.blocking_calls} blocking calls`);
|
|
493
|
+
}
|
|
494
|
+
console.log(` Throughput: ${sa.summary.has_rate_limiting} with rate limiting`);
|
|
495
|
+
if (sa.summary.scaling_bottlenecks > 0) {
|
|
496
|
+
console.log(` ${sa.summary.scaling_bottlenecks} scaling bottlenecks`);
|
|
497
|
+
}
|
|
498
|
+
console.log(` Reliability: ${sa.summary.overall_reliability}`);
|
|
499
|
+
if (sa.summary.anti_patterns_found > 0) {
|
|
500
|
+
console.log(` ${sa.summary.anti_patterns_found} anti-patterns found`);
|
|
501
|
+
}
|
|
502
|
+
console.log(` Optimizations: ${sa.summary.total_optimizations} (${sa.summary.critical_optimizations} critical)`);
|
|
503
|
+
console.log('');
|
|
504
|
+
}
|
|
505
|
+
// 5. Runtime summary (if events)
|
|
506
|
+
if (results.runtimeSummary) {
|
|
507
|
+
const rt = results.runtimeSummary;
|
|
508
|
+
console.log(dim('Runtime'));
|
|
509
|
+
console.log(` Events: ${formatNumber(rt.totalEvents)}`);
|
|
510
|
+
console.log(` Latency: p50=${rt.global.p50}ms p95=${rt.global.p95}ms p99=${rt.global.p99}ms`);
|
|
511
|
+
console.log('');
|
|
512
|
+
}
|
|
513
|
+
// 5. Run info
|
|
514
|
+
if (results.runId) {
|
|
515
|
+
console.log(dim('Run'));
|
|
516
|
+
console.log(` ID: ${results.runId}${results.resumed ? ' (cached)' : ''}`);
|
|
517
|
+
console.log('');
|
|
518
|
+
}
|
|
519
|
+
// 6. Findings (sorted by impact - highest first)
|
|
520
|
+
if (results.insights.length > 0) {
|
|
521
|
+
// Sort by impact percentage descending
|
|
522
|
+
const sortedInsights = [...results.insights].sort((a, b) => {
|
|
523
|
+
const impactA = a.impact?.estimatedImpactPercent || 0;
|
|
524
|
+
const impactB = b.impact?.estimatedImpactPercent || 0;
|
|
525
|
+
return impactB - impactA;
|
|
526
|
+
});
|
|
527
|
+
// Group findings by recommendation to avoid noisy repetition
|
|
528
|
+
// Julie Zhou: "Progress should be phase-based (not noisy per-file spam)"
|
|
529
|
+
const grouped = new Map();
|
|
530
|
+
for (const insight of sortedInsights) {
|
|
531
|
+
const recommendation = insight.impact?.assumptions || insight.headline;
|
|
532
|
+
if (!grouped.has(recommendation)) {
|
|
533
|
+
grouped.set(recommendation, {
|
|
534
|
+
recommendation,
|
|
535
|
+
severity: insight.severity,
|
|
536
|
+
layer: insight.impact?.layer || '',
|
|
537
|
+
impactType: insight.impact?.impactType || 'improvement',
|
|
538
|
+
impactPercent: insight.impact?.estimatedImpactPercent || 0,
|
|
539
|
+
locations: [],
|
|
540
|
+
fixes: [],
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (insight.location) {
|
|
544
|
+
grouped.get(recommendation).locations.push(insight.location);
|
|
545
|
+
}
|
|
546
|
+
// v1.8: Collect suggested fixes (access via type assertion since field is optional)
|
|
547
|
+
const suggestedFix = insight.suggestedFix;
|
|
548
|
+
if (suggestedFix && !grouped.get(recommendation).fixes.includes(suggestedFix)) {
|
|
549
|
+
grouped.get(recommendation).fixes.push(suggestedFix);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Sort by impact
|
|
553
|
+
const sortedGroups = Array.from(grouped.values()).sort((a, b) => b.impactPercent - a.impactPercent);
|
|
554
|
+
console.log(dim('Findings'));
|
|
555
|
+
for (const group of sortedGroups) {
|
|
556
|
+
const marker = SEVERITY_MARKER[group.severity] || '[-]';
|
|
557
|
+
const typeLabel = group.impactType === 'cost' ? 'cost reduction'
|
|
558
|
+
: group.impactType === 'latency' ? 'latency reduction'
|
|
559
|
+
: group.impactType;
|
|
560
|
+
const impactTag = group.layer
|
|
561
|
+
? ` ${dim(`[${group.layer}] ${group.impactPercent}% ${typeLabel}`)}`
|
|
562
|
+
: '';
|
|
563
|
+
const count = group.locations.length;
|
|
564
|
+
console.log(` ${marker} ${group.recommendation}${impactTag}`);
|
|
565
|
+
console.log(` ${dim(`${count} inference point${count !== 1 ? 's' : ''}`)}`);
|
|
566
|
+
// v1.8: Show fix suggestions when --fixes flag is used
|
|
567
|
+
if (opts.showFixes && group.fixes.length > 0) {
|
|
568
|
+
const fix = group.fixes[0]; // Show first unique fix
|
|
569
|
+
console.log(` ${dim('Fix:')} ${fix}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
console.log('');
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
console.log(dim('Findings'));
|
|
576
|
+
console.log(' No issues detected. Your inference setup looks good.');
|
|
577
|
+
console.log('');
|
|
578
|
+
}
|
|
579
|
+
// 6.5 Demo section - show when no runtime data (Magic Moment Implementation Spec)
|
|
580
|
+
// Creates curiosity: "Is MY streaming broken like this example?"
|
|
581
|
+
if (!results.runtimeSummary && results.inferenceMap) {
|
|
582
|
+
// Count streaming declarations in the codebase
|
|
583
|
+
const streamingCount = results.inferenceMap.callsites?.filter((c) => {
|
|
584
|
+
const callsite = c;
|
|
585
|
+
return callsite.parameters?.['stream'] === true ||
|
|
586
|
+
callsite.parameters?.['streaming'] === true;
|
|
587
|
+
}).length || 0;
|
|
588
|
+
renderDemoSection(streamingCount);
|
|
589
|
+
}
|
|
590
|
+
// 7. Saved artifacts + Next steps
|
|
591
|
+
console.log(dim('Saved'));
|
|
592
|
+
console.log(' .peakinfer/inferencemap.json');
|
|
593
|
+
console.log(' .peakinfer/insights.json');
|
|
594
|
+
if (results.joined) {
|
|
595
|
+
console.log(' .peakinfer/joined.json');
|
|
596
|
+
}
|
|
597
|
+
if (results.runtimeSummary) {
|
|
598
|
+
console.log(' .peakinfer/runtime.json');
|
|
599
|
+
}
|
|
600
|
+
if (results.htmlPath) {
|
|
601
|
+
console.log(` ${results.htmlPath}`);
|
|
602
|
+
}
|
|
603
|
+
if (results.pdfPath) {
|
|
604
|
+
console.log(` ${results.pdfPath}`);
|
|
605
|
+
}
|
|
606
|
+
console.log('');
|
|
607
|
+
// Next steps
|
|
608
|
+
console.log(dim('Next'));
|
|
609
|
+
// Prefer PDF in "open" suggestion if available
|
|
610
|
+
if (results.pdfPath) {
|
|
611
|
+
console.log(` open ${results.pdfPath}`);
|
|
612
|
+
}
|
|
613
|
+
else if (results.htmlPath) {
|
|
614
|
+
console.log(` open ${results.htmlPath}`);
|
|
615
|
+
}
|
|
616
|
+
if (!results.runtimeSummary && results.inferenceMap) {
|
|
617
|
+
console.log(` peakinfer . --events <logs.jsonl> (compare code vs runtime)`);
|
|
618
|
+
}
|
|
619
|
+
console.log('');
|
|
620
|
+
}
|
|
621
|
+
// =============================================================================
|
|
622
|
+
// COST ESTIMATE RENDERER
|
|
623
|
+
// =============================================================================
|
|
624
|
+
/**
|
|
625
|
+
* Render cost estimate before analysis.
|
|
626
|
+
* PRD v1.9.3 Section 2.3: Cost Estimation (Pre-Analysis Transparency)
|
|
627
|
+
*/
|
|
628
|
+
export function renderCostEstimate(estimate) {
|
|
629
|
+
const LINE = '─'.repeat(41);
|
|
630
|
+
console.log('');
|
|
631
|
+
console.log(bold('Cost Estimate'));
|
|
632
|
+
console.log(LINE);
|
|
633
|
+
// Model and file count
|
|
634
|
+
console.log(`Model: ${estimate.model}`);
|
|
635
|
+
console.log(`Files to scan: ${formatNumber(estimate.filesToScan)}`);
|
|
636
|
+
// Token estimates
|
|
637
|
+
const inputStr = `~${formatNumber(estimate.estimatedInputTokens)} input`;
|
|
638
|
+
const outputStr = `~${formatNumber(estimate.estimatedOutputTokens)} output`;
|
|
639
|
+
console.log(`Est. tokens: ${inputStr}, ${outputStr}`);
|
|
640
|
+
console.log('');
|
|
641
|
+
// Pricing breakdown
|
|
642
|
+
const sourceLabel = estimate.pricing.source === 'litellm' ? 'LiteLLM' : 'fallback';
|
|
643
|
+
console.log(`Pricing (${sourceLabel}):`);
|
|
644
|
+
console.log(` Input: $${estimate.inputCost.toFixed(2)} ($${estimate.pricing.inputPerMillion.toFixed(2)}/1M)`);
|
|
645
|
+
console.log(` Output: $${estimate.outputCost.toFixed(2)} ($${estimate.pricing.outputPerMillion.toFixed(2)}/1M)`);
|
|
646
|
+
console.log(LINE);
|
|
647
|
+
// Total cost with color based on warning level
|
|
648
|
+
const totalStr = `$${estimate.totalCost.toFixed(2)}`;
|
|
649
|
+
const hasWarning = estimate.warnings.length > 0;
|
|
650
|
+
const highestLevel = hasWarning ? estimate.warnings[0].level : null;
|
|
651
|
+
if (highestLevel === 'critical') {
|
|
652
|
+
console.log(`ESTIMATED TOTAL: ${red(totalStr)}`);
|
|
653
|
+
}
|
|
654
|
+
else if (highestLevel === 'red') {
|
|
655
|
+
console.log(`ESTIMATED TOTAL: ${red(totalStr)}`);
|
|
656
|
+
}
|
|
657
|
+
else if (highestLevel === 'yellow') {
|
|
658
|
+
console.log(`ESTIMATED TOTAL: ${yellow(totalStr)}`);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
console.log(`ESTIMATED TOTAL: ${green(totalStr)}`);
|
|
662
|
+
}
|
|
663
|
+
console.log('');
|
|
664
|
+
// Warnings with suggestions
|
|
665
|
+
if (estimate.warnings.length > 0) {
|
|
666
|
+
for (const warning of estimate.warnings) {
|
|
667
|
+
const marker = warning.level === 'critical' ? red('[!]') :
|
|
668
|
+
warning.level === 'red' ? red('[!]') :
|
|
669
|
+
yellow('[*]');
|
|
670
|
+
console.log(`${marker} ${warning.message}`);
|
|
671
|
+
}
|
|
672
|
+
console.log('');
|
|
673
|
+
console.log(dim('Consider:'));
|
|
674
|
+
console.log(dim(' - Analyzing a subdirectory: peakinfer analyze ./src/core'));
|
|
675
|
+
console.log(dim(' - Setting a budget: peakinfer analyze . --max-cost 10'));
|
|
676
|
+
console.log('');
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Render max-cost exceeded message.
|
|
681
|
+
*/
|
|
682
|
+
export function renderMaxCostExceeded(estimate, maxCost) {
|
|
683
|
+
renderCostEstimate(estimate);
|
|
684
|
+
console.log(red(`[!] Cost estimate ($${estimate.totalCost.toFixed(2)}) exceeds --max-cost threshold ($${maxCost.toFixed(2)})`));
|
|
685
|
+
console.log(dim(' Analysis skipped. Reduce scope or increase --max-cost.'));
|
|
686
|
+
console.log('');
|
|
687
|
+
}
|
|
688
|
+
// Render visual progress bar
|
|
689
|
+
function renderProgressBar(percent) {
|
|
690
|
+
const filled = Math.floor((percent / 100) * BAR_WIDTH);
|
|
691
|
+
const empty = BAR_WIDTH - filled;
|
|
692
|
+
return BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(empty);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Julie Zhou TUI Design Implementation
|
|
696
|
+
*
|
|
697
|
+
* Key principles from DD Section 6.4:
|
|
698
|
+
* - Progress should be phase-based (not noisy per-file spam)
|
|
699
|
+
* - Use stable phase names across runs
|
|
700
|
+
* - If a phase is slow, show a calm "still working" heartbeat, not a flood
|
|
701
|
+
*
|
|
702
|
+
* From DD Section 8.1:
|
|
703
|
+
* - "Planning…" appears briefly only in --verbose
|
|
704
|
+
* - Default mode shows stable phase progress
|
|
705
|
+
*/
|
|
706
|
+
export function createRenderer(opts = {}) {
|
|
707
|
+
let currentPlan = null;
|
|
708
|
+
let isResumed = false;
|
|
709
|
+
let phaseNumber = 0;
|
|
710
|
+
let totalPhases = 0;
|
|
711
|
+
let currentPhase = null;
|
|
712
|
+
let spinner = null;
|
|
713
|
+
// Calculate user-visible phases (excludes internal tasks)
|
|
714
|
+
function countUserPhases(plan) {
|
|
715
|
+
const userPhases = new Set();
|
|
716
|
+
for (const task of plan.tasks) {
|
|
717
|
+
const phase = getPhaseForTask(task);
|
|
718
|
+
if (phase)
|
|
719
|
+
userPhases.add(phase);
|
|
720
|
+
}
|
|
721
|
+
return userPhases.size;
|
|
722
|
+
}
|
|
723
|
+
// Map internal task types to user-meaningful phases
|
|
724
|
+
function getPhaseForTask(task) {
|
|
725
|
+
if (task.description === 'Load pricing data')
|
|
726
|
+
return null;
|
|
727
|
+
if (task.description === 'Load cached results')
|
|
728
|
+
return null;
|
|
729
|
+
if (task.description === 'Profile performance')
|
|
730
|
+
return 'PROFILING';
|
|
731
|
+
if (task.type === 'scan')
|
|
732
|
+
return 'SCANNING';
|
|
733
|
+
if (task.type === 'analyze')
|
|
734
|
+
return 'ANALYZING';
|
|
735
|
+
if (task.type === 'parse_events')
|
|
736
|
+
return 'PARSING';
|
|
737
|
+
if (task.type === 'join')
|
|
738
|
+
return 'CORRELATING';
|
|
739
|
+
if (task.type === 'load_templates')
|
|
740
|
+
return null;
|
|
741
|
+
if (task.type === 'generate_insights')
|
|
742
|
+
return 'GENERATING';
|
|
743
|
+
if (task.type === 'generate_html')
|
|
744
|
+
return null; // Part of save
|
|
745
|
+
if (task.type === 'save_artifacts')
|
|
746
|
+
return null; // Silent
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
// Check if we're in a TTY (interactive terminal)
|
|
750
|
+
const isTTY = process.stdout.isTTY;
|
|
751
|
+
// Start ora spinner for smooth animation during slow phases
|
|
752
|
+
// Shows progress bar at 0% immediately so users know progress tracking is active
|
|
753
|
+
function startSpinner(phaseName) {
|
|
754
|
+
if (!isTTY)
|
|
755
|
+
return;
|
|
756
|
+
stopSpinner();
|
|
757
|
+
// Build initial progress bar at 0%
|
|
758
|
+
const bar = chalk.hex(COLORS.neutral)('') + chalk.hex(COLORS.border)(BAR_EMPTY.repeat(BAR_WIDTH));
|
|
759
|
+
const initialText = `${phaseName}... ${bar} 0%`;
|
|
760
|
+
spinner = ora({
|
|
761
|
+
text: initialText,
|
|
762
|
+
spinner: 'dots',
|
|
763
|
+
color: 'gray',
|
|
764
|
+
}).start();
|
|
765
|
+
}
|
|
766
|
+
function stopSpinner() {
|
|
767
|
+
if (spinner) {
|
|
768
|
+
spinner.stop();
|
|
769
|
+
spinner = null;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// Update spinner text with progress bar (Claude Code-style TUI)
|
|
773
|
+
function updateSpinnerProgress(phaseName, percent, currentFile, detail) {
|
|
774
|
+
if (!spinner || !isTTY)
|
|
775
|
+
return;
|
|
776
|
+
const filled = Math.floor((percent / 100) * BAR_WIDTH);
|
|
777
|
+
const empty = BAR_WIDTH - filled;
|
|
778
|
+
const bar = chalk.hex(COLORS.neutral)(BAR_FILLED.repeat(filled)) +
|
|
779
|
+
chalk.hex(COLORS.border)(BAR_EMPTY.repeat(empty));
|
|
780
|
+
const percentStr = `${percent}%`.padStart(4);
|
|
781
|
+
// Build status line: "analyzing codebase... ████░░░░░░ 42% 12/47 files utils.ts"
|
|
782
|
+
let text = `${phaseName}... ${bar} ${percentStr}`;
|
|
783
|
+
// Show file count if available (e.g., "12/47 files")
|
|
784
|
+
if (detail) {
|
|
785
|
+
text += chalk.dim(` ${detail}`);
|
|
786
|
+
}
|
|
787
|
+
// Show current file being processed
|
|
788
|
+
if (currentFile) {
|
|
789
|
+
const fileDisplay = currentFile.length > 25
|
|
790
|
+
? '...' + currentFile.slice(-22)
|
|
791
|
+
: currentFile;
|
|
792
|
+
text += chalk.dim(` ${fileDisplay}`);
|
|
793
|
+
}
|
|
794
|
+
spinner.text = text;
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
renderHeader() {
|
|
798
|
+
console.log(bold(VERSION));
|
|
799
|
+
console.log('');
|
|
800
|
+
},
|
|
801
|
+
renderResumed(runId) {
|
|
802
|
+
isResumed = true;
|
|
803
|
+
renderResumed(runId);
|
|
804
|
+
},
|
|
805
|
+
renderPlan(plan) {
|
|
806
|
+
currentPlan = plan;
|
|
807
|
+
totalPhases = countUserPhases(plan);
|
|
808
|
+
if (isResumed)
|
|
809
|
+
return;
|
|
810
|
+
if (opts.verbose) {
|
|
811
|
+
renderPlan(plan);
|
|
812
|
+
}
|
|
813
|
+
// Non-verbose: No planning output (DD Section 8.1)
|
|
814
|
+
// "Planning…" appears briefly only in --verbose
|
|
815
|
+
},
|
|
816
|
+
renderTaskStart(task) {
|
|
817
|
+
if (isResumed)
|
|
818
|
+
return;
|
|
819
|
+
const phaseKey = getPhaseForTask(task);
|
|
820
|
+
if (!phaseKey)
|
|
821
|
+
return; // Skip internal tasks
|
|
822
|
+
const phaseName = PHASE[phaseKey];
|
|
823
|
+
// Only show if new phase
|
|
824
|
+
if (phaseName !== currentPhase) {
|
|
825
|
+
stopSpinner();
|
|
826
|
+
phaseNumber++;
|
|
827
|
+
currentPhase = phaseName;
|
|
828
|
+
if (opts.verbose && currentPlan) {
|
|
829
|
+
// Verbose: numbered phases like DD Section 6.4
|
|
830
|
+
process.stdout.write(` ${phaseNumber}/${totalPhases} ${phaseName}...`);
|
|
831
|
+
}
|
|
832
|
+
else if (isTTY) {
|
|
833
|
+
// Start ora spinner for smooth animation
|
|
834
|
+
startSpinner(phaseName);
|
|
835
|
+
}
|
|
836
|
+
// Non-TTY: don't show start, only completion
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
renderTaskComplete(task, result) {
|
|
840
|
+
if (isResumed)
|
|
841
|
+
return;
|
|
842
|
+
const phaseKey = getPhaseForTask(task);
|
|
843
|
+
if (!phaseKey)
|
|
844
|
+
return;
|
|
845
|
+
// Don't stop spinner here - let renderProgress handle completion
|
|
846
|
+
if (opts.verbose) {
|
|
847
|
+
renderTaskComplete(result);
|
|
848
|
+
}
|
|
849
|
+
// Non-verbose: phase completion shown via renderProgress
|
|
850
|
+
},
|
|
851
|
+
// Julie Zhou: Progress with meaningful completion data
|
|
852
|
+
// Enhanced with ora spinner and progress bar from peakinfer patterns
|
|
853
|
+
// Claude Code-style: shows progress bar + file count + current file
|
|
854
|
+
renderProgress(data) {
|
|
855
|
+
if (isResumed)
|
|
856
|
+
return;
|
|
857
|
+
const phaseLabel = {
|
|
858
|
+
scanning: PHASE.SCANNING,
|
|
859
|
+
analyzing: PHASE.ANALYZING,
|
|
860
|
+
profiling: PHASE.PROFILING,
|
|
861
|
+
parsing: PHASE.PARSING,
|
|
862
|
+
correlating: PHASE.CORRELATING,
|
|
863
|
+
generating: PHASE.GENERATING,
|
|
864
|
+
}[data.phase];
|
|
865
|
+
// If percent provided, this is a progress update (not completion)
|
|
866
|
+
// Update spinner if available, otherwise just skip (can't show progress bar in non-TTY)
|
|
867
|
+
if (data.percent !== undefined) {
|
|
868
|
+
if (isTTY && spinner) {
|
|
869
|
+
updateSpinnerProgress(phaseLabel, data.percent, data.currentFile, data.detail);
|
|
870
|
+
}
|
|
871
|
+
return; // Don't fall through to completion logic for progress updates
|
|
872
|
+
}
|
|
873
|
+
// Completion display (no percent = phase complete)
|
|
874
|
+
if (spinner) {
|
|
875
|
+
// Use ora's succeed for nice checkmark
|
|
876
|
+
spinner.stopAndPersist({
|
|
877
|
+
symbol: chalk.hex(COLORS.success)('✓'),
|
|
878
|
+
text: `${phaseLabel}... ${chalk.dim(data.detail || 'done')}`,
|
|
879
|
+
});
|
|
880
|
+
spinner = null;
|
|
881
|
+
}
|
|
882
|
+
else if (opts.verbose) {
|
|
883
|
+
// Verbose: show with duration-style detail
|
|
884
|
+
console.log(` ${phaseNumber}/${totalPhases} ${phaseLabel} ${dim(`(${data.detail || 'done'})`)}`);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
// Non-verbose non-TTY: clean completion
|
|
888
|
+
console.log(`${phaseLabel}... ${dim(data.detail || 'done')}`);
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
renderPartial(warnings) {
|
|
892
|
+
stopSpinner();
|
|
893
|
+
renderPartialState(warnings);
|
|
894
|
+
},
|
|
895
|
+
renderResults(results) {
|
|
896
|
+
stopSpinner();
|
|
897
|
+
// Clear any remaining progress line
|
|
898
|
+
if (currentPhase) {
|
|
899
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
900
|
+
}
|
|
901
|
+
console.log('');
|
|
902
|
+
// Check for zero state
|
|
903
|
+
if (results.mode !== 'runtime' && (!results.callsites || results.callsites.length === 0)) {
|
|
904
|
+
if (!results.insights || results.insights.length === 0) {
|
|
905
|
+
renderZeroState();
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
renderSuccess(results, { showFixes: opts.showFixes });
|
|
910
|
+
},
|
|
911
|
+
renderError(error, context) {
|
|
912
|
+
if (spinner) {
|
|
913
|
+
spinner.fail('Error');
|
|
914
|
+
spinner = null;
|
|
915
|
+
}
|
|
916
|
+
renderError(error, context);
|
|
917
|
+
},
|
|
918
|
+
// Direct access for testing
|
|
919
|
+
renderZeroState,
|
|
920
|
+
renderPartialState,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
//# sourceMappingURL=renderer.js.map
|