@vibecheckai/cli 3.5.0 → 3.5.1
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/bin/registry.js +174 -449
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/generators/mcp.js +13 -15
- package/bin/runners/context/proof-context.js +1 -248
- package/bin/runners/lib/analysis-core.js +180 -198
- package/bin/runners/lib/analyzers.js +241 -2212
- package/bin/runners/lib/cli-output.js +210 -242
- package/bin/runners/lib/detectors-v2.js +785 -547
- package/bin/runners/lib/entitlements-v2.js +431 -161
- package/bin/runners/lib/error-handler.js +9 -16
- package/bin/runners/lib/global-flags.js +0 -37
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/route-truth.js +322 -1167
- package/bin/runners/lib/scan-output.js +467 -493
- package/bin/runners/lib/ship-output.js +27 -280
- package/bin/runners/lib/terminal-ui.js +700 -310
- package/bin/runners/lib/truth.js +321 -1004
- package/bin/runners/lib/unified-output.js +158 -162
- package/bin/runners/lib/upsell.js +204 -104
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAllowlist.js +324 -0
- package/bin/runners/runAuth.js +94 -344
- package/bin/runners/runCheckpoint.js +45 -43
- package/bin/runners/runContext.js +24 -139
- package/bin/runners/runDoctor.js +101 -136
- package/bin/runners/runEvidencePack.js +219 -0
- package/bin/runners/runFix.js +71 -82
- package/bin/runners/runGuard.js +119 -606
- package/bin/runners/runInit.js +60 -22
- package/bin/runners/runInstall.js +281 -0
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runMcp.js +62 -139
- package/bin/runners/runPolish.js +83 -282
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +58 -33
- package/bin/runners/runReality.js +58 -81
- package/bin/runners/runReport.js +7 -34
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +844 -219
- package/bin/runners/runShip.js +59 -721
- package/bin/runners/runValidate.js +11 -24
- package/bin/runners/runWatch.js +76 -131
- package/bin/vibecheck.js +69 -295
- package/mcp-server/ARCHITECTURE.md +339 -0
- package/mcp-server/__tests__/cache.test.ts +313 -0
- package/mcp-server/__tests__/executor.test.ts +239 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
- package/mcp-server/__tests__/ids.test.ts +345 -0
- package/mcp-server/__tests__/integration/tools.test.ts +410 -0
- package/mcp-server/__tests__/registry.test.ts +365 -0
- package/mcp-server/__tests__/sandbox.test.ts +323 -0
- package/mcp-server/__tests__/schemas.test.ts +372 -0
- package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
- package/mcp-server/examples/doctor.request.json +14 -0
- package/mcp-server/examples/doctor.response.json +53 -0
- package/mcp-server/examples/error.response.json +15 -0
- package/mcp-server/examples/scan.request.json +14 -0
- package/mcp-server/examples/scan.response.json +108 -0
- package/mcp-server/handlers/tool-handler.ts +671 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index-v3.ts +293 -0
- package/mcp-server/index.js +1080 -1757
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/lib/cache.ts +341 -0
- package/mcp-server/lib/errors.ts +346 -0
- package/mcp-server/lib/executor.ts +792 -0
- package/mcp-server/lib/ids.ts +238 -0
- package/mcp-server/lib/logger.ts +368 -0
- package/mcp-server/lib/metrics.ts +365 -0
- package/mcp-server/lib/sandbox.ts +337 -0
- package/mcp-server/lib/validator.ts +229 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +32 -7
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/registry/tools.json +476 -0
- package/mcp-server/schemas/error-envelope.schema.json +125 -0
- package/mcp-server/schemas/finding.schema.json +167 -0
- package/mcp-server/schemas/report-artifact.schema.json +88 -0
- package/mcp-server/schemas/run-request.schema.json +75 -0
- package/mcp-server/schemas/verdict.schema.json +168 -0
- package/mcp-server/tier-auth.d.ts +71 -0
- package/mcp-server/tier-auth.js +371 -183
- package/mcp-server/truth-context.js +90 -131
- package/mcp-server/truth-firewall-tools.js +1000 -1611
- package/mcp-server/tsconfig.json +34 -0
- package/mcp-server/vibecheck-tools.js +2 -2
- package/mcp-server/vitest.config.ts +16 -0
- package/package.json +3 -4
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
- package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
- package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
- package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
- package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
- package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
- package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
- package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
- package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/agent-firewall/logger.js +0 -141
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
- package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
- package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
- package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
- package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
- package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
- package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
- package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
- package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
- package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
- package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
- package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
- package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
- package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
- package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
- package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
- package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
- package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
- package/bin/runners/lib/api-client.js +0 -269
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/authority-badge.js +0 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/accessibility-engine.js +0 -390
- package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
- package/bin/runners/lib/engines/ast-cache.js +0 -99
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/code-quality-engine.js +0 -255
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/console-logs-engine.js +0 -115
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/file-filter.js +0 -131
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/mock-data-engine.js +0 -315
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/parallel-processor.js +0 -71
- package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
- package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
- package/bin/runners/lib/engines/type-aware-engine.js +0 -376
- package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
- package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
- package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/exit-codes.js +0 -275
- package/bin/runners/lib/fingerprint.js +0 -377
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/help-formatter.js +0 -413
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/logger.js +0 -38
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/ship-output-enterprise.js +0 -239
- package/bin/runners/lib/unified-cli-output.js +0 -777
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAgent.d.ts +0 -5
- package/bin/runners/runAgent.js +0 -164
- package/bin/runners/runApprove.js +0 -1233
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runClassify.js +0 -862
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runContext.d.ts +0 -4
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runFirewall.d.ts +0 -5
- package/bin/runners/runFirewall.js +0 -137
- package/bin/runners/runFirewallHook.d.ts +0 -5
- package/bin/runners/runFirewallHook.js +0 -59
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runPolish.d.ts +0 -4
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runTruth.d.ts +0 -5
- package/bin/runners/runTruth.js +0 -104
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/logger.js +0 -173
- package/mcp-server/tools-v3.js +0 -1039
- package/mcp-server/tools.js +0 -495
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic ID Generation
|
|
3
|
+
*
|
|
4
|
+
* Produces stable, reproducible IDs for findings and other entities.
|
|
5
|
+
* Uses SHA256 for cryptographic stability - same inputs always produce same outputs.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: Agents must not thrash. IDs must be deterministic.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Evidence location for ID generation
|
|
14
|
+
*/
|
|
15
|
+
export interface EvidenceLocation {
|
|
16
|
+
path?: string;
|
|
17
|
+
lineStart?: number;
|
|
18
|
+
lineEnd?: number;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate a deterministic finding ID
|
|
24
|
+
*
|
|
25
|
+
* Formula: sha256(rule_id + path + lineStart + message) truncated to 8 hex chars
|
|
26
|
+
* This ensures:
|
|
27
|
+
* - Same finding always gets same ID across runs
|
|
28
|
+
* - Different findings get different IDs
|
|
29
|
+
* - IDs are short but collision-resistant
|
|
30
|
+
*/
|
|
31
|
+
export function generateFindingId(
|
|
32
|
+
ruleId: string,
|
|
33
|
+
evidence: EvidenceLocation
|
|
34
|
+
): string {
|
|
35
|
+
const category = ruleId.split('.')[0] || ruleId;
|
|
36
|
+
const input = [
|
|
37
|
+
ruleId,
|
|
38
|
+
evidence.path || '',
|
|
39
|
+
String(evidence.lineStart || 0),
|
|
40
|
+
evidence.message || '',
|
|
41
|
+
].join(':');
|
|
42
|
+
|
|
43
|
+
const hash = createHash('sha256').update(input).digest('hex');
|
|
44
|
+
return `${normalizeCategory(category)}-${hash.substring(0, 8)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize category to lowercase snake_case
|
|
49
|
+
*/
|
|
50
|
+
function normalizeCategory(category: string): string {
|
|
51
|
+
return category
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
54
|
+
.replace(/^_+|_+$/g, '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate a deterministic cache key
|
|
59
|
+
*
|
|
60
|
+
* Includes: tool name, vibecheck version, schema version, project fingerprint
|
|
61
|
+
*/
|
|
62
|
+
export function generateCacheKey(
|
|
63
|
+
tool: string,
|
|
64
|
+
projectFingerprint: string,
|
|
65
|
+
options?: Record<string, unknown>,
|
|
66
|
+
schemaVersion = '1.0.0'
|
|
67
|
+
): string {
|
|
68
|
+
const optionsHash = options
|
|
69
|
+
? createHash('md5')
|
|
70
|
+
.update(JSON.stringify(sortObject(options)))
|
|
71
|
+
.digest('hex')
|
|
72
|
+
.substring(0, 8)
|
|
73
|
+
: 'default';
|
|
74
|
+
|
|
75
|
+
return `${tool}:${schemaVersion}:${projectFingerprint}:${optionsHash}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a deterministic request ID
|
|
80
|
+
*
|
|
81
|
+
* For tracing - includes timestamp component for ordering
|
|
82
|
+
*/
|
|
83
|
+
export function generateRequestId(prefix = 'req'): string {
|
|
84
|
+
const timestamp = Date.now().toString(36);
|
|
85
|
+
const random = createHash('md5')
|
|
86
|
+
.update(`${timestamp}:${Math.random()}`)
|
|
87
|
+
.digest('hex')
|
|
88
|
+
.substring(0, 8);
|
|
89
|
+
return `${prefix}_${timestamp}_${random}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generate a deterministic run ID
|
|
94
|
+
*
|
|
95
|
+
* For correlating multiple findings from the same analysis run
|
|
96
|
+
*/
|
|
97
|
+
export function generateRunId(): string {
|
|
98
|
+
const timestamp = new Date().toISOString().replace(/[-:T.Z]/g, '');
|
|
99
|
+
const random = createHash('md5')
|
|
100
|
+
.update(`${timestamp}:${process.hrtime.bigint()}`)
|
|
101
|
+
.digest('hex')
|
|
102
|
+
.substring(0, 8);
|
|
103
|
+
return `run_${timestamp.substring(0, 14)}_${random}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate project fingerprint from key files
|
|
108
|
+
*
|
|
109
|
+
* Hashes: vibecheck config + lockfiles + manifests
|
|
110
|
+
*/
|
|
111
|
+
export function generateProjectFingerprint(
|
|
112
|
+
files: Array<{ path: string; content: string | Buffer }>
|
|
113
|
+
): string {
|
|
114
|
+
const hash = createHash('sha256');
|
|
115
|
+
|
|
116
|
+
// Sort files for determinism
|
|
117
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
118
|
+
|
|
119
|
+
for (const file of sorted) {
|
|
120
|
+
hash.update(file.path);
|
|
121
|
+
hash.update(':');
|
|
122
|
+
hash.update(file.content);
|
|
123
|
+
hash.update('\n');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return hash.digest('hex').substring(0, 16);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sort object keys recursively for deterministic serialization
|
|
131
|
+
*/
|
|
132
|
+
function sortObject(obj: unknown): unknown {
|
|
133
|
+
if (obj === null || typeof obj !== 'object') {
|
|
134
|
+
return obj;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (Array.isArray(obj)) {
|
|
138
|
+
return obj.map(sortObject);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const sorted: Record<string, unknown> = {};
|
|
142
|
+
const keys = Object.keys(obj as Record<string, unknown>).sort();
|
|
143
|
+
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
sorted[key] = sortObject((obj as Record<string, unknown>)[key]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return sorted;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Validate a finding ID format
|
|
153
|
+
*/
|
|
154
|
+
export function isValidFindingId(id: string): boolean {
|
|
155
|
+
return /^[a-z_]+-[a-f0-9]{8}$/.test(id);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parse a finding ID into components
|
|
160
|
+
*/
|
|
161
|
+
export function parseFindingId(id: string): { category: string; hash: string } | null {
|
|
162
|
+
const match = id.match(/^([a-z_]+)-([a-f0-9]{8})$/);
|
|
163
|
+
if (!match) return null;
|
|
164
|
+
return { category: match[1], hash: match[2] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Severity order for deterministic sorting
|
|
169
|
+
* CRITICAL > HIGH > MEDIUM > LOW > INFO
|
|
170
|
+
*/
|
|
171
|
+
export const SEVERITY_ORDER: Record<string, number> = {
|
|
172
|
+
BLOCK: 0,
|
|
173
|
+
CRITICAL: 0,
|
|
174
|
+
HIGH: 1,
|
|
175
|
+
WARN: 2,
|
|
176
|
+
MEDIUM: 2,
|
|
177
|
+
LOW: 3,
|
|
178
|
+
INFO: 4,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Compare findings for deterministic sorting
|
|
183
|
+
*
|
|
184
|
+
* Order: severity desc, rule_id asc, path asc, lineStart asc, message asc
|
|
185
|
+
*/
|
|
186
|
+
export function compareFindingsForSort(
|
|
187
|
+
a: { severity?: string; rule_id?: string; ruleId?: string; evidence?: Array<{ file?: string; line?: number }>; title?: string },
|
|
188
|
+
b: { severity?: string; rule_id?: string; ruleId?: string; evidence?: Array<{ file?: string; line?: number }>; title?: string }
|
|
189
|
+
): number {
|
|
190
|
+
// Severity (lower order = higher priority)
|
|
191
|
+
const severityA = SEVERITY_ORDER[a.severity || 'INFO'] ?? 4;
|
|
192
|
+
const severityB = SEVERITY_ORDER[b.severity || 'INFO'] ?? 4;
|
|
193
|
+
if (severityA !== severityB) return severityA - severityB;
|
|
194
|
+
|
|
195
|
+
// Rule ID
|
|
196
|
+
const ruleA = a.rule_id || a.ruleId || '';
|
|
197
|
+
const ruleB = b.rule_id || b.ruleId || '';
|
|
198
|
+
const ruleCompare = ruleA.localeCompare(ruleB);
|
|
199
|
+
if (ruleCompare !== 0) return ruleCompare;
|
|
200
|
+
|
|
201
|
+
// Path (first evidence)
|
|
202
|
+
const pathA = a.evidence?.[0]?.file || '';
|
|
203
|
+
const pathB = b.evidence?.[0]?.file || '';
|
|
204
|
+
const pathCompare = pathA.localeCompare(pathB);
|
|
205
|
+
if (pathCompare !== 0) return pathCompare;
|
|
206
|
+
|
|
207
|
+
// Line
|
|
208
|
+
const lineA = a.evidence?.[0]?.line || 0;
|
|
209
|
+
const lineB = b.evidence?.[0]?.line || 0;
|
|
210
|
+
if (lineA !== lineB) return lineA - lineB;
|
|
211
|
+
|
|
212
|
+
// Message/title
|
|
213
|
+
const titleA = a.title || '';
|
|
214
|
+
const titleB = b.title || '';
|
|
215
|
+
return titleA.localeCompare(titleB);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Sort findings deterministically
|
|
220
|
+
*/
|
|
221
|
+
export function sortFindings<T extends { severity?: string; rule_id?: string; ruleId?: string; evidence?: Array<{ file?: string; line?: number }>; title?: string }>(
|
|
222
|
+
findings: T[]
|
|
223
|
+
): T[] {
|
|
224
|
+
return [...findings].sort(compareFindingsForSort);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default {
|
|
228
|
+
generateFindingId,
|
|
229
|
+
generateCacheKey,
|
|
230
|
+
generateRequestId,
|
|
231
|
+
generateRunId,
|
|
232
|
+
generateProjectFingerprint,
|
|
233
|
+
isValidFindingId,
|
|
234
|
+
parseFindingId,
|
|
235
|
+
compareFindingsForSort,
|
|
236
|
+
sortFindings,
|
|
237
|
+
SEVERITY_ORDER,
|
|
238
|
+
};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Logging & Metrics
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Structured JSON logs
|
|
6
|
+
* - Request tracing with IDs
|
|
7
|
+
* - Span-based timing
|
|
8
|
+
* - Metrics collection
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import { appendFileSync, mkdirSync, existsSync } from 'fs';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Log levels
|
|
17
|
+
*/
|
|
18
|
+
export enum LogLevel {
|
|
19
|
+
DEBUG = 'debug',
|
|
20
|
+
INFO = 'info',
|
|
21
|
+
WARN = 'warn',
|
|
22
|
+
ERROR = 'error',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log entry structure
|
|
27
|
+
*/
|
|
28
|
+
export interface LogEntry {
|
|
29
|
+
timestamp: string;
|
|
30
|
+
level: LogLevel;
|
|
31
|
+
message: string;
|
|
32
|
+
requestId?: string;
|
|
33
|
+
spanId?: string;
|
|
34
|
+
tool?: string;
|
|
35
|
+
durationMs?: number;
|
|
36
|
+
data?: Record<string, unknown>;
|
|
37
|
+
error?: {
|
|
38
|
+
code: string;
|
|
39
|
+
message: string;
|
|
40
|
+
stack?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Span for timing operations
|
|
46
|
+
*/
|
|
47
|
+
export interface Span {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
startTime: number;
|
|
51
|
+
endTime?: number;
|
|
52
|
+
status?: 'ok' | 'error';
|
|
53
|
+
attributes?: Record<string, unknown>;
|
|
54
|
+
events?: Array<{ name: string; timestamp: number; attributes?: Record<string, unknown> }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Metrics entry
|
|
59
|
+
*/
|
|
60
|
+
export interface MetricEntry {
|
|
61
|
+
name: string;
|
|
62
|
+
value: number;
|
|
63
|
+
unit: 'ms' | 'count' | 'bytes' | 'percent';
|
|
64
|
+
timestamp: number;
|
|
65
|
+
tags?: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Logger configuration
|
|
70
|
+
*/
|
|
71
|
+
export interface LoggerConfig {
|
|
72
|
+
level?: LogLevel;
|
|
73
|
+
outputDir?: string;
|
|
74
|
+
enableConsole?: boolean;
|
|
75
|
+
enableFile?: boolean;
|
|
76
|
+
enableMetrics?: boolean;
|
|
77
|
+
redactSensitive?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Main logger class
|
|
82
|
+
*/
|
|
83
|
+
export class VibecheckLogger {
|
|
84
|
+
private config: Required<LoggerConfig>;
|
|
85
|
+
private requestId?: string;
|
|
86
|
+
private spans: Map<string, Span> = new Map();
|
|
87
|
+
private metrics: MetricEntry[] = [];
|
|
88
|
+
|
|
89
|
+
constructor(config: LoggerConfig = {}) {
|
|
90
|
+
this.config = {
|
|
91
|
+
level: config.level ?? LogLevel.INFO,
|
|
92
|
+
outputDir: config.outputDir ?? '.vibecheck/logs',
|
|
93
|
+
enableConsole: config.enableConsole ?? true,
|
|
94
|
+
enableFile: config.enableFile ?? false,
|
|
95
|
+
enableMetrics: config.enableMetrics ?? true,
|
|
96
|
+
redactSensitive: config.redactSensitive ?? true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set request ID for tracing
|
|
102
|
+
*/
|
|
103
|
+
setRequestId(id: string): void {
|
|
104
|
+
this.requestId = id;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create child logger with request context
|
|
109
|
+
*/
|
|
110
|
+
child(context: { requestId?: string; tool?: string }): VibecheckLogger {
|
|
111
|
+
const child = new VibecheckLogger(this.config);
|
|
112
|
+
child.requestId = context.requestId ?? this.requestId;
|
|
113
|
+
return child;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Log methods
|
|
118
|
+
*/
|
|
119
|
+
debug(message: string, data?: Record<string, unknown>): void {
|
|
120
|
+
this.log(LogLevel.DEBUG, message, data);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
info(message: string, data?: Record<string, unknown>): void {
|
|
124
|
+
this.log(LogLevel.INFO, message, data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
warn(message: string, data?: Record<string, unknown>): void {
|
|
128
|
+
this.log(LogLevel.WARN, message, data);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
error(message: string, error?: Error | Record<string, unknown>): void {
|
|
132
|
+
const errorData = error instanceof Error
|
|
133
|
+
? { code: error.name, message: error.message, stack: error.stack }
|
|
134
|
+
: error;
|
|
135
|
+
this.log(LogLevel.ERROR, message, undefined, errorData as { code: string; message: string; stack?: string });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Core log method
|
|
140
|
+
*/
|
|
141
|
+
private log(
|
|
142
|
+
level: LogLevel,
|
|
143
|
+
message: string,
|
|
144
|
+
data?: Record<string, unknown>,
|
|
145
|
+
error?: { code: string; message: string; stack?: string }
|
|
146
|
+
): void {
|
|
147
|
+
// Check level
|
|
148
|
+
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
|
|
149
|
+
if (levels.indexOf(level) < levels.indexOf(this.config.level)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const entry: LogEntry = {
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
level,
|
|
156
|
+
message,
|
|
157
|
+
requestId: this.requestId,
|
|
158
|
+
data: this.config.redactSensitive && data ? this.redact(data) : data,
|
|
159
|
+
error,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Output to console
|
|
163
|
+
if (this.config.enableConsole) {
|
|
164
|
+
this.writeConsole(entry);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Output to file
|
|
168
|
+
if (this.config.enableFile) {
|
|
169
|
+
this.writeFile(entry);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Write to console (structured JSON on stderr)
|
|
175
|
+
*/
|
|
176
|
+
private writeConsole(entry: LogEntry): void {
|
|
177
|
+
const line = JSON.stringify(entry);
|
|
178
|
+
console.error(line);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Write to file
|
|
183
|
+
*/
|
|
184
|
+
private writeFile(entry: LogEntry): void {
|
|
185
|
+
const logDir = this.config.outputDir;
|
|
186
|
+
if (!existsSync(logDir)) {
|
|
187
|
+
mkdirSync(logDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const date = new Date().toISOString().split('T')[0];
|
|
191
|
+
const logFile = join(logDir, `vibecheck-${date}.jsonl`);
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
appendFileSync(logFile, JSON.stringify(entry) + '\n');
|
|
195
|
+
} catch {
|
|
196
|
+
// Ignore write errors
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Redact sensitive data
|
|
202
|
+
*/
|
|
203
|
+
private redact(data: Record<string, unknown>): Record<string, unknown> {
|
|
204
|
+
const sensitiveKeys = ['password', 'secret', 'token', 'key', 'auth', 'credential', 'apiKey'];
|
|
205
|
+
const result: Record<string, unknown> = {};
|
|
206
|
+
|
|
207
|
+
for (const [key, value] of Object.entries(data)) {
|
|
208
|
+
if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
|
|
209
|
+
result[key] = '[REDACTED]';
|
|
210
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
211
|
+
result[key] = this.redact(value as Record<string, unknown>);
|
|
212
|
+
} else {
|
|
213
|
+
result[key] = value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Start a span for timing
|
|
222
|
+
*/
|
|
223
|
+
startSpan(name: string, attributes?: Record<string, unknown>): string {
|
|
224
|
+
const id = randomUUID().substring(0, 8);
|
|
225
|
+
const span: Span = {
|
|
226
|
+
id,
|
|
227
|
+
name,
|
|
228
|
+
startTime: Date.now(),
|
|
229
|
+
attributes,
|
|
230
|
+
events: [],
|
|
231
|
+
};
|
|
232
|
+
this.spans.set(id, span);
|
|
233
|
+
|
|
234
|
+
this.debug(`Span started: ${name}`, { spanId: id, ...attributes });
|
|
235
|
+
return id;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Add event to span
|
|
240
|
+
*/
|
|
241
|
+
addSpanEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void {
|
|
242
|
+
const span = this.spans.get(spanId);
|
|
243
|
+
if (span) {
|
|
244
|
+
span.events?.push({ name, timestamp: Date.now(), attributes });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* End a span
|
|
250
|
+
*/
|
|
251
|
+
endSpan(spanId: string, status: 'ok' | 'error' = 'ok'): Span | undefined {
|
|
252
|
+
const span = this.spans.get(spanId);
|
|
253
|
+
if (span) {
|
|
254
|
+
span.endTime = Date.now();
|
|
255
|
+
span.status = status;
|
|
256
|
+
const durationMs = span.endTime - span.startTime;
|
|
257
|
+
|
|
258
|
+
this.info(`Span ended: ${span.name}`, {
|
|
259
|
+
spanId,
|
|
260
|
+
durationMs,
|
|
261
|
+
status,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Record as metric
|
|
265
|
+
if (this.config.enableMetrics) {
|
|
266
|
+
this.recordMetric(`span.${span.name}.duration`, durationMs, 'ms', {
|
|
267
|
+
status,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.spans.delete(spanId);
|
|
272
|
+
return span;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Record a metric
|
|
279
|
+
*/
|
|
280
|
+
recordMetric(
|
|
281
|
+
name: string,
|
|
282
|
+
value: number,
|
|
283
|
+
unit: 'ms' | 'count' | 'bytes' | 'percent',
|
|
284
|
+
tags?: Record<string, string>
|
|
285
|
+
): void {
|
|
286
|
+
if (!this.config.enableMetrics) return;
|
|
287
|
+
|
|
288
|
+
const metric: MetricEntry = {
|
|
289
|
+
name,
|
|
290
|
+
value,
|
|
291
|
+
unit,
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
tags,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
this.metrics.push(metric);
|
|
297
|
+
|
|
298
|
+
// Log metric
|
|
299
|
+
this.debug(`Metric: ${name}=${value}${unit}`, { tags });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get collected metrics
|
|
304
|
+
*/
|
|
305
|
+
getMetrics(): MetricEntry[] {
|
|
306
|
+
return [...this.metrics];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Clear metrics
|
|
311
|
+
*/
|
|
312
|
+
clearMetrics(): void {
|
|
313
|
+
this.metrics = [];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Flush metrics to output
|
|
318
|
+
*/
|
|
319
|
+
flushMetrics(): void {
|
|
320
|
+
if (!this.config.enableFile || this.metrics.length === 0) return;
|
|
321
|
+
|
|
322
|
+
const logDir = this.config.outputDir;
|
|
323
|
+
if (!existsSync(logDir)) {
|
|
324
|
+
mkdirSync(logDir, { recursive: true });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const date = new Date().toISOString().split('T')[0];
|
|
328
|
+
const metricsFile = join(logDir, `metrics-${date}.jsonl`);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const lines = this.metrics.map(m => JSON.stringify(m)).join('\n') + '\n';
|
|
332
|
+
appendFileSync(metricsFile, lines);
|
|
333
|
+
this.metrics = [];
|
|
334
|
+
} catch {
|
|
335
|
+
// Ignore write errors
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Singleton logger
|
|
341
|
+
let globalLogger: VibecheckLogger | null = null;
|
|
342
|
+
|
|
343
|
+
export function getLogger(): VibecheckLogger {
|
|
344
|
+
if (!globalLogger) {
|
|
345
|
+
globalLogger = new VibecheckLogger();
|
|
346
|
+
}
|
|
347
|
+
return globalLogger;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function initLogger(config: LoggerConfig): void {
|
|
351
|
+
globalLogger = new VibecheckLogger(config);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Convenience function for creating request-scoped logger
|
|
356
|
+
*/
|
|
357
|
+
export function createRequestLogger(requestId?: string): VibecheckLogger {
|
|
358
|
+
const logger = getLogger().child({ requestId: requestId ?? randomUUID() });
|
|
359
|
+
return logger;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export default {
|
|
363
|
+
LogLevel,
|
|
364
|
+
VibecheckLogger,
|
|
365
|
+
getLogger,
|
|
366
|
+
initLogger,
|
|
367
|
+
createRequestLogger,
|
|
368
|
+
};
|