@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
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Commands (v1.6)
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing insight templates:
|
|
5
|
+
* - list: List available templates with optional category filter
|
|
6
|
+
* - info: Show detailed template information
|
|
7
|
+
* - roi: Calculate ROI for a template
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { loadTemplates, getTemplate, loadOptimizationTemplates, getOptimizationTemplate } from '../templates.js';
|
|
12
|
+
import type { InsightTemplate, OptimizationTemplate } from '../types.js';
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// HELPERS
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Format category with color indicator
|
|
20
|
+
*/
|
|
21
|
+
function formatCategory(category: string): string {
|
|
22
|
+
return `[${category}]`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format severity with indicator
|
|
27
|
+
*/
|
|
28
|
+
function formatSeverity(severity: string): string {
|
|
29
|
+
const indicators: Record<string, string> = {
|
|
30
|
+
critical: '[!]',
|
|
31
|
+
warning: '[*]',
|
|
32
|
+
info: '[-]',
|
|
33
|
+
};
|
|
34
|
+
return `${indicators[severity] || '[ ]'} ${severity}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Display templates in a formatted list
|
|
39
|
+
*/
|
|
40
|
+
function displayTemplateList(templates: InsightTemplate[]): void {
|
|
41
|
+
if (templates.length === 0) {
|
|
42
|
+
console.log('No templates found.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`\n${templates.length} template${templates.length !== 1 ? 's' : ''} available:\n`);
|
|
47
|
+
|
|
48
|
+
// Group by category
|
|
49
|
+
const byCategory = new Map<string, InsightTemplate[]>();
|
|
50
|
+
for (const t of templates) {
|
|
51
|
+
if (!byCategory.has(t.category)) {
|
|
52
|
+
byCategory.set(t.category, []);
|
|
53
|
+
}
|
|
54
|
+
byCategory.get(t.category)!.push(t);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const [category, categoryTemplates] of byCategory) {
|
|
58
|
+
console.log(`${formatCategory(category)}`);
|
|
59
|
+
for (const t of categoryTemplates) {
|
|
60
|
+
console.log(` ${formatSeverity(t.severity)} ${t.id}`);
|
|
61
|
+
console.log(` ${t.name}`);
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Display detailed template information
|
|
69
|
+
*/
|
|
70
|
+
function displayTemplateInfo(template: InsightTemplate): void {
|
|
71
|
+
console.log(`\n${template.name} (${template.id})`);
|
|
72
|
+
console.log('═'.repeat(60));
|
|
73
|
+
console.log(`Version: ${template.version}`);
|
|
74
|
+
console.log(`Category: ${formatCategory(template.category)}`);
|
|
75
|
+
if (template.layer) {
|
|
76
|
+
console.log(`Layer: [${template.layer}]`);
|
|
77
|
+
}
|
|
78
|
+
console.log(`Severity: ${formatSeverity(template.severity)}`);
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('Match Scope:', template.match.scope);
|
|
81
|
+
console.log('Conditions:');
|
|
82
|
+
for (const cond of template.match.conditions) {
|
|
83
|
+
console.log(` - ${cond.field} ${cond.op} ${cond.value ?? cond.pattern ?? ''}`);
|
|
84
|
+
}
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log('Output:');
|
|
87
|
+
console.log(` Headline: ${template.output.headline}`);
|
|
88
|
+
console.log(` Evidence: ${template.output.evidence}`);
|
|
89
|
+
|
|
90
|
+
if (template.defaults && Object.keys(template.defaults).length > 0) {
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log('Defaults:');
|
|
93
|
+
for (const [key, value] of Object.entries(template.defaults)) {
|
|
94
|
+
console.log(` ${key}: ${value}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// OPTIMIZATION TEMPLATE DISPLAY HELPERS (v1.8 - Inference Squeeze Guide)
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Display optimization templates in a formatted list
|
|
105
|
+
*/
|
|
106
|
+
function displayOptimizationList(templates: OptimizationTemplate[]): void {
|
|
107
|
+
if (templates.length === 0) {
|
|
108
|
+
console.log('No optimization templates found.');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\n${templates.length} optimization template${templates.length !== 1 ? 's' : ''} available:\n`);
|
|
113
|
+
|
|
114
|
+
// Group by category
|
|
115
|
+
const byCategory = new Map<string, OptimizationTemplate[]>();
|
|
116
|
+
for (const t of templates) {
|
|
117
|
+
if (!byCategory.has(t.category)) {
|
|
118
|
+
byCategory.set(t.category, []);
|
|
119
|
+
}
|
|
120
|
+
byCategory.get(t.category)!.push(t);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const [category, categoryTemplates] of byCategory) {
|
|
124
|
+
console.log(`[${category}]`);
|
|
125
|
+
for (const t of categoryTemplates) {
|
|
126
|
+
const risk = t.optimization.risk_level === 'high' ? '[!]' : t.optimization.risk_level === 'medium' ? '[*]' : '[-]';
|
|
127
|
+
console.log(` ${risk} ${t.id}`);
|
|
128
|
+
console.log(` ${t.name}`);
|
|
129
|
+
if (t.optimization.expected_cost_reduction) {
|
|
130
|
+
console.log(` Cost: ${t.optimization.expected_cost_reduction} | Effort: ${t.optimization.effort_estimate}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Display detailed optimization template information
|
|
139
|
+
*/
|
|
140
|
+
function displayOptimizationInfo(template: OptimizationTemplate): void {
|
|
141
|
+
console.log(`\n${template.name} (${template.id})`);
|
|
142
|
+
console.log('═'.repeat(70));
|
|
143
|
+
console.log(`Category: [${template.category}]`);
|
|
144
|
+
console.log(`Confidence: ${(template.confidence * 100).toFixed(0)}%`);
|
|
145
|
+
if (template.success_count) {
|
|
146
|
+
console.log(`Success: ${template.success_count} implementations`);
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
|
|
150
|
+
console.log('Optimization:');
|
|
151
|
+
console.log(` Technique: ${template.optimization.technique}`);
|
|
152
|
+
if (template.optimization.expected_cost_reduction) {
|
|
153
|
+
console.log(` Cost Reduction: ${template.optimization.expected_cost_reduction}`);
|
|
154
|
+
}
|
|
155
|
+
if (template.optimization.expected_latency_improvement) {
|
|
156
|
+
console.log(` Latency Improvement: ${template.optimization.expected_latency_improvement}`);
|
|
157
|
+
}
|
|
158
|
+
if (template.optimization.expected_throughput_improvement) {
|
|
159
|
+
console.log(` Throughput Improvement: ${template.optimization.expected_throughput_improvement}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(` Effort: ${template.optimization.effort_estimate}`);
|
|
162
|
+
console.log(` Risk: ${template.optimization.risk_level}`);
|
|
163
|
+
console.log('');
|
|
164
|
+
|
|
165
|
+
if (template.implementation?.prerequisites) {
|
|
166
|
+
console.log('Prerequisites:');
|
|
167
|
+
for (const prereq of template.implementation.prerequisites) {
|
|
168
|
+
console.log(` - ${prereq.requirement}`);
|
|
169
|
+
}
|
|
170
|
+
console.log('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (template.implementation?.automated_steps) {
|
|
174
|
+
console.log('Implementation Steps:');
|
|
175
|
+
for (const step of template.implementation.automated_steps) {
|
|
176
|
+
console.log(` ${step.step_id}: ${step.name}`);
|
|
177
|
+
}
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (template.monitoring?.key_metrics) {
|
|
182
|
+
console.log('Key Metrics:');
|
|
183
|
+
for (const metric of template.monitoring.key_metrics) {
|
|
184
|
+
console.log(` - ${metric.metric}: target ${metric.target}`);
|
|
185
|
+
}
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (template.economics?.implementation_cost) {
|
|
190
|
+
console.log('Implementation Cost:');
|
|
191
|
+
if (template.economics.implementation_cost.engineering_hours) {
|
|
192
|
+
console.log(` Engineering Hours: ${template.economics.implementation_cost.engineering_hours}`);
|
|
193
|
+
}
|
|
194
|
+
console.log(` Total Cost: $${template.economics.implementation_cost.total_cost.toLocaleString()}`);
|
|
195
|
+
console.log('');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log('Note: ROI estimates are indicative. Actual results depend on your environment.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Calculate and display ROI for a template
|
|
203
|
+
*/
|
|
204
|
+
function displayTemplateROI(template: InsightTemplate, monthlyCost?: number): void {
|
|
205
|
+
console.log(`\nROI Analysis: ${template.name}`);
|
|
206
|
+
console.log('═'.repeat(60));
|
|
207
|
+
|
|
208
|
+
// Use defaults or provided values
|
|
209
|
+
const defaults = template.defaults || {};
|
|
210
|
+
const baseCost = monthlyCost || defaults.monthly_cost || 10000;
|
|
211
|
+
|
|
212
|
+
// Estimate savings based on category
|
|
213
|
+
let savingsPercent = 0;
|
|
214
|
+
let savingsDescription = '';
|
|
215
|
+
|
|
216
|
+
switch (template.category) {
|
|
217
|
+
case 'cost':
|
|
218
|
+
savingsPercent = defaults.estimated_savings_percent || 20;
|
|
219
|
+
savingsDescription = 'Direct cost reduction';
|
|
220
|
+
break;
|
|
221
|
+
case 'latency':
|
|
222
|
+
savingsPercent = defaults.latency_improvement_percent || 30;
|
|
223
|
+
savingsDescription = 'User time saved (latency reduction)';
|
|
224
|
+
break;
|
|
225
|
+
case 'reliability':
|
|
226
|
+
savingsPercent = defaults.failure_reduction_percent || 15;
|
|
227
|
+
savingsDescription = 'Avoided failures and retries';
|
|
228
|
+
break;
|
|
229
|
+
case 'throughput':
|
|
230
|
+
savingsPercent = defaults.throughput_improvement_percent || 25;
|
|
231
|
+
savingsDescription = 'Capacity improvement';
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
savingsPercent = 10;
|
|
235
|
+
savingsDescription = 'General optimization';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const monthlySavings = Math.round(baseCost * (savingsPercent / 100));
|
|
239
|
+
const annualSavings = monthlySavings * 12;
|
|
240
|
+
|
|
241
|
+
console.log(`Base Monthly Cost: $${baseCost.toLocaleString()}`);
|
|
242
|
+
console.log(`Category: ${template.category}`);
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log(`Estimated Impact: ${savingsPercent}% ${savingsDescription}`);
|
|
245
|
+
console.log(`Monthly Savings: $${monthlySavings.toLocaleString()}`);
|
|
246
|
+
console.log(`Annual Savings: $${annualSavings.toLocaleString()}`);
|
|
247
|
+
console.log('');
|
|
248
|
+
console.log('Note: Estimates based on typical scenarios. Actual results may vary.');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// =============================================================================
|
|
252
|
+
// COMMANDS
|
|
253
|
+
// =============================================================================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Register template commands
|
|
257
|
+
*/
|
|
258
|
+
export function registerTemplateCommands(program: Command): void {
|
|
259
|
+
const templateCmd = program
|
|
260
|
+
.command('template')
|
|
261
|
+
.description('manage insight templates');
|
|
262
|
+
|
|
263
|
+
// List templates
|
|
264
|
+
templateCmd
|
|
265
|
+
.command('list')
|
|
266
|
+
.description('list available templates')
|
|
267
|
+
.option('--category <cat>', 'filter by category (cost, latency, reliability, throughput, drift)')
|
|
268
|
+
.option('--layer <layer>', 'filter by stack layer (application, api, gateway, runtime, model, hardware)')
|
|
269
|
+
.option('--offline', 'use cached templates only')
|
|
270
|
+
.action(async (options: { category?: string; layer?: string; offline?: boolean }) => {
|
|
271
|
+
try {
|
|
272
|
+
const templates = await loadTemplates({ offline: options.offline });
|
|
273
|
+
|
|
274
|
+
let filtered = templates;
|
|
275
|
+
|
|
276
|
+
// Filter by category if specified
|
|
277
|
+
if (options.category) {
|
|
278
|
+
filtered = filtered.filter(t => t.category === options.category);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// v1.8: Filter by layer if specified
|
|
282
|
+
if (options.layer) {
|
|
283
|
+
filtered = filtered.filter(t => t.layer === options.layer);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
displayTemplateList(filtered);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to load templates');
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Template info
|
|
294
|
+
templateCmd
|
|
295
|
+
.command('info')
|
|
296
|
+
.description('show template details')
|
|
297
|
+
.argument('<id>', 'template ID')
|
|
298
|
+
.option('--offline', 'use cached templates only')
|
|
299
|
+
.action(async (id: string, options: { offline?: boolean }) => {
|
|
300
|
+
try {
|
|
301
|
+
const template = await getTemplate(id, { offline: options.offline });
|
|
302
|
+
|
|
303
|
+
if (!template) {
|
|
304
|
+
console.error(`Template not found: ${id}`);
|
|
305
|
+
console.log('\nRun "peakinfer template list" to see available templates.');
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
displayTemplateInfo(template);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to load template');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Template ROI
|
|
317
|
+
templateCmd
|
|
318
|
+
.command('roi')
|
|
319
|
+
.description('calculate ROI for a template')
|
|
320
|
+
.argument('<id>', 'template ID')
|
|
321
|
+
.option('--monthly-cost <amount>', 'your monthly inference cost in USD', parseFloat)
|
|
322
|
+
.option('--offline', 'use cached templates only')
|
|
323
|
+
.action(async (id: string, options: { monthlyCost?: number; offline?: boolean }) => {
|
|
324
|
+
try {
|
|
325
|
+
const template = await getTemplate(id, { offline: options.offline });
|
|
326
|
+
|
|
327
|
+
if (!template) {
|
|
328
|
+
console.error(`Template not found: ${id}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
displayTemplateROI(template, options.monthlyCost);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to calculate ROI');
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ==========================================================================
|
|
340
|
+
// OPTIMIZATION TEMPLATES (v1.8 - Inference Squeeze Guide)
|
|
341
|
+
// ==========================================================================
|
|
342
|
+
|
|
343
|
+
// List optimization templates
|
|
344
|
+
templateCmd
|
|
345
|
+
.command('optimizations')
|
|
346
|
+
.description('list community optimization templates (Inference Squeeze Guide)')
|
|
347
|
+
.option('--category <cat>', 'filter by category (runtime_optimization, batching_optimization, memory_optimization, application_optimization, cost_optimization, monitoring, scaling)')
|
|
348
|
+
.action((options: { category?: string }) => {
|
|
349
|
+
try {
|
|
350
|
+
let templates = loadOptimizationTemplates();
|
|
351
|
+
|
|
352
|
+
// Filter by category if specified
|
|
353
|
+
if (options.category) {
|
|
354
|
+
templates = templates.filter(t => t.category === options.category);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
displayOptimizationList(templates);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to load optimization templates');
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Optimization template info
|
|
365
|
+
templateCmd
|
|
366
|
+
.command('optimization')
|
|
367
|
+
.description('show optimization template details')
|
|
368
|
+
.argument('<id>', 'optimization template ID')
|
|
369
|
+
.action((id: string) => {
|
|
370
|
+
try {
|
|
371
|
+
const template = getOptimizationTemplate(id);
|
|
372
|
+
|
|
373
|
+
if (!template) {
|
|
374
|
+
console.error(`Optimization template not found: ${id}`);
|
|
375
|
+
console.log('\nRun "peakinfer template optimizations" to see available templates.');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
displayOptimizationInfo(template);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to load optimization template');
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate-Map Command (v1.9.3)
|
|
3
|
+
*
|
|
4
|
+
* CLI command for validating InferenceMap JSON files against the v0.1 schema.
|
|
5
|
+
* Per PRD v1.9.3: `peakinfer validate-map ./analysis.json`
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { resolve, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
interface ValidationResult {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
errors: ValidationError[];
|
|
20
|
+
warnings: ValidationWarning[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ValidationError {
|
|
24
|
+
path: string;
|
|
25
|
+
message: string;
|
|
26
|
+
expected?: string;
|
|
27
|
+
actual?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ValidationWarning {
|
|
31
|
+
path: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// SCHEMA VALIDATION
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load the InferenceMap v0.1 JSON Schema
|
|
41
|
+
*/
|
|
42
|
+
function loadSchema(): object {
|
|
43
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
44
|
+
const __dirname = dirname(__filename);
|
|
45
|
+
const schemaPath = resolve(__dirname, '../../schemas/inference-map.v0.1.json');
|
|
46
|
+
|
|
47
|
+
if (!existsSync(schemaPath)) {
|
|
48
|
+
throw new Error(`Schema not found at ${schemaPath}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return JSON.parse(readFileSync(schemaPath, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate an InferenceMap against the v0.1 schema.
|
|
56
|
+
* Performs structural validation without external dependencies.
|
|
57
|
+
*/
|
|
58
|
+
function validateInferenceMap(data: unknown): ValidationResult {
|
|
59
|
+
const errors: ValidationError[] = [];
|
|
60
|
+
const warnings: ValidationWarning[] = [];
|
|
61
|
+
|
|
62
|
+
// Type guard
|
|
63
|
+
if (typeof data !== 'object' || data === null) {
|
|
64
|
+
errors.push({ path: '$', message: 'InferenceMap must be an object' });
|
|
65
|
+
return { valid: false, errors, warnings };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const map = data as Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
// Required fields
|
|
71
|
+
const requiredFields = ['version', 'root', 'generatedAt', 'summary', 'callsites'];
|
|
72
|
+
for (const field of requiredFields) {
|
|
73
|
+
if (!(field in map)) {
|
|
74
|
+
errors.push({ path: `$.${field}`, message: `Missing required field: ${field}` });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Version check
|
|
79
|
+
if ('version' in map && map.version !== '0.1') {
|
|
80
|
+
errors.push({
|
|
81
|
+
path: '$.version',
|
|
82
|
+
message: 'Invalid version',
|
|
83
|
+
expected: '0.1',
|
|
84
|
+
actual: String(map.version),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Root validation
|
|
89
|
+
if ('root' in map && typeof map.root !== 'string') {
|
|
90
|
+
errors.push({ path: '$.root', message: 'root must be a string' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// generatedAt validation (ISO 8601)
|
|
94
|
+
if ('generatedAt' in map) {
|
|
95
|
+
if (typeof map.generatedAt !== 'string') {
|
|
96
|
+
errors.push({ path: '$.generatedAt', message: 'generatedAt must be a string' });
|
|
97
|
+
} else {
|
|
98
|
+
const date = new Date(map.generatedAt);
|
|
99
|
+
if (isNaN(date.getTime())) {
|
|
100
|
+
errors.push({ path: '$.generatedAt', message: 'generatedAt must be a valid ISO 8601 date-time' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Summary validation
|
|
106
|
+
if ('summary' in map) {
|
|
107
|
+
if (typeof map.summary !== 'object' || map.summary === null) {
|
|
108
|
+
errors.push({ path: '$.summary', message: 'summary must be an object' });
|
|
109
|
+
} else {
|
|
110
|
+
const summary = map.summary as Record<string, unknown>;
|
|
111
|
+
|
|
112
|
+
// Required summary fields
|
|
113
|
+
const summaryRequired = ['totalCallsites', 'providers', 'models', 'patterns'];
|
|
114
|
+
for (const field of summaryRequired) {
|
|
115
|
+
if (!(field in summary)) {
|
|
116
|
+
errors.push({ path: `$.summary.${field}`, message: `Missing required field: ${field}` });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Type checks
|
|
121
|
+
if ('totalCallsites' in summary && typeof summary.totalCallsites !== 'number') {
|
|
122
|
+
errors.push({ path: '$.summary.totalCallsites', message: 'totalCallsites must be a number' });
|
|
123
|
+
}
|
|
124
|
+
if ('providers' in summary && !Array.isArray(summary.providers)) {
|
|
125
|
+
errors.push({ path: '$.summary.providers', message: 'providers must be an array' });
|
|
126
|
+
}
|
|
127
|
+
if ('models' in summary && !Array.isArray(summary.models)) {
|
|
128
|
+
errors.push({ path: '$.summary.models', message: 'models must be an array' });
|
|
129
|
+
}
|
|
130
|
+
if ('patterns' in summary && (typeof summary.patterns !== 'object' || summary.patterns === null)) {
|
|
131
|
+
errors.push({ path: '$.summary.patterns', message: 'patterns must be an object' });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Callsites validation
|
|
137
|
+
if ('callsites' in map) {
|
|
138
|
+
if (!Array.isArray(map.callsites)) {
|
|
139
|
+
errors.push({ path: '$.callsites', message: 'callsites must be an array' });
|
|
140
|
+
} else {
|
|
141
|
+
const callsites = map.callsites as unknown[];
|
|
142
|
+
for (let i = 0; i < callsites.length; i++) {
|
|
143
|
+
const callsite = callsites[i];
|
|
144
|
+
if (typeof callsite !== 'object' || callsite === null) {
|
|
145
|
+
errors.push({ path: `$.callsites[${i}]`, message: 'callsite must be an object' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const cs = callsite as Record<string, unknown>;
|
|
150
|
+
|
|
151
|
+
// Required callsite fields
|
|
152
|
+
const callsiteRequired = ['id', 'file', 'line', 'patterns', 'confidence'];
|
|
153
|
+
for (const field of callsiteRequired) {
|
|
154
|
+
if (!(field in cs)) {
|
|
155
|
+
errors.push({ path: `$.callsites[${i}].${field}`, message: `Missing required field: ${field}` });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Type checks
|
|
160
|
+
if ('id' in cs && typeof cs.id !== 'string') {
|
|
161
|
+
errors.push({ path: `$.callsites[${i}].id`, message: 'id must be a string' });
|
|
162
|
+
}
|
|
163
|
+
if ('file' in cs && typeof cs.file !== 'string') {
|
|
164
|
+
errors.push({ path: `$.callsites[${i}].file`, message: 'file must be a string' });
|
|
165
|
+
}
|
|
166
|
+
if ('line' in cs) {
|
|
167
|
+
if (typeof cs.line !== 'number' || !Number.isInteger(cs.line) || cs.line < 1) {
|
|
168
|
+
errors.push({ path: `$.callsites[${i}].line`, message: 'line must be a positive integer' });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if ('confidence' in cs) {
|
|
172
|
+
if (typeof cs.confidence !== 'number' || cs.confidence < 0 || cs.confidence > 1) {
|
|
173
|
+
errors.push({ path: `$.callsites[${i}].confidence`, message: 'confidence must be a number between 0 and 1' });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if ('patterns' in cs && (typeof cs.patterns !== 'object' || cs.patterns === null)) {
|
|
177
|
+
errors.push({ path: `$.callsites[${i}].patterns`, message: 'patterns must be an object' });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Provider validation (known providers)
|
|
181
|
+
const validProviders = [
|
|
182
|
+
'openai', 'anthropic', 'google', 'cohere', 'mistral',
|
|
183
|
+
'bedrock', 'azure_openai', 'together', 'fireworks',
|
|
184
|
+
'groq', 'replicate', 'perplexity',
|
|
185
|
+
'vllm', 'sglang', 'tgi', 'ollama', 'llamacpp',
|
|
186
|
+
'unknown',
|
|
187
|
+
];
|
|
188
|
+
if ('provider' in cs && cs.provider !== null && typeof cs.provider === 'string') {
|
|
189
|
+
if (!validProviders.includes(cs.provider)) {
|
|
190
|
+
warnings.push({
|
|
191
|
+
path: `$.callsites[${i}].provider`,
|
|
192
|
+
message: `Unknown provider: ${cs.provider}. Known providers: ${validProviders.join(', ')}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Metadata validation (optional)
|
|
201
|
+
if ('metadata' in map && map.metadata !== null) {
|
|
202
|
+
if (typeof map.metadata !== 'object') {
|
|
203
|
+
warnings.push({ path: '$.metadata', message: 'metadata should be an object' });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
valid: errors.length === 0,
|
|
209
|
+
errors,
|
|
210
|
+
warnings,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// OUTPUT FORMATTING
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Format validation results for terminal output
|
|
220
|
+
*/
|
|
221
|
+
function formatResults(result: ValidationResult, filePath: string): void {
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log(`InferenceMap Validation: ${filePath}`);
|
|
224
|
+
console.log('─'.repeat(60));
|
|
225
|
+
|
|
226
|
+
if (result.valid) {
|
|
227
|
+
console.log('✓ Valid InferenceMap v0.1');
|
|
228
|
+
} else {
|
|
229
|
+
console.log('✗ Invalid InferenceMap');
|
|
230
|
+
}
|
|
231
|
+
console.log('');
|
|
232
|
+
|
|
233
|
+
if (result.errors.length > 0) {
|
|
234
|
+
console.log(`Errors (${result.errors.length}):`);
|
|
235
|
+
for (const error of result.errors) {
|
|
236
|
+
console.log(` ✗ ${error.path}: ${error.message}`);
|
|
237
|
+
if (error.expected !== undefined) {
|
|
238
|
+
console.log(` Expected: ${error.expected}`);
|
|
239
|
+
console.log(` Actual: ${error.actual}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (result.warnings.length > 0) {
|
|
246
|
+
console.log(`Warnings (${result.warnings.length}):`);
|
|
247
|
+
for (const warning of result.warnings) {
|
|
248
|
+
console.log(` ⚠ ${warning.path}: ${warning.message}`);
|
|
249
|
+
}
|
|
250
|
+
console.log('');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (result.valid && result.warnings.length === 0) {
|
|
254
|
+
console.log('No issues found.');
|
|
255
|
+
console.log('');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// =============================================================================
|
|
260
|
+
// COMMAND REGISTRATION
|
|
261
|
+
// =============================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Register the validate-map command
|
|
265
|
+
*/
|
|
266
|
+
export function registerValidateMapCommand(program: Command): void {
|
|
267
|
+
program
|
|
268
|
+
.command('validate-map')
|
|
269
|
+
.description('validate an InferenceMap JSON file against the v0.1 schema')
|
|
270
|
+
.argument('<file>', 'path to InferenceMap JSON file')
|
|
271
|
+
.option('--json', 'output validation results as JSON')
|
|
272
|
+
.option('--quiet', 'only output errors (exit code indicates validity)')
|
|
273
|
+
.action((file: string, options: { json?: boolean; quiet?: boolean }) => {
|
|
274
|
+
try {
|
|
275
|
+
// Resolve file path
|
|
276
|
+
const filePath = resolve(file);
|
|
277
|
+
|
|
278
|
+
// Check file exists
|
|
279
|
+
if (!existsSync(filePath)) {
|
|
280
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Read and parse file
|
|
285
|
+
let data: unknown;
|
|
286
|
+
try {
|
|
287
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
288
|
+
data = JSON.parse(content);
|
|
289
|
+
} catch (parseError) {
|
|
290
|
+
if (options.json) {
|
|
291
|
+
console.log(JSON.stringify({
|
|
292
|
+
valid: false,
|
|
293
|
+
errors: [{
|
|
294
|
+
path: '$',
|
|
295
|
+
message: `Invalid JSON: ${parseError instanceof Error ? parseError.message : 'Parse error'}`,
|
|
296
|
+
}],
|
|
297
|
+
warnings: [],
|
|
298
|
+
}, null, 2));
|
|
299
|
+
} else if (!options.quiet) {
|
|
300
|
+
console.error(`Error: Invalid JSON in ${file}`);
|
|
301
|
+
console.error(parseError instanceof Error ? parseError.message : 'Parse error');
|
|
302
|
+
}
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Validate
|
|
307
|
+
const result = validateInferenceMap(data);
|
|
308
|
+
|
|
309
|
+
// Output results
|
|
310
|
+
if (options.json) {
|
|
311
|
+
console.log(JSON.stringify(result, null, 2));
|
|
312
|
+
} else if (!options.quiet) {
|
|
313
|
+
formatResults(result, file);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Exit with appropriate code
|
|
317
|
+
process.exit(result.valid ? 0 : 1);
|
|
318
|
+
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('Error:', error instanceof Error ? error.message : 'Validation failed');
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|