@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/agent.js
ADDED
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
import { existsSync, statSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { parseEvents, aggregate } from './runtime.js';
|
|
4
|
+
import { loadTemplates, getDefaultPrompt } from './templates.js';
|
|
5
|
+
import { loadPricing } from './costs.js';
|
|
6
|
+
import { saveArtifacts, checkResumable, loadArtifacts, generateRunId } from './artifacts.js';
|
|
7
|
+
import { generateHTML } from './html.js';
|
|
8
|
+
import { generatePDF } from './pdf.js';
|
|
9
|
+
import { VERSION } from './version.js';
|
|
10
|
+
import { enrichInsightsWithImpact, generateImpactSummary } from './impact.js';
|
|
11
|
+
import { saveRun, getLatestRun, loadRun } from './history.js';
|
|
12
|
+
import { compareSnapshots, formatComparisonSummary } from './comparison.js';
|
|
13
|
+
import { generatePredictions } from './prediction.js';
|
|
14
|
+
import { generateCounterfactuals } from './counterfactuals.js';
|
|
15
|
+
// Agent SDK pattern (DESIGN.md v2.0 Section 2.1, Patterns v0.2)
|
|
16
|
+
import { DiscoveryAgent, AnalyzerAgent, JoinerAgent, InsightAgent, RuntimeAnalyzerAgent, CorrelationAnalyzerAgent, StaticAnalysisOrchestrator, } from './agents/index.js';
|
|
17
|
+
import { getPricingContext } from './costs.js';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// HELPERS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Create synthetic enriched inference points from runtime events for insight evaluation.
|
|
23
|
+
* Groups events by provider:model and computes usage statistics.
|
|
24
|
+
* This enables runtime-only analysis to benefit from template-based insights.
|
|
25
|
+
*/
|
|
26
|
+
function createSyntheticCallsitesFromEvents(events) {
|
|
27
|
+
// Group events by provider:model
|
|
28
|
+
const groups = new Map();
|
|
29
|
+
for (const event of events) {
|
|
30
|
+
const key = `${event.provider}:${event.model}`;
|
|
31
|
+
if (!groups.has(key)) {
|
|
32
|
+
groups.set(key, []);
|
|
33
|
+
}
|
|
34
|
+
groups.get(key).push(event);
|
|
35
|
+
}
|
|
36
|
+
// Convert each group to a synthetic enriched inference point
|
|
37
|
+
const callsites = [];
|
|
38
|
+
let id = 1;
|
|
39
|
+
for (const [key, groupEvents] of groups) {
|
|
40
|
+
const [provider, model] = key.split(':');
|
|
41
|
+
// Compute usage stats
|
|
42
|
+
const calls = groupEvents.length;
|
|
43
|
+
const tokens_in = groupEvents.reduce((sum, e) => sum + e.input_tokens, 0);
|
|
44
|
+
const tokens_out = groupEvents.reduce((sum, e) => sum + e.output_tokens, 0);
|
|
45
|
+
const latencies = groupEvents.map(e => e.latency_ms).sort((a, b) => a - b);
|
|
46
|
+
const p50Index = Math.floor(latencies.length * 0.5);
|
|
47
|
+
const p95Index = Math.floor(latencies.length * 0.95);
|
|
48
|
+
const p99Index = Math.floor(latencies.length * 0.99);
|
|
49
|
+
callsites.push({
|
|
50
|
+
id: `runtime-${id++}`,
|
|
51
|
+
file: 'runtime', // Synthetic location
|
|
52
|
+
line: 0,
|
|
53
|
+
provider: provider,
|
|
54
|
+
model,
|
|
55
|
+
framework: null,
|
|
56
|
+
runtime: null,
|
|
57
|
+
patterns: {
|
|
58
|
+
streaming: groupEvents.some(e => e.streaming === true),
|
|
59
|
+
batching: groupEvents.some(e => e.batch_id !== undefined),
|
|
60
|
+
caching: groupEvents.some(e => e.cached === true),
|
|
61
|
+
retries: groupEvents.some(e => (e.retry_count || 0) > 0),
|
|
62
|
+
fallback: groupEvents.some(e => e.fallback_used === true),
|
|
63
|
+
},
|
|
64
|
+
confidence: 1.0,
|
|
65
|
+
usage: {
|
|
66
|
+
calls,
|
|
67
|
+
tokens_in,
|
|
68
|
+
tokens_out,
|
|
69
|
+
latency_p50: latencies[p50Index] || 0,
|
|
70
|
+
latency_p95: latencies[p95Index] || latencies[p50Index] || 0,
|
|
71
|
+
latency_p99: latencies[p99Index] || latencies[p95Index] || latencies[p50Index] || 0,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return callsites;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Convert StaticAnalysisOutput to LLM insights for display.
|
|
79
|
+
* Maps optimizations from all dimensions (cost, latency, throughput, reliability) to insights.
|
|
80
|
+
*/
|
|
81
|
+
function convertPerformanceToInsights(analysis) {
|
|
82
|
+
const insights = [];
|
|
83
|
+
// Convert all optimizations to insights
|
|
84
|
+
for (const opt of analysis.all_optimizations) {
|
|
85
|
+
const severity = opt.priority === 'critical' || opt.priority === 'high' ? 'critical' :
|
|
86
|
+
opt.priority === 'medium' ? 'warning' : 'info';
|
|
87
|
+
const categoryMap = {
|
|
88
|
+
cost: 'Cost Optimization',
|
|
89
|
+
latency: 'Latency Optimization',
|
|
90
|
+
throughput: 'Throughput Optimization',
|
|
91
|
+
reliability: 'Reliability Improvement',
|
|
92
|
+
};
|
|
93
|
+
const impactTypeMap = {
|
|
94
|
+
cost: 'cost',
|
|
95
|
+
latency: 'latency',
|
|
96
|
+
throughput: 'throughput',
|
|
97
|
+
reliability: 'improvement',
|
|
98
|
+
};
|
|
99
|
+
// Parse impact percentage from the impact string (e.g., "90% savings" or "50% improvement")
|
|
100
|
+
const impactMatch = opt.impact.match(/(\d+)%/);
|
|
101
|
+
const impactPercent = impactMatch ? parseInt(impactMatch[1]) : 20;
|
|
102
|
+
insights.push({
|
|
103
|
+
severity,
|
|
104
|
+
category: categoryMap[opt.dimension] || opt.dimension,
|
|
105
|
+
headline: opt.description,
|
|
106
|
+
evidence: `${opt.type}: ${opt.impact}`,
|
|
107
|
+
location: `${opt.file}:${opt.line}`,
|
|
108
|
+
recommendation: opt.description,
|
|
109
|
+
impact: {
|
|
110
|
+
layer: opt.dimension,
|
|
111
|
+
impactType: impactTypeMap[opt.dimension] || 'improvement',
|
|
112
|
+
estimatedImpactPercent: impactPercent,
|
|
113
|
+
effort: opt.effort,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// Add reliability anti-pattern insights
|
|
118
|
+
for (const profile of analysis.performance_profiles) {
|
|
119
|
+
if (profile.reliability?.anti_patterns) {
|
|
120
|
+
for (const antiPattern of profile.reliability.anti_patterns) {
|
|
121
|
+
const severity = antiPattern.severity === 'high' ? 'critical' :
|
|
122
|
+
antiPattern.severity === 'medium' ? 'warning' : 'info';
|
|
123
|
+
insights.push({
|
|
124
|
+
severity,
|
|
125
|
+
category: 'Reliability Issue',
|
|
126
|
+
headline: antiPattern.pattern,
|
|
127
|
+
evidence: antiPattern.description,
|
|
128
|
+
location: antiPattern.location,
|
|
129
|
+
recommendation: `Fix: ${antiPattern.pattern}`,
|
|
130
|
+
impact: {
|
|
131
|
+
layer: 'reliability',
|
|
132
|
+
impactType: 'improvement',
|
|
133
|
+
estimatedImpactPercent: antiPattern.severity === 'high' ? 40 : 20,
|
|
134
|
+
effort: 'low',
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return insights;
|
|
141
|
+
}
|
|
142
|
+
// =============================================================================
|
|
143
|
+
// PASS 1: PLAN
|
|
144
|
+
// =============================================================================
|
|
145
|
+
function detectMode(opts) {
|
|
146
|
+
// Check if the main path is an events file (case-insensitive for robustness)
|
|
147
|
+
const pathLower = opts.path.toLowerCase();
|
|
148
|
+
const isEventsFile = pathLower.endsWith('.jsonl') ||
|
|
149
|
+
pathLower.endsWith('.ndjson') ||
|
|
150
|
+
pathLower.endsWith('.json') ||
|
|
151
|
+
pathLower.endsWith('.csv');
|
|
152
|
+
// Also check if it's a file (not directory) - file paths with these extensions are events
|
|
153
|
+
const pathIsFile = !isDirectory(opts.path);
|
|
154
|
+
// Runtime mode: events file path without separate --events option
|
|
155
|
+
if (isEventsFile && pathIsFile && !opts.events) {
|
|
156
|
+
return 'runtime';
|
|
157
|
+
}
|
|
158
|
+
// Combined mode: directory path with --events option
|
|
159
|
+
if (!isEventsFile && opts.events) {
|
|
160
|
+
return 'combined';
|
|
161
|
+
}
|
|
162
|
+
// Combined mode: events file path with separate --events option (rare but valid)
|
|
163
|
+
if (isEventsFile && opts.events) {
|
|
164
|
+
return 'combined';
|
|
165
|
+
}
|
|
166
|
+
// Static mode: directory path without --events option
|
|
167
|
+
if (!isEventsFile && !opts.events) {
|
|
168
|
+
return 'static';
|
|
169
|
+
}
|
|
170
|
+
return 'combined';
|
|
171
|
+
}
|
|
172
|
+
function isDirectory(path) {
|
|
173
|
+
try {
|
|
174
|
+
return existsSync(path) && statSync(path).isDirectory();
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fetch runtime events from a URL
|
|
182
|
+
*/
|
|
183
|
+
async function fetchEventsFromUrl(url) {
|
|
184
|
+
const response = await fetch(url);
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new Error(`Failed to fetch events from ${url}: ${response.status} ${response.statusText}`);
|
|
187
|
+
}
|
|
188
|
+
return response.text();
|
|
189
|
+
}
|
|
190
|
+
export function plan(opts) {
|
|
191
|
+
const tasks = [];
|
|
192
|
+
let id = 1;
|
|
193
|
+
const mode = detectMode(opts);
|
|
194
|
+
const pathIsDirectory = isDirectory(opts.path);
|
|
195
|
+
// Generate run ID and check resumability
|
|
196
|
+
const inputs = {
|
|
197
|
+
repoRoot: isDirectory(opts.path) ? opts.path : undefined,
|
|
198
|
+
eventsPath: opts.events || (isDirectory(opts.path) ? undefined : opts.path),
|
|
199
|
+
offline: opts.offline,
|
|
200
|
+
};
|
|
201
|
+
const runId = generateRunId(inputs);
|
|
202
|
+
const resumeCheck = checkResumable(inputs);
|
|
203
|
+
const shouldResume = !opts.noCache && resumeCheck.canResume;
|
|
204
|
+
// If we can resume, skip analysis tasks
|
|
205
|
+
if (!shouldResume) {
|
|
206
|
+
// Always load pricing first
|
|
207
|
+
tasks.push({
|
|
208
|
+
id: id++,
|
|
209
|
+
type: 'scan', // Reusing for pricing load
|
|
210
|
+
description: 'Load pricing data',
|
|
211
|
+
});
|
|
212
|
+
// Only add static analysis tasks if path is a directory AND mode requires it
|
|
213
|
+
if ((mode === 'static' || mode === 'combined') && pathIsDirectory) {
|
|
214
|
+
tasks.push({
|
|
215
|
+
id: id++,
|
|
216
|
+
type: 'scan',
|
|
217
|
+
description: 'Scan repository',
|
|
218
|
+
});
|
|
219
|
+
// Unified analysis: discovery + profiling in single LLM call
|
|
220
|
+
tasks.push({
|
|
221
|
+
id: id++,
|
|
222
|
+
type: 'analyze',
|
|
223
|
+
description: 'Analyze and profile inference points',
|
|
224
|
+
depends_on: [id - 1],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (mode === 'runtime' || mode === 'combined') {
|
|
228
|
+
tasks.push({
|
|
229
|
+
id: id++,
|
|
230
|
+
type: 'parse_events',
|
|
231
|
+
description: 'Parse runtime events',
|
|
232
|
+
});
|
|
233
|
+
// NEW: LLM-based runtime analysis for ALL modes with runtime data (Patterns v0.2)
|
|
234
|
+
tasks.push({
|
|
235
|
+
id: id++,
|
|
236
|
+
type: 'analyze', // Reuse 'analyze' type for runtime LLM analysis
|
|
237
|
+
description: 'Analyze runtime patterns',
|
|
238
|
+
depends_on: [id - 1],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (mode === 'combined') {
|
|
242
|
+
tasks.push({
|
|
243
|
+
id: id++,
|
|
244
|
+
type: 'join',
|
|
245
|
+
description: 'Correlate static + runtime',
|
|
246
|
+
});
|
|
247
|
+
// NEW: LLM-based correlation analysis (Patterns v0.2)
|
|
248
|
+
tasks.push({
|
|
249
|
+
id: id++,
|
|
250
|
+
type: 'join', // Reuse 'join' type for correlation analysis
|
|
251
|
+
description: 'Analyze code-runtime drift',
|
|
252
|
+
depends_on: [id - 1],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
tasks.push({
|
|
256
|
+
id: id++,
|
|
257
|
+
type: 'load_templates',
|
|
258
|
+
description: 'Load insight templates',
|
|
259
|
+
});
|
|
260
|
+
tasks.push({
|
|
261
|
+
id: id++,
|
|
262
|
+
type: 'generate_insights',
|
|
263
|
+
description: 'Generate findings',
|
|
264
|
+
});
|
|
265
|
+
// v1.5: Compare with previous run if requested
|
|
266
|
+
if (opts.compare) {
|
|
267
|
+
tasks.push({
|
|
268
|
+
id: id++,
|
|
269
|
+
type: 'compare',
|
|
270
|
+
description: 'Compare with previous run',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// v1.5: Generate deploy-time predictions if requested
|
|
274
|
+
if (opts.predict) {
|
|
275
|
+
tasks.push({
|
|
276
|
+
id: id++,
|
|
277
|
+
type: 'predict',
|
|
278
|
+
description: 'Generate latency predictions',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// v1.5: Always generate counterfactual insights (show optimization opportunities)
|
|
282
|
+
tasks.push({
|
|
283
|
+
id: id++,
|
|
284
|
+
type: 'counterfactuals',
|
|
285
|
+
description: 'Identify optimization opportunities',
|
|
286
|
+
});
|
|
287
|
+
if (opts.html) {
|
|
288
|
+
tasks.push({
|
|
289
|
+
id: id++,
|
|
290
|
+
type: 'generate_html',
|
|
291
|
+
description: 'Generate HTML report',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (opts.pdf) {
|
|
295
|
+
tasks.push({
|
|
296
|
+
id: id++,
|
|
297
|
+
type: 'generate_pdf',
|
|
298
|
+
description: 'Generate PDF report',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
tasks.push({
|
|
302
|
+
id: id++,
|
|
303
|
+
type: 'save_artifacts',
|
|
304
|
+
description: 'Save artifacts',
|
|
305
|
+
});
|
|
306
|
+
// v1.5: Save to history for comparison/prediction (unless --no-history)
|
|
307
|
+
if (!opts.noHistory) {
|
|
308
|
+
tasks.push({
|
|
309
|
+
id: id++,
|
|
310
|
+
type: 'save_history',
|
|
311
|
+
description: 'Save to history',
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// Resuming - just need to load cached artifacts
|
|
317
|
+
tasks.push({
|
|
318
|
+
id: id++,
|
|
319
|
+
type: 'scan', // Reusing for load cached
|
|
320
|
+
description: 'Load cached results',
|
|
321
|
+
});
|
|
322
|
+
// Always generate HTML if requested, even when resuming
|
|
323
|
+
if (opts.html) {
|
|
324
|
+
tasks.push({
|
|
325
|
+
id: id++,
|
|
326
|
+
type: 'generate_html',
|
|
327
|
+
description: 'Generate HTML report',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (opts.pdf) {
|
|
331
|
+
tasks.push({
|
|
332
|
+
id: id++,
|
|
333
|
+
type: 'generate_pdf',
|
|
334
|
+
description: 'Generate PDF report',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (opts.html || opts.pdf) {
|
|
338
|
+
tasks.push({
|
|
339
|
+
id: id++,
|
|
340
|
+
type: 'save_artifacts',
|
|
341
|
+
description: 'Save artifacts',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
plan: { mode, tasks },
|
|
347
|
+
runId,
|
|
348
|
+
canResume: shouldResume,
|
|
349
|
+
runDir: resumeCheck.runDir,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
// =============================================================================
|
|
353
|
+
// PASS 2: EXECUTE
|
|
354
|
+
// =============================================================================
|
|
355
|
+
async function executeTask(task, ctx, templates, runDir, onProgress) {
|
|
356
|
+
switch (task.type) {
|
|
357
|
+
case 'scan':
|
|
358
|
+
if (task.description === 'Load pricing data') {
|
|
359
|
+
await loadPricing();
|
|
360
|
+
}
|
|
361
|
+
else if (task.description === 'Load cached results') {
|
|
362
|
+
// Resume from cache
|
|
363
|
+
if (runDir) {
|
|
364
|
+
const cached = loadArtifacts(runDir);
|
|
365
|
+
ctx.inferenceMap = cached.inferenceMap;
|
|
366
|
+
ctx.insights = cached.insights;
|
|
367
|
+
ctx.joined = cached.joined;
|
|
368
|
+
ctx.runtimeSummary = cached.runtime;
|
|
369
|
+
if (cached.inferenceMap) {
|
|
370
|
+
ctx.callsites = cached.inferenceMap.callsites;
|
|
371
|
+
}
|
|
372
|
+
if (cached.insights && cached.insights.length > 0) {
|
|
373
|
+
ctx.impactSummary = generateImpactSummary(cached.insights);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Validate that path is a directory before attempting to scan
|
|
379
|
+
if (!isDirectory(ctx.opts.path)) {
|
|
380
|
+
const ext = ctx.opts.path.toLowerCase();
|
|
381
|
+
if (ext.endsWith('.jsonl') || ext.endsWith('.ndjson') || ext.endsWith('.json') || ext.endsWith('.csv')) {
|
|
382
|
+
throw new Error(`Cannot scan file "${ctx.opts.path}" as a codebase. This looks like an events file - try 'peakinfer analyze ${ctx.opts.path}' for runtime analysis.`);
|
|
383
|
+
}
|
|
384
|
+
throw new Error(`Expected directory for static analysis, got file: ${ctx.opts.path}`);
|
|
385
|
+
}
|
|
386
|
+
// Agent SDK pattern: DiscoveryAgent with constrained tools (Glob/Grep/Read)
|
|
387
|
+
const discoveryResult = await DiscoveryAgent.execute({ root: ctx.opts.path });
|
|
388
|
+
ctx.scanResult = discoveryResult.result.scanResult;
|
|
389
|
+
const fileCount = ctx.scanResult?.files.length ?? 0;
|
|
390
|
+
onProgress?.({ phase: 'scanning', detail: `${fileCount} files` });
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
case 'analyze':
|
|
394
|
+
// Handle static code analysis, runtime pattern analysis, AND performance profiling
|
|
395
|
+
if (task.description === 'Analyze runtime patterns') {
|
|
396
|
+
// NEW: LLM-based runtime analysis (Patterns v0.2)
|
|
397
|
+
if (!ctx.events || !ctx.runtimeSummary) {
|
|
398
|
+
ctx.warnings.push('Runtime analysis skipped: no events parsed');
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
// Get pricing context for models in the data
|
|
403
|
+
const models = Object.keys(ctx.runtimeSummary.byModel);
|
|
404
|
+
const pricingContext = getPricingContext(models);
|
|
405
|
+
// Emit progress: starting runtime pattern analysis
|
|
406
|
+
onProgress?.({ phase: 'analyzing', detail: `analyzing ${ctx.events.length} runtime events...` });
|
|
407
|
+
const runtimeResult = await RuntimeAnalyzerAgent.execute({
|
|
408
|
+
events: ctx.events,
|
|
409
|
+
runtimeSummary: ctx.runtimeSummary,
|
|
410
|
+
pricingContext,
|
|
411
|
+
});
|
|
412
|
+
// Store runtime insights for later merging
|
|
413
|
+
const runtimeInsights = runtimeResult.result.insights.map(i => ({
|
|
414
|
+
...i,
|
|
415
|
+
source: 'llm',
|
|
416
|
+
}));
|
|
417
|
+
ctx.llmInsights = [...(ctx.llmInsights || []), ...runtimeInsights];
|
|
418
|
+
onProgress?.({ phase: 'analyzing', detail: `${runtimeResult.result.insights.length} runtime insights` });
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
ctx.warnings.push(`Runtime analysis warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else if (task.description === 'Analyze and profile inference points') {
|
|
425
|
+
// UNIFIED: Discovery + Profiling in single LLM call
|
|
426
|
+
if (!ctx.scanResult)
|
|
427
|
+
throw new Error('Scan result required');
|
|
428
|
+
try {
|
|
429
|
+
const scanRoot = ctx.scanResult.root;
|
|
430
|
+
// Read all source files (no artificial limit - process all candidates)
|
|
431
|
+
const filesToAnalyze = ctx.scanResult.files
|
|
432
|
+
.map(f => {
|
|
433
|
+
const fullPath = resolve(scanRoot, f.path);
|
|
434
|
+
try {
|
|
435
|
+
return {
|
|
436
|
+
path: fullPath,
|
|
437
|
+
content: readFileSync(fullPath, 'utf-8'),
|
|
438
|
+
language: f.language,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
.filter((f) => f !== null);
|
|
446
|
+
if (filesToAnalyze.length === 0) {
|
|
447
|
+
ctx.warnings.push('Analysis skipped: no source files available');
|
|
448
|
+
ctx.callsites = [];
|
|
449
|
+
ctx.llmInsights = [];
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
// Run unified analysis (discovery + profiling in one LLM call)
|
|
453
|
+
// Pass progress callback for Claude Code-style per-file progress updates
|
|
454
|
+
const orchestrator = new StaticAnalysisOrchestrator();
|
|
455
|
+
ctx.staticAnalysis = await orchestrator.analyze({ files: filesToAnalyze }, (progressData) => {
|
|
456
|
+
// Forward progress to renderer with percent for progress bar
|
|
457
|
+
onProgress?.({
|
|
458
|
+
phase: 'analyzing',
|
|
459
|
+
percent: progressData.percent,
|
|
460
|
+
currentFile: progressData.currentFile,
|
|
461
|
+
detail: `${progressData.completed}/${progressData.total} files`,
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
// Extract callsites from unified analysis for rest of pipeline
|
|
465
|
+
const callsitesFromAnalysis = [];
|
|
466
|
+
for (const profile of ctx.staticAnalysis.performance_profiles) {
|
|
467
|
+
callsitesFromAnalysis.push({
|
|
468
|
+
id: profile.inference_point_id,
|
|
469
|
+
file: profile.file.replace(scanRoot + '/', '').replace(scanRoot, ''),
|
|
470
|
+
line: profile.line,
|
|
471
|
+
provider: profile.provider,
|
|
472
|
+
model: profile.model ?? null,
|
|
473
|
+
framework: null,
|
|
474
|
+
runtime: null,
|
|
475
|
+
patterns: {},
|
|
476
|
+
confidence: 0.9,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
ctx.callsites = callsitesFromAnalysis;
|
|
480
|
+
// Convert performance profiles to insights
|
|
481
|
+
const performanceInsights = convertPerformanceToInsights(ctx.staticAnalysis);
|
|
482
|
+
ctx.llmInsights = performanceInsights;
|
|
483
|
+
// Build inference map
|
|
484
|
+
let promptMeta = { llmUsed: true };
|
|
485
|
+
try {
|
|
486
|
+
const prompt = getDefaultPrompt();
|
|
487
|
+
promptMeta.promptId = prompt.id;
|
|
488
|
+
promptMeta.promptVersion = prompt.version;
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
// Prompt not found, use defaults
|
|
492
|
+
}
|
|
493
|
+
ctx.inferenceMap = buildInferenceMap(ctx.opts.path, ctx.callsites, promptMeta);
|
|
494
|
+
onProgress?.({
|
|
495
|
+
phase: 'profiling',
|
|
496
|
+
detail: `${ctx.staticAnalysis.summary.total_optimizations} optimizations found`,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
ctx.warnings.push(`Analysis warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
501
|
+
ctx.callsites = [];
|
|
502
|
+
ctx.llmInsights = [];
|
|
503
|
+
ctx.inferenceMap = buildInferenceMap(ctx.opts.path, [], { llmUsed: false });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (task.description === 'Profile performance') {
|
|
507
|
+
// Legacy: Skip - now handled by unified analysis above
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
// Original static code analysis
|
|
512
|
+
if (!ctx.scanResult)
|
|
513
|
+
throw new Error('Scan result required');
|
|
514
|
+
try {
|
|
515
|
+
// Agent SDK pattern: AnalyzerAgent with tool-limited semantic analysis
|
|
516
|
+
// Pass progress callback for visual progress bar during LLM analysis
|
|
517
|
+
const analyzerResult = await AnalyzerAgent.execute({
|
|
518
|
+
scanResult: ctx.scanResult,
|
|
519
|
+
onProgress: onProgress ? (data) => {
|
|
520
|
+
onProgress({ phase: 'analyzing', percent: data.percent, currentFile: data.currentFile });
|
|
521
|
+
} : undefined,
|
|
522
|
+
});
|
|
523
|
+
ctx.callsites = analyzerResult.result.callsites;
|
|
524
|
+
ctx.llmInsights = analyzerResult.result.llmInsights;
|
|
525
|
+
// Get prompt metadata for report
|
|
526
|
+
let promptMeta = { llmUsed: ctx.llmInsights.length > 0 };
|
|
527
|
+
try {
|
|
528
|
+
const prompt = getDefaultPrompt();
|
|
529
|
+
promptMeta.promptId = prompt.id;
|
|
530
|
+
promptMeta.promptVersion = prompt.version;
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// Prompt not found, use defaults
|
|
534
|
+
}
|
|
535
|
+
ctx.inferenceMap = buildInferenceMap(ctx.opts.path, ctx.callsites, promptMeta);
|
|
536
|
+
onProgress?.({ phase: 'analyzing', detail: `${ctx.callsites.length} inference points` });
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
// Partial state: analysis failed but we can continue
|
|
540
|
+
ctx.warnings.push(`Analysis warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
541
|
+
ctx.callsites = [];
|
|
542
|
+
ctx.llmInsights = [];
|
|
543
|
+
ctx.inferenceMap = buildInferenceMap(ctx.opts.path, [], { llmUsed: false });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
case 'parse_events': {
|
|
548
|
+
try {
|
|
549
|
+
// Build normalization options from CLI flags (PRD §6.4)
|
|
550
|
+
const normalizationOptions = {
|
|
551
|
+
format_hint: ctx.opts.formatHint,
|
|
552
|
+
field_hints: ctx.opts.fieldHints,
|
|
553
|
+
lenient: ctx.opts.lenient,
|
|
554
|
+
strict: ctx.opts.strict,
|
|
555
|
+
codebase_context: ctx.scanResult, // Pass codebase context for smarter normalization
|
|
556
|
+
};
|
|
557
|
+
// Handle --events-url: fetch from URL first
|
|
558
|
+
if (ctx.opts.eventsUrl) {
|
|
559
|
+
const eventsContent = await fetchEventsFromUrl(ctx.opts.eventsUrl);
|
|
560
|
+
// Write to temp file for parsing
|
|
561
|
+
const tempPath = '.peakinfer/.tmp_events.jsonl';
|
|
562
|
+
writeFileSync(tempPath, eventsContent);
|
|
563
|
+
ctx.events = await parseEvents(tempPath, normalizationOptions);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
const eventsPath = ctx.opts.events || ctx.opts.path;
|
|
567
|
+
ctx.events = await parseEvents(eventsPath, normalizationOptions);
|
|
568
|
+
}
|
|
569
|
+
ctx.runtimeSummary = aggregate(ctx.events);
|
|
570
|
+
// Emit progress with event count
|
|
571
|
+
onProgress?.({ phase: 'parsing', detail: `${ctx.events.length} events` });
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
// Partial state: event parsing failed
|
|
575
|
+
ctx.warnings.push(`Events parsing warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
576
|
+
ctx.events = [];
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
case 'join':
|
|
581
|
+
// Handle both basic join AND LLM-based correlation analysis
|
|
582
|
+
if (task.description === 'Analyze code-runtime drift') {
|
|
583
|
+
// NEW: LLM-based correlation analysis (Patterns v0.2)
|
|
584
|
+
if (!ctx.callsites || !ctx.events || !ctx.runtimeSummary) {
|
|
585
|
+
ctx.warnings.push('Correlation analysis skipped: missing callsites or events');
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
// Emit progress: starting drift detection analysis
|
|
590
|
+
onProgress?.({ phase: 'correlating', detail: 'detecting code-runtime drift...' });
|
|
591
|
+
const correlationResult = await CorrelationAnalyzerAgent.execute({
|
|
592
|
+
callsites: ctx.callsites,
|
|
593
|
+
events: ctx.events,
|
|
594
|
+
runtimeSummary: ctx.runtimeSummary,
|
|
595
|
+
});
|
|
596
|
+
// Merge correlation insights
|
|
597
|
+
const correlationInsights = correlationResult.result.insights.map(i => ({
|
|
598
|
+
...i,
|
|
599
|
+
source: 'llm',
|
|
600
|
+
}));
|
|
601
|
+
ctx.llmInsights = [...(ctx.llmInsights || []), ...correlationInsights];
|
|
602
|
+
// Update drift signals in joined output
|
|
603
|
+
if (ctx.joined) {
|
|
604
|
+
ctx.joined.drift = [
|
|
605
|
+
...ctx.joined.drift,
|
|
606
|
+
...correlationResult.result.driftSignals,
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
onProgress?.({
|
|
610
|
+
phase: 'correlating',
|
|
611
|
+
detail: `alignment: ${Math.round(correlationResult.result.alignmentScore * 100)}%`,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
ctx.warnings.push(`Correlation analysis warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
// Original basic join
|
|
620
|
+
if (!ctx.callsites || !ctx.events)
|
|
621
|
+
throw new Error('Callsites and events required');
|
|
622
|
+
// Agent SDK pattern: JoinerAgent correlates static + runtime
|
|
623
|
+
const joinerResult = await JoinerAgent.execute({ callsites: ctx.callsites, events: ctx.events });
|
|
624
|
+
ctx.joined = joinerResult.result.joined;
|
|
625
|
+
onProgress?.({
|
|
626
|
+
phase: 'correlating',
|
|
627
|
+
detail: `${ctx.joined.callsites.filter(c => 'usage' in c && c.usage).length} matched`,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
case 'load_templates':
|
|
632
|
+
// Templates already loaded, just verify
|
|
633
|
+
break;
|
|
634
|
+
case 'generate_insights': {
|
|
635
|
+
// Agent SDK pattern: InsightAgent evaluates templates
|
|
636
|
+
// For runtime-only mode, create synthetic inference points from events for template evaluation
|
|
637
|
+
let data;
|
|
638
|
+
if (ctx.joined) {
|
|
639
|
+
data = ctx.joined;
|
|
640
|
+
}
|
|
641
|
+
else if (ctx.callsites && ctx.callsites.length > 0) {
|
|
642
|
+
data = { callsites: ctx.callsites };
|
|
643
|
+
}
|
|
644
|
+
else if (ctx.events && ctx.events.length > 0) {
|
|
645
|
+
// Runtime-only mode: create synthetic enriched inference points from events
|
|
646
|
+
data = { callsites: createSyntheticCallsitesFromEvents(ctx.events) };
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
data = { callsites: [] };
|
|
650
|
+
}
|
|
651
|
+
const insightResult = await InsightAgent.execute({ data, templates });
|
|
652
|
+
const templateInsights = insightResult.result.insights;
|
|
653
|
+
// Convert LLM insights to Insight format, preserving any LLM-provided impact estimates
|
|
654
|
+
const llmFormattedInsights = (ctx.llmInsights || []).map(llmInsight => ({
|
|
655
|
+
id: `llm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
656
|
+
severity: llmInsight.severity,
|
|
657
|
+
category: llmInsight.category,
|
|
658
|
+
headline: llmInsight.headline,
|
|
659
|
+
evidence: llmInsight.evidence,
|
|
660
|
+
location: llmInsight.location,
|
|
661
|
+
recommendation: llmInsight.recommendation,
|
|
662
|
+
source: 'llm', // Mark as LLM-generated
|
|
663
|
+
// Preserve LLM-provided impact estimates if present
|
|
664
|
+
impact: llmInsight.impact ? {
|
|
665
|
+
layer: llmInsight.impact.layer,
|
|
666
|
+
impactType: llmInsight.impact.impactType,
|
|
667
|
+
estimatedImpactPercent: llmInsight.impact.estimatedImpactPercent,
|
|
668
|
+
effort: llmInsight.impact.effort,
|
|
669
|
+
confidence: 0.8, // LLM estimates have higher confidence
|
|
670
|
+
} : undefined,
|
|
671
|
+
}));
|
|
672
|
+
// Combine: LLM semantic insights first (phase 1), then template pattern insights (phase 2)
|
|
673
|
+
const combinedInsights = [...llmFormattedInsights, ...templateInsights];
|
|
674
|
+
// Enrich all insights with impact estimates (fills in missing ones)
|
|
675
|
+
ctx.insights = enrichInsightsWithImpact(combinedInsights);
|
|
676
|
+
// Generate stack-ranked impact summary
|
|
677
|
+
ctx.impactSummary = generateImpactSummary(ctx.insights);
|
|
678
|
+
// Emit progress with insight count
|
|
679
|
+
onProgress?.({ phase: 'generating', detail: `${ctx.insights.length} findings` });
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
case 'generate_html':
|
|
683
|
+
if (!ctx.inferenceMap)
|
|
684
|
+
throw new Error('InferenceMap required for HTML');
|
|
685
|
+
ctx.htmlContent = generateHTML({
|
|
686
|
+
inferenceMap: ctx.inferenceMap,
|
|
687
|
+
insights: ctx.insights || [],
|
|
688
|
+
joined: ctx.joined,
|
|
689
|
+
runtime: ctx.runtimeSummary,
|
|
690
|
+
});
|
|
691
|
+
break;
|
|
692
|
+
case 'generate_pdf': {
|
|
693
|
+
if (!ctx.htmlContent)
|
|
694
|
+
throw new Error('HTML content required for PDF');
|
|
695
|
+
// Generate human-friendly PDF filename
|
|
696
|
+
const pdfAbsolutePath = ctx.inferenceMap?.metadata?.absolutePath || ctx.opts.path;
|
|
697
|
+
const pdfProjectName = pdfAbsolutePath.split('/').filter(Boolean).pop() || 'project';
|
|
698
|
+
const pdfProjectSlug = pdfProjectName.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').substring(0, 50);
|
|
699
|
+
const pdfFileName = `${pdfProjectSlug}_peakinfer_report.pdf`;
|
|
700
|
+
const pdfPath = `.peakinfer/${pdfFileName}`;
|
|
701
|
+
await generatePDF(ctx.htmlContent, pdfPath);
|
|
702
|
+
ctx.pdfPath = pdfPath;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
case 'save_artifacts': {
|
|
706
|
+
const inputs = {
|
|
707
|
+
repoRoot: isDirectory(ctx.opts.path) ? ctx.opts.path : undefined,
|
|
708
|
+
eventsPath: ctx.opts.events || (isDirectory(ctx.opts.path) ? undefined : ctx.opts.path),
|
|
709
|
+
offline: ctx.opts.offline,
|
|
710
|
+
};
|
|
711
|
+
// Extract project name for human-friendly report naming
|
|
712
|
+
const absolutePath = ctx.inferenceMap?.metadata?.absolutePath || ctx.opts.path;
|
|
713
|
+
const projectName = absolutePath.split('/').filter(Boolean).pop() || 'project';
|
|
714
|
+
saveArtifacts({
|
|
715
|
+
inferenceMap: ctx.inferenceMap,
|
|
716
|
+
insights: ctx.insights,
|
|
717
|
+
joined: ctx.joined,
|
|
718
|
+
runtime: ctx.runtimeSummary,
|
|
719
|
+
html: ctx.htmlContent,
|
|
720
|
+
}, '.peakinfer', {
|
|
721
|
+
runId: ctx.runId,
|
|
722
|
+
inputs,
|
|
723
|
+
projectName,
|
|
724
|
+
});
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
case 'save_history': {
|
|
728
|
+
// v1.5: Save run to history for comparison/prediction features
|
|
729
|
+
const mode = ctx.joined ? 'combined' : (ctx.events?.length ? 'runtime' : 'static');
|
|
730
|
+
// Prepare analysis data for history storage
|
|
731
|
+
const historyData = {
|
|
732
|
+
inferenceMap: ctx.inferenceMap,
|
|
733
|
+
insights: ctx.insights,
|
|
734
|
+
joined: ctx.joined,
|
|
735
|
+
runtime: ctx.runtimeSummary,
|
|
736
|
+
};
|
|
737
|
+
// Generate human-friendly HTML path if generated
|
|
738
|
+
const absolutePath = ctx.inferenceMap?.metadata?.absolutePath || ctx.opts.path;
|
|
739
|
+
const projectName = absolutePath.split('/').filter(Boolean).pop() || 'project';
|
|
740
|
+
const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').substring(0, 50);
|
|
741
|
+
saveRun({
|
|
742
|
+
path: ctx.opts.path,
|
|
743
|
+
analysisType: mode,
|
|
744
|
+
data: historyData,
|
|
745
|
+
htmlPath: ctx.htmlContent ? `.peakinfer/${projectSlug}_peakinfer_report.html` : undefined,
|
|
746
|
+
pdfPath: ctx.pdfPath,
|
|
747
|
+
});
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case 'compare': {
|
|
751
|
+
// v1.5: Compare with previous run
|
|
752
|
+
// Build current snapshot
|
|
753
|
+
const currentSnapshot = {
|
|
754
|
+
runId: ctx.runId,
|
|
755
|
+
timestamp: new Date().toISOString(),
|
|
756
|
+
callsites: ctx.callsites || [],
|
|
757
|
+
insights: ctx.insights,
|
|
758
|
+
};
|
|
759
|
+
// Get baseline (specific run or latest)
|
|
760
|
+
let baselineRun;
|
|
761
|
+
if (ctx.opts.compareRunId) {
|
|
762
|
+
baselineRun = loadRun(ctx.opts.compareRunId);
|
|
763
|
+
if (!baselineRun) {
|
|
764
|
+
ctx.warnings.push(`Comparison skipped: run ${ctx.opts.compareRunId} not found`);
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
baselineRun = getLatestRun(ctx.opts.path);
|
|
770
|
+
if (!baselineRun) {
|
|
771
|
+
ctx.warnings.push('Comparison skipped: no previous runs found');
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// Build baseline snapshot
|
|
776
|
+
const baselineSnapshot = {
|
|
777
|
+
runId: baselineRun.manifest.runId,
|
|
778
|
+
timestamp: baselineRun.manifest.timestamp,
|
|
779
|
+
callsites: baselineRun.data.inferenceMap?.callsites || [],
|
|
780
|
+
insights: baselineRun.data.insights,
|
|
781
|
+
};
|
|
782
|
+
// Perform comparison
|
|
783
|
+
ctx.comparison = compareSnapshots(baselineSnapshot, currentSnapshot);
|
|
784
|
+
// Log summary for progress
|
|
785
|
+
const summary = formatComparisonSummary(ctx.comparison);
|
|
786
|
+
onProgress?.({ phase: 'generating', detail: `compared with ${baselineRun.manifest.runId.slice(0, 8)}` });
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case 'predict': {
|
|
790
|
+
// v1.5: Generate deploy-time latency predictions
|
|
791
|
+
if (!ctx.inferenceMap) {
|
|
792
|
+
ctx.warnings.push('Prediction skipped: no inference map available');
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
// Generate predictions based on inference points
|
|
796
|
+
const predictionResult = generatePredictions(ctx.inferenceMap, 0, // Historical run count (can be enhanced later with actual history)
|
|
797
|
+
{ targetP95: ctx.opts.targetP95 });
|
|
798
|
+
ctx.prediction = predictionResult;
|
|
799
|
+
// Log summary for progress
|
|
800
|
+
const riskCount = predictionResult.summary.highRiskCount + predictionResult.summary.mediumRiskCount;
|
|
801
|
+
onProgress?.({
|
|
802
|
+
phase: 'generating',
|
|
803
|
+
detail: `${predictionResult.predictions.length} predictions, ${riskCount} at risk`,
|
|
804
|
+
});
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case 'counterfactuals': {
|
|
808
|
+
// v1.5: Generate what-if optimization scenarios
|
|
809
|
+
if (!ctx.inferenceMap) {
|
|
810
|
+
ctx.warnings.push('Counterfactuals skipped: no inference map available');
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
// Generate counterfactual insights
|
|
814
|
+
const counterfactualResult = generateCounterfactuals(ctx.inferenceMap);
|
|
815
|
+
ctx.counterfactuals = counterfactualResult;
|
|
816
|
+
// Log summary for progress
|
|
817
|
+
onProgress?.({
|
|
818
|
+
phase: 'generating',
|
|
819
|
+
detail: `${counterfactualResult.summary.totalOpportunities} optimization opportunities`,
|
|
820
|
+
});
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function buildInferenceMap(root, callsites, metadata = {}) {
|
|
826
|
+
const providers = [...new Set(callsites.map(c => c.provider).filter(Boolean))];
|
|
827
|
+
const models = [...new Set(callsites.map(c => c.model).filter(Boolean))];
|
|
828
|
+
const patternCounts = {};
|
|
829
|
+
for (const cs of callsites) {
|
|
830
|
+
for (const [pattern, enabled] of Object.entries(cs.patterns)) {
|
|
831
|
+
if (enabled) {
|
|
832
|
+
patternCounts[pattern] = (patternCounts[pattern] || 0) + 1;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
version: '0.1', // InferenceMap schema version (not CLI version)
|
|
838
|
+
root,
|
|
839
|
+
generatedAt: new Date().toISOString(),
|
|
840
|
+
metadata: {
|
|
841
|
+
absolutePath: resolve(root),
|
|
842
|
+
promptId: metadata.promptId || 'peak-performance',
|
|
843
|
+
promptVersion: metadata.promptVersion,
|
|
844
|
+
templatesVersion: VERSION, // CLI version for audit trail
|
|
845
|
+
llmProvider: metadata.llmUsed ? 'anthropic' : 'none',
|
|
846
|
+
llmModel: metadata.llmUsed ? 'claude-sonnet-4-20250514' : undefined,
|
|
847
|
+
},
|
|
848
|
+
summary: {
|
|
849
|
+
totalCallsites: callsites.length,
|
|
850
|
+
providers,
|
|
851
|
+
models,
|
|
852
|
+
patterns: patternCounts,
|
|
853
|
+
},
|
|
854
|
+
callsites,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
// =============================================================================
|
|
858
|
+
// PUBLIC API
|
|
859
|
+
// =============================================================================
|
|
860
|
+
export class Agent {
|
|
861
|
+
callbacks;
|
|
862
|
+
constructor(callbacks = {}) {
|
|
863
|
+
this.callbacks = callbacks;
|
|
864
|
+
}
|
|
865
|
+
async run(opts) {
|
|
866
|
+
const planResult = plan(opts);
|
|
867
|
+
const { plan: executionPlan, runId, canResume, runDir } = planResult;
|
|
868
|
+
// Notify if resuming from cache
|
|
869
|
+
if (canResume) {
|
|
870
|
+
this.callbacks.onResumed?.(runId);
|
|
871
|
+
}
|
|
872
|
+
this.callbacks.onPlanReady?.(executionPlan);
|
|
873
|
+
const ctx = {
|
|
874
|
+
opts,
|
|
875
|
+
runId,
|
|
876
|
+
resumed: canResume,
|
|
877
|
+
warnings: [],
|
|
878
|
+
};
|
|
879
|
+
const results = [];
|
|
880
|
+
// Load templates once (not needed if resuming)
|
|
881
|
+
const templates = canResume ? [] : await loadTemplates({ offline: opts.offline });
|
|
882
|
+
for (const task of executionPlan.tasks) {
|
|
883
|
+
this.callbacks.onTaskStart?.(task);
|
|
884
|
+
const startTime = Date.now();
|
|
885
|
+
try {
|
|
886
|
+
await executeTask(task, ctx, templates, runDir, this.callbacks.onProgress);
|
|
887
|
+
const result = {
|
|
888
|
+
taskId: task.id,
|
|
889
|
+
status: 'success',
|
|
890
|
+
durationMs: Date.now() - startTime,
|
|
891
|
+
};
|
|
892
|
+
results.push(result);
|
|
893
|
+
this.callbacks.onTaskComplete?.(task, result);
|
|
894
|
+
}
|
|
895
|
+
catch (error) {
|
|
896
|
+
const result = {
|
|
897
|
+
taskId: task.id,
|
|
898
|
+
status: 'failed',
|
|
899
|
+
error: error instanceof Error ? error.message : String(error),
|
|
900
|
+
durationMs: Date.now() - startTime,
|
|
901
|
+
};
|
|
902
|
+
results.push(result);
|
|
903
|
+
this.callbacks.onTaskComplete?.(task, result);
|
|
904
|
+
// Critical tasks abort execution only if not partial-safe
|
|
905
|
+
if (['scan'].includes(task.type) && task.description !== 'Load cached results') {
|
|
906
|
+
throw error;
|
|
907
|
+
}
|
|
908
|
+
// analyze and parse_events can fail gracefully (partial state)
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// Notify if there were warnings (partial state)
|
|
912
|
+
if (ctx.warnings.length > 0) {
|
|
913
|
+
this.callbacks.onPartial?.(ctx.warnings);
|
|
914
|
+
}
|
|
915
|
+
// Generate human-friendly report filename
|
|
916
|
+
const absolutePath = ctx.inferenceMap?.metadata?.absolutePath || opts.path;
|
|
917
|
+
const projectName = absolutePath.split('/').filter(Boolean).pop() || 'project';
|
|
918
|
+
const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').substring(0, 50);
|
|
919
|
+
const reportFileName = ctx.htmlContent
|
|
920
|
+
? `.peakinfer/${projectSlug}_peakinfer_report.html`
|
|
921
|
+
: undefined;
|
|
922
|
+
const agentResults = {
|
|
923
|
+
mode: executionPlan.mode,
|
|
924
|
+
runId,
|
|
925
|
+
resumed: canResume,
|
|
926
|
+
scanResult: ctx.scanResult,
|
|
927
|
+
callsites: ctx.callsites,
|
|
928
|
+
events: ctx.events,
|
|
929
|
+
runtimeSummary: ctx.runtimeSummary,
|
|
930
|
+
joined: ctx.joined,
|
|
931
|
+
insights: ctx.insights || [],
|
|
932
|
+
impactSummary: ctx.impactSummary,
|
|
933
|
+
inferenceMap: ctx.inferenceMap,
|
|
934
|
+
staticAnalysis: ctx.staticAnalysis,
|
|
935
|
+
comparison: ctx.comparison, // v1.5: Historical comparison
|
|
936
|
+
prediction: ctx.prediction, // v1.5: Deploy-time predictions
|
|
937
|
+
counterfactuals: ctx.counterfactuals, // v1.5: What-if scenarios
|
|
938
|
+
htmlPath: reportFileName,
|
|
939
|
+
pdfPath: ctx.pdfPath,
|
|
940
|
+
warnings: ctx.warnings.length > 0 ? ctx.warnings : undefined,
|
|
941
|
+
};
|
|
942
|
+
// Handle --out: write output to file
|
|
943
|
+
if (opts.out) {
|
|
944
|
+
const outputData = {
|
|
945
|
+
schema: 'peakinfer-analysis',
|
|
946
|
+
version: '1.0', // Analysis export format version
|
|
947
|
+
cliVersion: VERSION,
|
|
948
|
+
mode: agentResults.mode,
|
|
949
|
+
runId: agentResults.runId,
|
|
950
|
+
timestamp: new Date().toISOString(),
|
|
951
|
+
inferenceMap: agentResults.inferenceMap,
|
|
952
|
+
insights: agentResults.insights,
|
|
953
|
+
impactSummary: agentResults.impactSummary,
|
|
954
|
+
comparison: agentResults.comparison,
|
|
955
|
+
prediction: agentResults.prediction,
|
|
956
|
+
counterfactuals: agentResults.counterfactuals,
|
|
957
|
+
runtimeSummary: agentResults.runtimeSummary,
|
|
958
|
+
};
|
|
959
|
+
writeFileSync(opts.out, JSON.stringify(outputData, null, 2));
|
|
960
|
+
}
|
|
961
|
+
this.callbacks.onComplete?.(agentResults);
|
|
962
|
+
return agentResults;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
//# sourceMappingURL=agent.js.map
|