Skip to content

Latest commit

 

History

History
256 lines (211 loc) · 9.09 KB

File metadata and controls

256 lines (211 loc) · 9.09 KB

Visor SDK (Programmatic Usage)

Run Visor from Node.js without shelling out. The SDK is a thin façade over the existing engine: it just wires inputs/outputs and reuses all core behavior (routing, providers, templates, etc.).

Install

npm i -D @probelabs/visor

Quick Start

JavaScript (ESM)

import { loadConfig, runChecks } from '@probelabs/visor/sdk';

// Load config from object (not file!) - validation and defaults applied
const config = await loadConfig({
  version: '1.0',
  checks: {
    'security': { type: 'command', exec: 'npm audit' },
    'lint': { type: 'command', exec: 'npm run lint' },
  }
});

const result = await runChecks({
  config,
  checks: Object.keys(config.checks),
  output: { format: 'json' },
});
console.log('Total issues:', result.reviewSummary.issues?.length ?? 0);

TypeScript

import { loadConfig, runChecks, type VisorConfig, type RunOptions } from '@probelabs/visor/sdk';

// Type-safe config construction
const rawConfig: Partial<VisorConfig> = {
  version: '1.0',
  checks: {
    'security': { type: 'command', exec: 'npm audit' },
    'lint': { type: 'command', exec: 'npm run lint' },
  }
};

const config = await loadConfig(rawConfig);
const result = await runChecks({
  config,
  checks: Object.keys(config.checks),
  output: { format: 'json' },
});

// Type-safe result access with full type inference
console.log('Total issues:', result.reviewSummary.issues?.length ?? 0);
console.log('Checks executed:', result.checksExecuted);
console.log('Execution time:', result.executionTime);

CommonJS

const { loadConfig, runChecks } = require('@probelabs/visor/sdk');
(async () => {
  const config = await loadConfig({
    version: '1.0',
    checks: { test: { type: 'command', exec: 'echo test' } }
  });
  const result = await runChecks({ config, checks: Object.keys(config.checks), output: { format: 'json' } });
  console.log('Total issues:', result.reviewSummary.issues?.length ?? 0);
})();

Loading from Files

You can also load config from files:

import { loadConfig, runChecks } from '@probelabs/visor/sdk';

// Load from specific file path
const config = await loadConfig('./my-config.yaml');

// Or discover default (.visor.yaml/.visor.yml)
const config2 = await loadConfig();

const result = await runChecks({
  config,
  checks: Object.keys(config.checks),
  output: { format: 'json' },
});

Note: The output parameter in runChecks() options controls the CLI output format (table/json/markdown/sarif). The config's output field is for GitHub PR comments and is optional for programmatic use.

Strict Validation Mode

By default, unknown config keys generate warnings but don't fail. Enable strict mode to catch config errors early:

import { runChecks } from '@probelabs/visor/sdk';

const config = {
  version: '1.0',
  checks: { test: { type: 'command', exec: 'echo test' } },
  typo_field: 'oops'  // This would normally just warn
};

try {
  await runChecks({
    config,
    checks: ['test'],
    strictValidation: true  // Now throws error for unknown keys
  });
} catch (error) {
  console.error('Config error:', error.message);
  // Error: Unknown top-level key 'typo_field' will be ignored.
}

API

Functions

  • loadConfig(configOrPath?: string | Partial<VisorConfig>, options?: { strict?: boolean }): Promise<VisorConfig>
    • Loads and validates a config from an object, file path, or discovers defaults
    • Accepts config objects (validates and applies defaults) or file paths
    • Returns fully validated config with all defaults applied
    • Set options.strict to treat warnings as errors
  • resolveChecks(checkIds: string[], config: VisorConfig | undefined): string[]
    • Expands check IDs to include dependencies in the correct order.
    • Detects and throws on circular dependencies.
  • runChecks(options: RunOptions): Promise<AnalysisResult>
    • Runs checks programmatically. Thin wrapper around the engine's executeChecks.

Types

