@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/src/history.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Storage Module (v1.5)
|
|
3
|
+
*
|
|
4
|
+
* Enables persistent storage of analysis runs for:
|
|
5
|
+
* - Historical comparison (Feature 2)
|
|
6
|
+
* - Deploy-time prediction (Feature 3)
|
|
7
|
+
*
|
|
8
|
+
* Directory structure:
|
|
9
|
+
* .peakinfer/
|
|
10
|
+
* └── history/
|
|
11
|
+
* ├── index.json # Global index of all runs
|
|
12
|
+
* └── <runId>/ # Individual run storage
|
|
13
|
+
* ├── manifest.json
|
|
14
|
+
* ├── inference-map.json
|
|
15
|
+
* └── analysis.json
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createHash, randomUUID } from 'crypto';
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync } from 'fs';
|
|
20
|
+
import { join, resolve } from 'path';
|
|
21
|
+
import type {
|
|
22
|
+
HistoryManifest,
|
|
23
|
+
HistoryIndex,
|
|
24
|
+
AnalysisType,
|
|
25
|
+
InferenceMap,
|
|
26
|
+
Insight,
|
|
27
|
+
JoinedOutput,
|
|
28
|
+
RuntimeSummary,
|
|
29
|
+
} from './types.js';
|
|
30
|
+
import { VERSION } from './version.js';
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// CONSTANTS
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
const HISTORY_DIR = '.peakinfer/history';
|
|
37
|
+
const INDEX_FILE = 'index.json';
|
|
38
|
+
const HISTORY_VERSION = '1.0';
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// TYPES
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
export interface AnalysisData {
|
|
45
|
+
inferenceMap?: InferenceMap;
|
|
46
|
+
insights?: Insight[];
|
|
47
|
+
joined?: JoinedOutput;
|
|
48
|
+
runtime?: RuntimeSummary;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SaveRunOptions {
|
|
52
|
+
path: string;
|
|
53
|
+
analysisType: AnalysisType;
|
|
54
|
+
data: AnalysisData;
|
|
55
|
+
durationMs?: number;
|
|
56
|
+
htmlPath?: string;
|
|
57
|
+
pdfPath?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface LoadedRun {
|
|
61
|
+
manifest: HistoryManifest;
|
|
62
|
+
data: AnalysisData;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// HELPERS
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the history directory path (relative to cwd or specified base)
|
|
71
|
+
*/
|
|
72
|
+
export function getHistoryDir(baseDir: string = '.'): string {
|
|
73
|
+
return join(baseDir, HISTORY_DIR);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a deterministic hash from a normalized path.
|
|
78
|
+
* Used for efficient lookup of runs for a specific project.
|
|
79
|
+
*/
|
|
80
|
+
export function hashPath(path: string): string {
|
|
81
|
+
const normalized = resolve(path).toLowerCase();
|
|
82
|
+
return createHash('sha256').update(normalized).digest('hex').slice(0, 12);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generate a unique run ID (timestamp-based for chronological ordering)
|
|
87
|
+
*/
|
|
88
|
+
function generateHistoryRunId(): string {
|
|
89
|
+
const timestamp = Date.now().toString(36); // Base36 timestamp
|
|
90
|
+
const random = randomUUID().slice(0, 8); // Short random suffix
|
|
91
|
+
return `${timestamp}-${random}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Ensure directory exists
|
|
96
|
+
*/
|
|
97
|
+
function ensureDir(dir: string): void {
|
|
98
|
+
if (!existsSync(dir)) {
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Write JSON file with pretty printing
|
|
105
|
+
*/
|
|
106
|
+
function writeJSON(filePath: string, data: unknown): void {
|
|
107
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read JSON file safely
|
|
112
|
+
*/
|
|
113
|
+
function readJSON<T>(filePath: string): T | null {
|
|
114
|
+
try {
|
|
115
|
+
if (!existsSync(filePath)) return null;
|
|
116
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
117
|
+
return JSON.parse(content) as T;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// =============================================================================
|
|
124
|
+
// INDEX MANAGEMENT
|
|
125
|
+
// =============================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Load the history index
|
|
129
|
+
*/
|
|
130
|
+
function loadIndex(historyDir: string): HistoryIndex {
|
|
131
|
+
const indexPath = join(historyDir, INDEX_FILE);
|
|
132
|
+
const existing = readJSON<HistoryIndex>(indexPath);
|
|
133
|
+
|
|
134
|
+
if (existing) {
|
|
135
|
+
return existing;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Return empty index
|
|
139
|
+
return {
|
|
140
|
+
version: HISTORY_VERSION,
|
|
141
|
+
lastUpdated: new Date().toISOString(),
|
|
142
|
+
runs: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Save the history index
|
|
148
|
+
*/
|
|
149
|
+
function saveIndex(historyDir: string, index: HistoryIndex): void {
|
|
150
|
+
ensureDir(historyDir);
|
|
151
|
+
const indexPath = join(historyDir, INDEX_FILE);
|
|
152
|
+
index.lastUpdated = new Date().toISOString();
|
|
153
|
+
writeJSON(indexPath, index);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// PUBLIC API
|
|
158
|
+
// =============================================================================
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Save an analysis run to history.
|
|
162
|
+
* Returns the run ID for reference.
|
|
163
|
+
*/
|
|
164
|
+
export function saveRun(options: SaveRunOptions, baseDir: string = '.'): string {
|
|
165
|
+
const historyDir = getHistoryDir(baseDir);
|
|
166
|
+
const runId = generateHistoryRunId();
|
|
167
|
+
const runDir = join(historyDir, runId);
|
|
168
|
+
const pathHash = hashPath(options.path);
|
|
169
|
+
|
|
170
|
+
ensureDir(runDir);
|
|
171
|
+
|
|
172
|
+
// Calculate summary metrics
|
|
173
|
+
const inferencePointCount = options.data.inferenceMap?.callsites?.length ?? 0;
|
|
174
|
+
const eventCount = options.data.runtime?.totalEvents;
|
|
175
|
+
const driftCount = options.data.joined?.drift?.length;
|
|
176
|
+
const insightCount = options.data.insights?.length;
|
|
177
|
+
|
|
178
|
+
// Track saved artifacts
|
|
179
|
+
const artifacts: HistoryManifest['artifacts'] = {};
|
|
180
|
+
|
|
181
|
+
// Save inference map
|
|
182
|
+
if (options.data.inferenceMap) {
|
|
183
|
+
const fileName = 'inference-map.json';
|
|
184
|
+
writeJSON(join(runDir, fileName), options.data.inferenceMap);
|
|
185
|
+
artifacts.inferenceMap = fileName;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Save full analysis data
|
|
189
|
+
const analysisFileName = 'analysis.json';
|
|
190
|
+
writeJSON(join(runDir, analysisFileName), options.data);
|
|
191
|
+
artifacts.analysis = analysisFileName;
|
|
192
|
+
|
|
193
|
+
// Record report paths if provided
|
|
194
|
+
if (options.htmlPath) {
|
|
195
|
+
artifacts.html = options.htmlPath;
|
|
196
|
+
}
|
|
197
|
+
if (options.pdfPath) {
|
|
198
|
+
artifacts.pdf = options.pdfPath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create manifest
|
|
202
|
+
const manifest: HistoryManifest = {
|
|
203
|
+
runId,
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
path: resolve(options.path),
|
|
206
|
+
pathHash,
|
|
207
|
+
analysisType: options.analysisType,
|
|
208
|
+
version: VERSION,
|
|
209
|
+
inferencePointCount,
|
|
210
|
+
eventCount,
|
|
211
|
+
driftCount,
|
|
212
|
+
insightCount,
|
|
213
|
+
durationMs: options.durationMs,
|
|
214
|
+
artifacts,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Save manifest
|
|
218
|
+
writeJSON(join(runDir, 'manifest.json'), manifest);
|
|
219
|
+
|
|
220
|
+
// Update index
|
|
221
|
+
const index = loadIndex(historyDir);
|
|
222
|
+
index.runs.push({
|
|
223
|
+
runId,
|
|
224
|
+
timestamp: manifest.timestamp,
|
|
225
|
+
pathHash,
|
|
226
|
+
analysisType: options.analysisType,
|
|
227
|
+
inferencePointCount,
|
|
228
|
+
});
|
|
229
|
+
saveIndex(historyDir, index);
|
|
230
|
+
|
|
231
|
+
return runId;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Load a specific run by ID.
|
|
236
|
+
*/
|
|
237
|
+
export function loadRun(runId: string, baseDir: string = '.'): LoadedRun | null {
|
|
238
|
+
const historyDir = getHistoryDir(baseDir);
|
|
239
|
+
const runDir = join(historyDir, runId);
|
|
240
|
+
|
|
241
|
+
// Load manifest
|
|
242
|
+
const manifest = readJSON<HistoryManifest>(join(runDir, 'manifest.json'));
|
|
243
|
+
if (!manifest) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Load analysis data
|
|
248
|
+
const data = readJSON<AnalysisData>(join(runDir, 'analysis.json')) ?? {};
|
|
249
|
+
|
|
250
|
+
return { manifest, data };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* List all runs for a specific path (or all runs if no path specified).
|
|
255
|
+
* Returns runs sorted by timestamp (most recent first).
|
|
256
|
+
*/
|
|
257
|
+
export function listRuns(path?: string, baseDir: string = '.'): HistoryManifest[] {
|
|
258
|
+
const historyDir = getHistoryDir(baseDir);
|
|
259
|
+
const index = loadIndex(historyDir);
|
|
260
|
+
|
|
261
|
+
// Filter by path hash if specified
|
|
262
|
+
const pathHash = path ? hashPath(path) : null;
|
|
263
|
+
const filteredRuns = pathHash
|
|
264
|
+
? index.runs.filter(r => r.pathHash === pathHash)
|
|
265
|
+
: index.runs;
|
|
266
|
+
|
|
267
|
+
// Load full manifests for filtered runs
|
|
268
|
+
const manifests: HistoryManifest[] = [];
|
|
269
|
+
for (const run of filteredRuns) {
|
|
270
|
+
const runDir = join(historyDir, run.runId);
|
|
271
|
+
const manifest = readJSON<HistoryManifest>(join(runDir, 'manifest.json'));
|
|
272
|
+
if (manifest) {
|
|
273
|
+
manifests.push(manifest);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Sort by timestamp descending (most recent first)
|
|
278
|
+
manifests.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
279
|
+
|
|
280
|
+
return manifests;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the most recent run for a path.
|
|
285
|
+
* Returns null if no history exists.
|
|
286
|
+
*/
|
|
287
|
+
export function getLatestRun(path: string, baseDir: string = '.'): LoadedRun | null {
|
|
288
|
+
const runs = listRuns(path, baseDir);
|
|
289
|
+
|
|
290
|
+
if (runs.length === 0) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// First run is most recent (already sorted)
|
|
295
|
+
return loadRun(runs[0].runId, baseDir);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Prune old runs, keeping only the most recent N runs per path.
|
|
300
|
+
* Returns the number of runs deleted.
|
|
301
|
+
*/
|
|
302
|
+
export function pruneHistory(keepCount: number = 10, baseDir: string = '.'): number {
|
|
303
|
+
const historyDir = getHistoryDir(baseDir);
|
|
304
|
+
const index = loadIndex(historyDir);
|
|
305
|
+
|
|
306
|
+
// Group runs by pathHash
|
|
307
|
+
const runsByPath = new Map<string, typeof index.runs>();
|
|
308
|
+
for (const run of index.runs) {
|
|
309
|
+
const existing = runsByPath.get(run.pathHash) ?? [];
|
|
310
|
+
existing.push(run);
|
|
311
|
+
runsByPath.set(run.pathHash, existing);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Find runs to delete
|
|
315
|
+
const runsToDelete: string[] = [];
|
|
316
|
+
for (const [, runs] of runsByPath) {
|
|
317
|
+
// Sort by timestamp descending
|
|
318
|
+
runs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
319
|
+
|
|
320
|
+
// Mark excess runs for deletion
|
|
321
|
+
if (runs.length > keepCount) {
|
|
322
|
+
for (let i = keepCount; i < runs.length; i++) {
|
|
323
|
+
runsToDelete.push(runs[i].runId);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Delete run directories
|
|
329
|
+
for (const runId of runsToDelete) {
|
|
330
|
+
const runDir = join(historyDir, runId);
|
|
331
|
+
if (existsSync(runDir)) {
|
|
332
|
+
rmSync(runDir, { recursive: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Update index to remove deleted runs
|
|
337
|
+
if (runsToDelete.length > 0) {
|
|
338
|
+
const deleteSet = new Set(runsToDelete);
|
|
339
|
+
index.runs = index.runs.filter(r => !deleteSet.has(r.runId));
|
|
340
|
+
saveIndex(historyDir, index);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return runsToDelete.length;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Delete a specific run by ID.
|
|
348
|
+
* Returns true if the run was deleted, false if not found.
|
|
349
|
+
*/
|
|
350
|
+
export function deleteRun(runId: string, baseDir: string = '.'): boolean {
|
|
351
|
+
const historyDir = getHistoryDir(baseDir);
|
|
352
|
+
const runDir = join(historyDir, runId);
|
|
353
|
+
|
|
354
|
+
// Check if run exists
|
|
355
|
+
if (!existsSync(runDir)) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Delete the run directory
|
|
360
|
+
rmSync(runDir, { recursive: true });
|
|
361
|
+
|
|
362
|
+
// Update the index
|
|
363
|
+
const index = loadIndex(historyDir);
|
|
364
|
+
const originalLength = index.runs.length;
|
|
365
|
+
index.runs = index.runs.filter(r => r.runId !== runId);
|
|
366
|
+
|
|
367
|
+
if (index.runs.length < originalLength) {
|
|
368
|
+
saveIndex(historyDir, index);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Clear all history (delete everything).
|
|
376
|
+
* Returns the number of runs deleted.
|
|
377
|
+
*/
|
|
378
|
+
export function clearAllHistory(baseDir: string = '.'): number {
|
|
379
|
+
const historyDir = getHistoryDir(baseDir);
|
|
380
|
+
const index = loadIndex(historyDir);
|
|
381
|
+
const count = index.runs.length;
|
|
382
|
+
|
|
383
|
+
// Delete all run directories
|
|
384
|
+
for (const run of index.runs) {
|
|
385
|
+
const runDir = join(historyDir, run.runId);
|
|
386
|
+
if (existsSync(runDir)) {
|
|
387
|
+
rmSync(runDir, { recursive: true });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Reset the index
|
|
392
|
+
const emptyIndex: HistoryIndex = {
|
|
393
|
+
version: HISTORY_VERSION,
|
|
394
|
+
lastUpdated: new Date().toISOString(),
|
|
395
|
+
runs: [],
|
|
396
|
+
};
|
|
397
|
+
saveIndex(historyDir, emptyIndex);
|
|
398
|
+
|
|
399
|
+
return count;
|
|
400
|
+
}
|