# Programmatic API

Use SpecVerse from your own TypeScript/JavaScript — embed the engine to **parse, infer, realize, and
generate** from `.specly` specs without shelling out to the `spv` CLI. This is the *build-&-run* role:
you're integrating SpecVerse into a tool, service, build step, or CI check. (To *extend* SpecVerse with
new entity types or engines, see [SPECVERSE-EXTENDING.md](SPECVERSE-EXTENDING.md) instead.)

**See also:**
- [SPECVERSE-TOOLING.md](SPECVERSE-TOOLING.md) — the `spv` CLI (same engines, command-line surface)
- [SPECVERSE-SPECIFYING.md](SPECVERSE-SPECIFYING.md) — writing the `.specly` files the API operates on
- [SPECVERSE-AI.md](SPECVERSE-AI.md) — the AI provider abstraction used for behaviour generation

> The `spv` CLI is a thin wrapper over exactly these calls — anything the CLI does, you can do in code.

## When to use the API vs the CLI vs MCP

| You want to… | Use |
|---|---|
| Validate / process specs inside a build tool, service, or CI job | **the programmatic API** (this guide) |
| Run a one-off from a shell or a Makefile | the `spv` CLI ([TOOLING](SPECVERSE-TOOLING.md)) |
| Let an LLM/agent drive SpecVerse | the MCP server (`@specverse/mcp`) |

## Install + the package map

```bash
npm install @specverse/engines @specverse/entities @specverse/types
# add @specverse/runtime only if you render views; @specverse/assets is pulled in transitively
```

| Package / subpath | What it gives you |
|---|---|
| `@specverse/entities` | `EngineRegistry` (discovers + hands you engines), the composed JSON Schema |
| `@specverse/engines/parser` | parse + schema-validate `.specly` → AST |
| `@specverse/engines/inference` | expand a spec to full architecture (controllers/services/events/views) |
| `@specverse/engines/realize` | generate code from an inferred spec + a manifest |
| `@specverse/engines/generators` | Mermaid diagrams, docs |
| `@specverse/engines/ai` | AI provider abstraction (behaviour generation) — see [SPECVERSE-AI.md](SPECVERSE-AI.md) |
| `@specverse/types` | `ParserEngine` / `InferenceEngine` / `RealizeEngine` interfaces + AST types |

## The one pattern: registry → engine → call

Every flow is the same three steps — get the registry, ask it for the engine that provides a
**capability**, initialize, call. This is the exact pattern the CLI uses.

```ts
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';

const registry = new EngineRegistry();
await registry.discover();                                   // load the registered engines

const parser = registry.getEngineForCapability('parse') as ParserEngine;
await parser.initialize();
```

Capabilities: `parse` · `infer` · `realize` · `generate-diagrams`.

## Parse + validate

```ts
import { readFileSync } from 'node:fs';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';

const registry = new EngineRegistry();
await registry.discover();
const parser = registry.getEngineForCapability('parse') as ParserEngine;
await parser.initialize();

const content = readFileSync('my-app.specly', 'utf8');
const result = parser.parseContent(content, 'my-app.specly');

if (result.errors.length > 0) {
  // schema + convention errors, as human-readable strings
  console.error('Invalid spec:', result.errors);
  process.exit(1);
}
const ast = result.ast!;     // SpecVerseAST — validated, conventions expanded
```

`parseContent(content, filename)` does both schema-validation passes and convention processing. An empty
`result.errors` array means the spec is valid; `result.ast` is the expanded AST the other engines consume.

## Infer (expand to full architecture)

```ts
import type { InferenceEngine } from '@specverse/types';

const inferEngine = registry.getEngineForCapability('infer') as InferenceEngine;
await inferEngine.initialize({ options: { verbose: false } });

const inferResult = await inferEngine.infer(ast, {
  generateControllers: true,
  generateServices: true,
  generateEvents: true,
  generateViews: true,
  generateDeployment: false,   // also infer the deployment topology
  verbose: false,
});

inferResult.yaml;   // the inferred .specly (string) — controllers/services/events/views filled in
```