All types below are exported from @probelabs/visor/sdk.

  • VisorOptions (base options used by RunOptions)

    • cwd?: string - Working directory for execution
    • debug?: boolean - Enable debug mode
    • maxParallelism?: number - Maximum parallel checks
    • failFast?: boolean - Stop on first failure
    • tagFilter?: TagFilter - Filter checks by tags
  • RunOptions (extends VisorOptions)

    • config?: VisorConfig - Config object (mutually exclusive with configPath)
    • configPath?: string - Path to config file (mutually exclusive with config)
    • checks?: string[] - Check IDs to run (default: all checks from config)
    • timeoutMs?: number - Execution timeout in milliseconds
    • output?: { format?: 'table'|'json'|'markdown'|'sarif' } - Output format
    • strictValidation?: boolean - Treat config warnings (unknown keys) as errors (default: false)
    • executionContext?: ExecutionContext - Execution context for providers (hooks, CLI state, etc.)
  • AnalysisResult

    • reviewSummary: ReviewSummary - Contains issues: Issue[] and scoring
    • executionTime: number - Total execution time in ms
    • timestamp: string - ISO timestamp
    • checksExecuted: string[] - List of executed check IDs
    • repositoryInfo: GitRepositoryInfo - Repository metadata
    • executionStatistics?: ExecutionStatistics - Detailed per-check statistics (optional)
    • debug?: DebugInfo - Debug information when debug mode enabled (optional)
    • failureConditions?: FailureConditionResult[] - Failure condition results (optional)
  • ExecutionContext - Context passed to check providers

    • cliMessage?: string - CLI message value (from --message argument)
    • hooks?: VisorHooks - SDK hooks for human input and check completion
    • workflowInputs?: Record<string, unknown> - Inputs when executing within a workflow
    • args?: Record<string, unknown> - Custom arguments from on_init directives
  • HumanInputRequest - Request object for human input hooks

    • checkId: string - Check ID requesting input
    • prompt: string - Prompt to display
    • placeholder?: string - Placeholder text
    • allowEmpty: boolean - Whether empty input is allowed
    • multiline: boolean - Whether multiline input is supported
    • timeout?: number - Timeout in milliseconds
    • default?: string - Default value
  • TagFilter - Tag filtering configuration

    • include?: string[] - Tags to include (ANY match)
    • exclude?: string[] - Tags to exclude (ANY match)

Refer to src/types/config.ts for VisorConfig, Issue, and related types.

Safety & Criticality (Quick Note)

When building configs programmatically, model safety explicitly:

  • Declare criticality on steps with criticality: external|internal|policy|info.
  • Add contracts to critical steps:
    • assume: preconditions checked before execution
    • guarantee: postconditions checked after execution
  • Use declarative transitions for routing rather than goto_js.

Example config (JS object):

const cfg = await loadConfig({
  version: '1.0',
  checks: {
    'post-comment': {
      type: 'github',
      criticality: 'external',
      on: ['pr_opened'],
      op: 'comment.create',
      assume: ["isMember()"],
      guarantee: ["output && typeof output.id === 'number'"],
      continue_on_failure: false,
    },
    // Structured outputs with unified `schema` (object) for validation
    'summarize-json': {
      type: 'ai',
      schema: {
        type: 'object',
        properties: { ok: { type: 'boolean' }, items: { type: 'array', items: { type: 'string' } } },
        required: ['ok', 'items']
      },
      prompt: 'Return JSON with ok and items...'
    },
    // Command/script can also use JSON Schema via `schema`
    'aggregate': {
      type: 'script',
      content: 'return { all_valid: true };',
      schema: { type: 'object', properties: { all_valid: { type: 'boolean' } }, required: ['all_valid'], additionalProperties: false }
    }
  },
});

Notes

  • SDK adds no new sandboxing or providers; all safety lives in the core engine.
  • For offline demos, unset provider env vars if you rely on mock providers.
  • You can still use all CLI features alongside the SDK in the same project.

Examples (in repo)

  • examples/sdk-basic.mjs (ESM) – Minimal example with raw config object
  • examples/sdk-cjs.cjs (CJS) – CommonJS usage
  • examples/sdk-manual-config.mjs (ESM) – Manual config construction with validation
  • examples/sdk-typescript.ts (TypeScript) – Type-safe example showing:
    • Full TypeScript type safety with SDK type definitions
    • Importing types from @probelabs/visor/sdk
    • Type inference for configs and results
    • Compile-time type checking
    • Using exported types (VisorConfig, RunOptions)
    • To run: npx tsc examples/sdk-typescript.ts --module esnext --target es2022 --moduleResolution bundler --esModuleInterop --skipLibCheck && node examples/sdk-typescript.js
  • examples/sdk-comprehensive.mjs (ESM) – Complex example showing:
    • Multi-level check dependencies (depends_on)
    • Tag filtering
    • Parallel execution control
    • Dependency resolution with resolveChecks()
    • Strict validation mode
    • Complete pipeline execution

These are also exercised by CI smoke tests.