@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,125 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseCommand } from '../../src/action/commands.js';
|
|
3
|
+
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// COMMAND PARSING TESTS
|
|
6
|
+
// =============================================================================
|
|
7
|
+
|
|
8
|
+
describe('parseCommand', () => {
|
|
9
|
+
describe('/peakinfer command', () => {
|
|
10
|
+
it('parses /peakinfer as rerun', () => {
|
|
11
|
+
const cmd = parseCommand('/peakinfer');
|
|
12
|
+
expect(cmd).toEqual({ type: 'rerun' });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('parses /peakinfer rerun as rerun', () => {
|
|
16
|
+
const cmd = parseCommand('/peakinfer rerun');
|
|
17
|
+
expect(cmd).toEqual({ type: 'rerun' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('is case insensitive', () => {
|
|
21
|
+
const cmd = parseCommand('/PEAKINFER');
|
|
22
|
+
expect(cmd).toEqual({ type: 'rerun' });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('trims whitespace', () => {
|
|
26
|
+
const cmd = parseCommand(' /peakinfer ');
|
|
27
|
+
expect(cmd).toEqual({ type: 'rerun' });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('/fix command', () => {
|
|
32
|
+
it('parses /fix <id> correctly', () => {
|
|
33
|
+
const cmd = parseCommand('/fix 1');
|
|
34
|
+
expect(cmd).toEqual({ type: 'fix', issueId: 1 });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('parses /fix with larger ids', () => {
|
|
38
|
+
const cmd = parseCommand('/fix 123');
|
|
39
|
+
expect(cmd).toEqual({ type: 'fix', issueId: 123 });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('parses /peakinfer fix <id>', () => {
|
|
43
|
+
const cmd = parseCommand('/peakinfer fix 5');
|
|
44
|
+
expect(cmd).toEqual({ type: 'fix', issueId: 5 });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('parses /fix all as fix-all', () => {
|
|
48
|
+
const cmd = parseCommand('/fix all');
|
|
49
|
+
expect(cmd).toEqual({ type: 'fix-all' });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('parses /peakinfer fix all', () => {
|
|
53
|
+
const cmd = parseCommand('/peakinfer fix all');
|
|
54
|
+
expect(cmd).toEqual({ type: 'fix-all' });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('/dismiss command', () => {
|
|
59
|
+
it('parses /dismiss <id> correctly', () => {
|
|
60
|
+
const cmd = parseCommand('/dismiss 2');
|
|
61
|
+
expect(cmd).toEqual({ type: 'dismiss', issueId: 2 });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('parses /peakinfer dismiss <id>', () => {
|
|
65
|
+
const cmd = parseCommand('/peakinfer dismiss 7');
|
|
66
|
+
expect(cmd).toEqual({ type: 'dismiss', issueId: 7 });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('non-commands', () => {
|
|
71
|
+
it('returns null for regular comments', () => {
|
|
72
|
+
expect(parseCommand('Great work on this PR!')).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns null for partial commands', () => {
|
|
76
|
+
expect(parseCommand('/fix')).toBeNull();
|
|
77
|
+
expect(parseCommand('/dismiss')).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns null for invalid ids', () => {
|
|
81
|
+
expect(parseCommand('/fix abc')).toBeNull();
|
|
82
|
+
expect(parseCommand('/dismiss xyz')).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns null for empty string', () => {
|
|
86
|
+
expect(parseCommand('')).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns null for comments mentioning peakinfer', () => {
|
|
90
|
+
expect(parseCommand('can you run peakinfer again?')).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// COMMAND SCENARIOS
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
describe('Command Scenarios', () => {
|
|
100
|
+
it('user wants to re-run analysis after fixing issues', () => {
|
|
101
|
+
// User pushes fixes, then comments to re-analyze
|
|
102
|
+
const cmd = parseCommand('/peakinfer');
|
|
103
|
+
expect(cmd?.type).toBe('rerun');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('user wants to accept a specific fix', () => {
|
|
107
|
+
// User reviews the top issue and accepts it
|
|
108
|
+
const cmd = parseCommand('/fix 1');
|
|
109
|
+
expect(cmd?.type).toBe('fix');
|
|
110
|
+
expect(cmd?.issueId).toBe(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('user wants to dismiss an issue they disagree with', () => {
|
|
114
|
+
// User thinks the issue is a false positive
|
|
115
|
+
const cmd = parseCommand('/dismiss 3');
|
|
116
|
+
expect(cmd?.type).toBe('dismiss');
|
|
117
|
+
expect(cmd?.issueId).toBe(3);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('user wants to accept all available fixes', () => {
|
|
121
|
+
// User trusts all suggestions and wants to apply them
|
|
122
|
+
const cmd = parseCommand('/fix all');
|
|
123
|
+
expect(cmd?.type).toBe('fix-all');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generatePRComment, generateExhaustedComment } from '../../src/action/comments.js';
|
|
3
|
+
import type { Insight } from '../../src/types.js';
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// HELPER: Create mock insight
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
function createInsight(overrides: Partial<Insight> = {}): Insight {
|
|
10
|
+
return {
|
|
11
|
+
templateId: overrides.templateId || 'test-template',
|
|
12
|
+
headline: overrides.headline || 'Test issue headline',
|
|
13
|
+
evidence: overrides.evidence || 'Test evidence',
|
|
14
|
+
severity: overrides.severity || 'warning',
|
|
15
|
+
category: overrides.category || 'cost',
|
|
16
|
+
location: overrides.location || 'src/test.ts:10',
|
|
17
|
+
recommendation: overrides.recommendation,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// VERDICT TESTS
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
describe('Verdict Logic', () => {
|
|
26
|
+
it('returns "Safe to Merge" when no issues', () => {
|
|
27
|
+
const comment = generatePRComment({
|
|
28
|
+
results: {
|
|
29
|
+
inferenceMap: {
|
|
30
|
+
callsites: [],
|
|
31
|
+
summary: { totalCallsites: 5, providers: ['openai'], models: ['gpt-4'] },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
baseline: null,
|
|
35
|
+
status: 'pass',
|
|
36
|
+
regressions: [],
|
|
37
|
+
newIssues: [],
|
|
38
|
+
changedFiles: [],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(comment).toContain('✅ Safe to Merge');
|
|
42
|
+
expect(comment).toContain('No issues found');
|
|
43
|
+
expect(comment).toContain('5 inference points');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns "Mostly Good" for 1-5 warnings', () => {
|
|
47
|
+
const comment = generatePRComment({
|
|
48
|
+
results: {
|
|
49
|
+
inferenceMap: {
|
|
50
|
+
callsites: [],
|
|
51
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
baseline: null,
|
|
55
|
+
status: 'warning',
|
|
56
|
+
regressions: [],
|
|
57
|
+
newIssues: [
|
|
58
|
+
createInsight({ severity: 'warning', headline: 'Warning 1' }),
|
|
59
|
+
createInsight({ severity: 'warning', headline: 'Warning 2' }),
|
|
60
|
+
],
|
|
61
|
+
changedFiles: [],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(comment).toContain('🟢 Mostly Good');
|
|
65
|
+
expect(comment).toContain('2 optional improvements');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns "Review Recommended" for 1 critical issue', () => {
|
|
69
|
+
const comment = generatePRComment({
|
|
70
|
+
results: {
|
|
71
|
+
inferenceMap: {
|
|
72
|
+
callsites: [],
|
|
73
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
baseline: null,
|
|
77
|
+
status: 'warning',
|
|
78
|
+
regressions: [],
|
|
79
|
+
newIssues: [
|
|
80
|
+
createInsight({ severity: 'critical', headline: 'Critical issue' }),
|
|
81
|
+
],
|
|
82
|
+
changedFiles: [],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(comment).toContain('🟡 Review Recommended');
|
|
86
|
+
expect(comment).toContain('1 issue needs attention');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns "Review Recommended" for >5 warnings', () => {
|
|
90
|
+
const warnings = Array(7).fill(null).map((_, i) =>
|
|
91
|
+
createInsight({ severity: 'warning', headline: `Warning ${i + 1}` })
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const comment = generatePRComment({
|
|
95
|
+
results: {
|
|
96
|
+
inferenceMap: {
|
|
97
|
+
callsites: [],
|
|
98
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
baseline: null,
|
|
102
|
+
status: 'warning',
|
|
103
|
+
regressions: [],
|
|
104
|
+
newIssues: warnings,
|
|
105
|
+
changedFiles: [],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(comment).toContain('🟡 Review Recommended');
|
|
109
|
+
expect(comment).toContain('7 improvements suggested');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns "Changes Requested" for ≥2 critical issues', () => {
|
|
113
|
+
const comment = generatePRComment({
|
|
114
|
+
results: {
|
|
115
|
+
inferenceMap: {
|
|
116
|
+
callsites: [],
|
|
117
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
baseline: null,
|
|
121
|
+
status: 'fail',
|
|
122
|
+
regressions: [],
|
|
123
|
+
newIssues: [
|
|
124
|
+
createInsight({ severity: 'critical', headline: 'Critical 1' }),
|
|
125
|
+
createInsight({ severity: 'critical', headline: 'Critical 2' }),
|
|
126
|
+
createInsight({ severity: 'critical', headline: 'Critical 3' }),
|
|
127
|
+
],
|
|
128
|
+
changedFiles: [],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(comment).toContain('🔴 Changes Requested');
|
|
132
|
+
expect(comment).toContain('3 issues need attention before merge');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// =============================================================================
|
|
137
|
+
// TOP ISSUE TESTS
|
|
138
|
+
// =============================================================================
|
|
139
|
+
|
|
140
|
+
describe('Top Issue Highlight', () => {
|
|
141
|
+
it('shows the highest severity issue first', () => {
|
|
142
|
+
const comment = generatePRComment({
|
|
143
|
+
results: {
|
|
144
|
+
inferenceMap: {
|
|
145
|
+
callsites: [],
|
|
146
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
baseline: null,
|
|
150
|
+
status: 'warning',
|
|
151
|
+
regressions: [],
|
|
152
|
+
newIssues: [
|
|
153
|
+
createInsight({ severity: 'info', headline: 'Info issue' }),
|
|
154
|
+
createInsight({ severity: 'critical', headline: 'Critical issue', location: 'src/api.ts:45' }),
|
|
155
|
+
createInsight({ severity: 'warning', headline: 'Warning issue' }),
|
|
156
|
+
],
|
|
157
|
+
changedFiles: [],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(comment).toContain('**Top Issue**');
|
|
161
|
+
expect(comment).toContain('Critical issue');
|
|
162
|
+
expect(comment).toContain('`src/api.ts:45`');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('shows evidence as "Why it matters"', () => {
|
|
166
|
+
const comment = generatePRComment({
|
|
167
|
+
results: {
|
|
168
|
+
inferenceMap: {
|
|
169
|
+
callsites: [],
|
|
170
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
baseline: null,
|
|
174
|
+
status: 'warning',
|
|
175
|
+
regressions: [],
|
|
176
|
+
newIssues: [
|
|
177
|
+
createInsight({
|
|
178
|
+
severity: 'critical',
|
|
179
|
+
headline: 'Missing error handling',
|
|
180
|
+
evidence: 'Unhandled failures will crash the service',
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
changedFiles: [],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(comment).toContain('**Why it matters**');
|
|
187
|
+
expect(comment).toContain('Unhandled failures will crash the service');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// COLLAPSIBLE DETAILS TESTS
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
describe('Collapsible Details', () => {
|
|
196
|
+
it('does not show details section for single issue', () => {
|
|
197
|
+
const comment = generatePRComment({
|
|
198
|
+
results: {
|
|
199
|
+
inferenceMap: {
|
|
200
|
+
callsites: [],
|
|
201
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
baseline: null,
|
|
205
|
+
status: 'warning',
|
|
206
|
+
regressions: [],
|
|
207
|
+
newIssues: [
|
|
208
|
+
createInsight({ severity: 'critical', headline: 'Single issue' }),
|
|
209
|
+
],
|
|
210
|
+
changedFiles: [],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(comment).not.toContain('<details>');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('shows collapsible details for multiple issues', () => {
|
|
217
|
+
const comment = generatePRComment({
|
|
218
|
+
results: {
|
|
219
|
+
inferenceMap: {
|
|
220
|
+
callsites: [],
|
|
221
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
baseline: null,
|
|
225
|
+
status: 'warning',
|
|
226
|
+
regressions: [],
|
|
227
|
+
newIssues: [
|
|
228
|
+
createInsight({ severity: 'critical', headline: 'Critical 1' }),
|
|
229
|
+
createInsight({ severity: 'warning', headline: 'Warning 1' }),
|
|
230
|
+
createInsight({ severity: 'warning', headline: 'Warning 2' }),
|
|
231
|
+
],
|
|
232
|
+
changedFiles: [],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(comment).toContain('<details>');
|
|
236
|
+
expect(comment).toContain('See all 3 issues');
|
|
237
|
+
expect(comment).toContain('</details>');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('groups issues by severity in details', () => {
|
|
241
|
+
const comment = generatePRComment({
|
|
242
|
+
results: {
|
|
243
|
+
inferenceMap: {
|
|
244
|
+
callsites: [],
|
|
245
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
baseline: null,
|
|
249
|
+
status: 'warning',
|
|
250
|
+
regressions: [],
|
|
251
|
+
newIssues: [
|
|
252
|
+
createInsight({ severity: 'critical', headline: 'Critical 1' }),
|
|
253
|
+
createInsight({ severity: 'critical', headline: 'Critical 2' }),
|
|
254
|
+
createInsight({ severity: 'warning', headline: 'Warning 1' }),
|
|
255
|
+
createInsight({ severity: 'info', headline: 'Info 1' }),
|
|
256
|
+
],
|
|
257
|
+
changedFiles: [],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Critical shown as top issue, so details shows remaining:
|
|
261
|
+
// 1 critical, 1 warning, 1 info
|
|
262
|
+
expect(comment).toContain('**Critical**');
|
|
263
|
+
expect(comment).toContain('**Warning**');
|
|
264
|
+
expect(comment).toContain('**Info**');
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// COMMANDS FOOTER TESTS
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
describe('Commands Footer', () => {
|
|
273
|
+
it('shows commands when issues exist', () => {
|
|
274
|
+
const comment = generatePRComment({
|
|
275
|
+
results: {
|
|
276
|
+
inferenceMap: {
|
|
277
|
+
callsites: [],
|
|
278
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
baseline: null,
|
|
282
|
+
status: 'warning',
|
|
283
|
+
regressions: [],
|
|
284
|
+
newIssues: [
|
|
285
|
+
createInsight({ severity: 'warning', headline: 'Test issue' }),
|
|
286
|
+
],
|
|
287
|
+
changedFiles: [],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(comment).toContain('/fix 1');
|
|
291
|
+
expect(comment).toContain('/dismiss 1');
|
|
292
|
+
expect(comment).toContain('/fix all');
|
|
293
|
+
expect(comment).toContain('/peakinfer');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('does not show commands when no issues', () => {
|
|
297
|
+
const comment = generatePRComment({
|
|
298
|
+
results: {
|
|
299
|
+
inferenceMap: {
|
|
300
|
+
callsites: [],
|
|
301
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
baseline: null,
|
|
305
|
+
status: 'pass',
|
|
306
|
+
regressions: [],
|
|
307
|
+
newIssues: [],
|
|
308
|
+
changedFiles: [],
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(comment).not.toContain('/fix');
|
|
312
|
+
expect(comment).not.toContain('/dismiss');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('always includes PeakInfer attribution', () => {
|
|
316
|
+
const comment = generatePRComment({
|
|
317
|
+
results: {
|
|
318
|
+
inferenceMap: {
|
|
319
|
+
callsites: [],
|
|
320
|
+
summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
baseline: null,
|
|
324
|
+
status: 'pass',
|
|
325
|
+
regressions: [],
|
|
326
|
+
newIssues: [],
|
|
327
|
+
changedFiles: [],
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(comment).toContain('Generated by');
|
|
331
|
+
expect(comment).toContain('PeakInfer');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// =============================================================================
|
|
336
|
+
// EXHAUSTED CREDITS TESTS
|
|
337
|
+
// =============================================================================
|
|
338
|
+
|
|
339
|
+
describe('Exhausted Credits Comment', () => {
|
|
340
|
+
it('shows usage and alternatives', () => {
|
|
341
|
+
const comment = generateExhaustedComment(300, 300);
|
|
342
|
+
|
|
343
|
+
expect(comment).toContain('Free Tier Limit Reached');
|
|
344
|
+
expect(comment).toContain('300/300');
|
|
345
|
+
expect(comment).toContain('npm i -g @kalmantic/peakinfer');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Integration Tests
|
|
3
|
+
* Per Test Cases v1.9.3 - Critical Path Tests
|
|
4
|
+
*/
|
|
5
|
+
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
|
|
6
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
const CLI_PATH = join(__dirname, '..', 'dist', 'cli.js');
|
|
11
|
+
const FIXTURES_PATH = join(__dirname, 'fixtures');
|
|
12
|
+
const TEMP_DIR = join(__dirname, '.temp-cli-test');
|
|
13
|
+
|
|
14
|
+
interface CLIResult {
|
|
15
|
+
exitCode: number;
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
duration: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runCLI(args: string[], options?: { env?: Record<string, string>; timeout?: number }): Promise<CLIResult> {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
const timeout = options?.timeout || 60000;
|
|
25
|
+
|
|
26
|
+
const child = spawn('node', [CLI_PATH, ...args], {
|
|
27
|
+
env: { ...process.env, ...options?.env },
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
let stdout = '';
|
|
32
|
+
let stderr = '';
|
|
33
|
+
|
|
34
|
+
child.stdout.on('data', (data) => {
|
|
35
|
+
stdout += data.toString();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
child.stderr.on('data', (data) => {
|
|
39
|
+
stderr += data.toString();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
child.kill();
|
|
44
|
+
reject(new Error(`CLI timed out after ${timeout}ms`));
|
|
45
|
+
}, timeout);
|
|
46
|
+
|
|
47
|
+
child.on('close', (code) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
resolve({
|
|
50
|
+
exitCode: code || 0,
|
|
51
|
+
stdout,
|
|
52
|
+
stderr,
|
|
53
|
+
duration: Date.now() - startTime,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.on('error', (err) => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
reject(err);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('CLI demo command', () => {
|
|
65
|
+
test('Runs without arguments', async () => {
|
|
66
|
+
const result = await runCLI(['demo']);
|
|
67
|
+
expect(result.exitCode).toBe(0);
|
|
68
|
+
expect(result.stdout).toContain('DRIFT DETECTED');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Completes in under 30 seconds', async () => {
|
|
72
|
+
const result = await runCLI(['demo']);
|
|
73
|
+
expect(result.duration).toBeLessThan(30000);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('Works offline with cached demo data', async () => {
|
|
77
|
+
const result = await runCLI(['demo'], {
|
|
78
|
+
env: {
|
|
79
|
+
ANTHROPIC_API_KEY: '', // No API key
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(result.exitCode).toBe(0);
|
|
84
|
+
expect(result.stdout).toContain('inference points');
|
|
85
|
+
expect(result.stderr).not.toContain('API');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('Shows magic moment (drift detection)', async () => {
|
|
89
|
+
const result = await runCLI(['demo']);
|
|
90
|
+
expect(result.stdout).toContain('YOUR CODE');
|
|
91
|
+
expect(result.stdout).toContain('RUNTIME');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('Shows issues with severity', async () => {
|
|
95
|
+
const result = await runCLI(['demo']);
|
|
96
|
+
expect(result.stdout).toMatch(/CRITICAL|HIGH|MEDIUM|LOW/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('Shows CTA for next step', async () => {
|
|
100
|
+
const result = await runCLI(['demo']);
|
|
101
|
+
expect(result.stdout).toContain('peakinfer analyze');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('CLI help', () => {
|
|
106
|
+
test('Shows help with --help', async () => {
|
|
107
|
+
const result = await runCLI(['--help']);
|
|
108
|
+
expect(result.exitCode).toBe(0);
|
|
109
|
+
expect(result.stdout).toContain('analyze');
|
|
110
|
+
expect(result.stdout).toContain('demo');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Shows version with --version', async () => {
|
|
114
|
+
const result = await runCLI(['--version']);
|
|
115
|
+
expect(result.exitCode).toBe(0);
|
|
116
|
+
expect(result.stdout).toMatch(/\d+\.\d+\.\d+/);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('CLI analyze command', () => {
|
|
121
|
+
beforeAll(() => {
|
|
122
|
+
// Create temp directory with test file
|
|
123
|
+
if (!existsSync(TEMP_DIR)) {
|
|
124
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create a simple test file
|
|
128
|
+
const testFile = `
|
|
129
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
130
|
+
|
|
131
|
+
const client = new Anthropic();
|
|
132
|
+
|
|
133
|
+
export async function chat(prompt: string) {
|
|
134
|
+
const response = await client.messages.create({
|
|
135
|
+
model: 'claude-sonnet-4-20250514',
|
|
136
|
+
max_tokens: 1000,
|
|
137
|
+
messages: [{ role: 'user', content: prompt }],
|
|
138
|
+
});
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
writeFileSync(join(TEMP_DIR, 'test.ts'), testFile);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterAll(() => {
|
|
146
|
+
// Cleanup
|
|
147
|
+
if (existsSync(TEMP_DIR)) {
|
|
148
|
+
rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('Fails gracefully without API key', async () => {
|
|
153
|
+
const result = await runCLI(['analyze', TEMP_DIR], {
|
|
154
|
+
env: {
|
|
155
|
+
ANTHROPIC_API_KEY: '',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// Should exit with error or show helpful message
|
|
159
|
+
expect(result.stdout + result.stderr).toMatch(/API|key|ANTHROPIC/i);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('Shows error for non-existent path', async () => {
|
|
163
|
+
const result = await runCLI(['analyze', '/non/existent/path']);
|
|
164
|
+
expect(result.exitCode).not.toBe(0);
|
|
165
|
+
expect(result.stderr).toContain('not found');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('Accepts --verbose flag', async () => {
|
|
169
|
+
const result = await runCLI(['analyze', TEMP_DIR, '--verbose'], {
|
|
170
|
+
env: {
|
|
171
|
+
ANTHROPIC_API_KEY: '',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
// Should not crash with verbose flag
|
|
175
|
+
expect(result.exitCode).toBeDefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('Accepts --fixes flag', async () => {
|
|
179
|
+
const result = await runCLI(['analyze', TEMP_DIR, '--fixes'], {
|
|
180
|
+
env: {
|
|
181
|
+
ANTHROPIC_API_KEY: '',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
// Should not crash with fixes flag
|
|
185
|
+
expect(result.exitCode).toBeDefined();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('CLI template commands', () => {
|
|
190
|
+
test('Lists templates', async () => {
|
|
191
|
+
const result = await runCLI(['template', 'list']);
|
|
192
|
+
expect(result.exitCode).toBe(0);
|
|
193
|
+
expect(result.stdout).toMatch(/template|insight|optimization/i);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('CLI history commands', () => {
|
|
198
|
+
test('Shows history', async () => {
|
|
199
|
+
const result = await runCLI(['history']);
|
|
200
|
+
expect(result.exitCode).toBe(0);
|
|
201
|
+
// May be empty but should not crash
|
|
202
|
+
});
|
|
203
|
+
});
|