## Realize (generate code)

Realize turns an **inferred** spec into code, driven by an implementation **manifest** (which technology
maps to which instance factory — Fastify/Prisma/React/etc.). Pass the manifest path to `initialize`.

```ts
import type { RealizeEngine } from '@specverse/types';
import { load as parseYaml } from 'js-yaml';

const inferredSpec = parseYaml(inferResult.yaml);   // realizeAll takes the spec object

const realizeEngine = registry.getEngineForCapability('realize') as RealizeEngine;
await realizeEngine.initialize({
  manifestPath: 'manifests/implementation.yaml',
  workingDir: process.cwd(),
});

await realizeEngine.realizeAll(inferredSpec, 'generated/code');   // writes the project tree
```

The CLI resolves the manifest by walking up from the spec for `manifests/implementation.yaml`; in code,
point `manifestPath` at it directly. (Manifest authoring is covered in
[SPECVERSE-REALIZING.md](SPECVERSE-REALIZING.md).)

## Generate diagrams

```ts
const gen = registry.getEngineForCapability('generate-diagrams') as any;
await gen.initialize();

const diagrams = await gen.generateDiagrams(ast, { type: 'all' });  // Map<string, string>
for (const [kind, mermaid] of diagrams) {
  // kind: 'class-diagram' | 'er-diagram' | ... ; mermaid: the ```mermaid body
}
```

## Error handling

Two shapes to handle: **validation errors** (returned in `result.errors`, not thrown) and **engine
errors** (thrown — missing engine, I/O, realize failures).

```ts
const parser = registry.getEngineForCapability('parse') as ParserEngine | undefined;
if (!parser) throw new Error('parser engine not registered — did you call registry.discover()?');

try {
  const result = parser.parseContent(content, filename);
  if (result.errors.length) return { ok: false, errors: result.errors };  // validation: not thrown
  // ...infer / realize / generate...
  return { ok: true };
} catch (err) {
  // engine/runtime failure (I/O, realize emit, etc.)
  return { ok: false, errors: [(err as Error).message] };
}
```

## Integration recipes

**Validation service endpoint** (Express/Fastify):

```ts
// POST /validate  { spec: "<.specly text>" }  → { ok, errors }
app.post('/validate', async (req, res) => {
  const result = parser.parseContent(req.body.spec, 'request.specly');   // parser initialized once at boot
  res.json({ ok: result.errors.length === 0, errors: result.errors });
});
```

**CI gate** (fail the build on an invalid spec):

```ts
import { globSync } from 'glob';
let bad = 0;
for (const f of globSync('specs/**/*.specly')) {
  const r = parser.parseContent(readFileSync(f, 'utf8'), f);
  if (r.errors.length) { console.error(`✗ ${f}`); r.errors.forEach(e => console.error('   ' + e)); bad++; }
}
process.exit(bad ? 1 : 0);
```

**Build-tool plugin** (e.g. a Vite/webpack step): parse on change, fail the build on errors, optionally
`realizeAll` into a generated source dir. Initialize the registry + engines **once** (not per file) — the
registry discovery + engine init are the expensive part; `parseContent` is sub-millisecond for typical
specs.

## TypeScript

The engine interfaces and AST types ship in `@specverse/types`:

```ts
import type { ParserEngine, InferenceEngine, RealizeEngine, SpecVerseAST } from '@specverse/types';
import { EngineRegistry } from '@specverse/entities';
```

`getEngineForCapability` returns the base engine type; cast to the specific interface (`as ParserEngine`)
as shown — that's the pattern the CLI uses.

## Notes

- **Initialize once, reuse.** `new EngineRegistry()` + `discover()` + each engine's `initialize()` is the
  setup cost; do it at startup and reuse the engine instances across calls.
- **The CLI is the reference.** Every command in the `spv` CLI is one of these flows — read its source if
  you need an exact call shape for an edge case.
