# 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

```bash
npm i -D @probelabs/visor
```

## Quick Start

### JavaScript (ESM)
```ts
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
```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
```js
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:

```ts
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:

```ts
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):
```ts
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.
