Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/a2ui-catalog.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
applyTo: "packages/genui/a2ui*/**"
---

When maintaining A2UI component catalogs, keep the catalog-facing contract in a TypeScript interface marked with `@a2uiCatalog <ComponentName>`. The extractor consumes TypeDoc reflection data and does not parse TS/TSX source itself, so inline the JSON-schema-facing property shape instead of relying on aliases or external interfaces.

Only `@a2uiCatalog` is a custom tag. Use standard TypeDoc-supported comments and tags for metadata: summaries for descriptions, `@remarks` for additional description, `@defaultValue` for schema defaults, and `@deprecated` for deprecated fields. Do not write JSON Schema in comments. Preserve existing enum order when regenerating catalog JSON, because catalog snapshots and LLM prompts can depend on deterministic option ordering.
4 changes: 4 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ coverage:
threshold: "1%"

ignore:
- ".github/**"
- "codecov.yml"
- "packages/genui/**"
- "pnpm-lock.yaml"
- "rstest.config.ts"

fixes:
- "/home/runner/_work/lynx-stack::"
Expand Down
134 changes: 134 additions & 0 deletions packages/genui/a2ui-catalog-extractor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# A2UI Catalog Extractor

`@lynx-js/a2ui-catalog-extractor` generates A2UI component catalog JSON from TypeDoc project reflections.
Developers author catalog-facing TypeScript interfaces and comments; this package consumes TypeDoc reflection data and writes `catalog.json`.

The extractor does not parse TS/TSX source text, does not import the TypeScript compiler API, and does not ask developers to write JSON Schema.

## A2UI Catalog Shape

A2UI v0.9 catalogs describe the capabilities a renderer exposes to an agent:

- `catalogId`: stable catalog identifier used during catalog negotiation.
- `components`: component name to JSON Schema for runtime component props.
- `functions`: named functions with JSON Schema `parameters` and a scalar `returnType`.
- `theme`: theme property name to JSON Schema.

This package generates the `components` map and can wrap it with `catalogId`, `functions`, and `theme` through `createA2UICatalog`.

## Authoring Rules

Only TypeScript `interface` reflections are converted.
Mark the catalog-facing interface with the single custom tag:

```tsx
/**
* @a2uiCatalog Text
*/
export interface TextProps {
/** Literal text or path binding. */
text: string | { path: string };
variant?: 'h1' | 'h2' | 'body';
}
```

The generated schema is:

```json
{
"Text": {
"properties": {
"text": {
"oneOf": [
{ "type": "string" },
{
"type": "object",
"properties": { "path": { "type": "string" } },
"required": ["path"],
"additionalProperties": false
}
],
"description": "Literal text or path binding."
},
"variant": {
"type": "string",
"enum": ["h1", "h2", "body"]
}
},
"required": ["text"]
}
}
```

## Comment Mapping

Only `@a2uiCatalog` is custom.
All other metadata uses standard TypeDoc-supported tags:

- summary text maps to JSON Schema `description`.
- `@remarks` is appended to `description`.
- `@defaultValue` maps to JSON Schema `default`; JSON values are parsed when possible. Wrap object and array defaults in a code span, for example ``@defaultValue `{}```.
- `@deprecated` maps to JSON Schema `deprecated: true`.
- Optional properties are omitted from `required`.

## Type Mapping

The extractor generates schema from the TypeDoc type model:

- `string`, `number`, `boolean`
- string literal unions as `enum`
- other unions as `oneOf`
- `T[]`, `Array<T>`, `ReadonlyArray<T>`
- inline object type literals
- `Record<string, T>`

Unsupported references and ambiguous catalog-facing types such as `any`, `unknown`, `never`, `void`, and nullable unions fail with an actionable error.
Inline the catalog-facing shape in the marked interface instead of relying on imported type aliases.

## CLI

Run TypeDoc conversion and write one file per component:

```bash
a2ui-catalog-extractor --catalog-dir src/catalog --out-dir dist/catalog
```

Use an existing TypeDoc JSON project:

```bash
a2ui-catalog-extractor --typedoc-json typedoc.json --out-dir dist/catalog
```

## API

```ts
import {
createA2UICatalog,
extractCatalogComponents,
extractCatalogComponentsFromTypeDocJson,
writeComponentCatalogs,
} from '@lynx-js/a2ui-catalog-extractor';

const components = await extractCatalogComponents({
sourceFiles: ['src/catalog/Text/index.tsx'],
});

const catalog = createA2UICatalog({
catalogId: 'https://example.com/catalogs/basic/v1/catalog.json',
components,
});

await writeComponentCatalogs({
sourceFiles: ['src/catalog/Text/index.tsx'],
outDir: 'dist/catalog',
});

const componentsFromJson = extractCatalogComponentsFromTypeDocJson(projectJson);
```

## References

- [A2UI Catalogs](https://a2ui.org/concepts/catalogs/)
- [A2UI v0.9 protocol](https://a2ui.org/specification/v0.9-a2ui/)
- [TypeDoc custom tags](https://typedoc.org/documents/Tags.html)
- [TypeDoc JSON output](https://typedoc.org/documents/Options.Output.html)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
// eslint-disable-next-line import/no-unresolved -- Generated by the package build.
import '../dist/cli.js';
39 changes: 39 additions & 0 deletions packages/genui/a2ui-catalog-extractor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@lynx-js/a2ui-catalog-extractor",
"version": "0.0.0",
"private": true,
"description": "TypeDoc-driven A2UI catalog extractor for TypeScript interfaces.",
"license": "Apache-2.0",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./skill": "./skills/a2ui-catalog-extractor/SKILL.md",
"./package.json": "./package.json"
},
"bin": {
"a2ui-catalog-extractor": "./bin/a2ui-catalog-extractor.js"
},
"files": [
"bin",
"dist",
"skills",
"README.md"
],
"scripts": {
"build": "rslib build",
"test": "rstest"
},
"dependencies": {
"typedoc": "^0.28.19"
},
"devDependencies": {
"@rstest/core": "catalog:rstest",
"@types/node": "^24.10.13"
},
"engines": {
"node": ">=22"
}
}
20 changes: 20 additions & 0 deletions packages/genui/a2ui-catalog-extractor/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import type { RslibConfig } from '@rslib/core';
import { defineConfig } from '@rslib/core';

const config: RslibConfig = defineConfig({
lib: [
{ format: 'esm', syntax: 'es2022', dts: { bundle: true, tsgo: true } },
],
source: {
entry: {
index: './src/index.ts',
cli: './src/cli.ts',
},
tsconfigPath: './tsconfig.build.json',
},
});

export default config;
12 changes: 12 additions & 0 deletions packages/genui/a2ui-catalog-extractor/rstest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { defineConfig } from '@rstest/core';

const config: ReturnType<typeof defineConfig> = defineConfig({
name: 'genui/a2ui-catalog-extractor',
globals: true,
include: ['test/**/*.test.ts'],
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: a2ui-catalog-extractor
description: Generate or maintain A2UI catalog JSON from TypeDoc reflections for TypeScript interfaces marked with @a2uiCatalog.
---

# A2UI Catalog Extractor

Use this skill when a task involves authoring, updating, debugging, or generating A2UI catalog JSON from TSX/TypeScript component interfaces.

## Workflow

1. Mark only the catalog-facing TypeScript interface with `@a2uiCatalog <ComponentName>`.
2. Use only standard TypeDoc-supported tags besides `@a2uiCatalog`: summaries, `@remarks`, `@defaultValue`, and `@deprecated`.
3. Keep catalog-facing shapes inline in the marked interface. The extractor consumes TypeDoc reflection data and does not parse source files itself.
4. Generate component catalog files with:

```bash
a2ui-catalog-extractor --catalog-dir src/catalog --out-dir dist/catalog
```

## Supported Type Shapes

- `string`, `number`, `boolean`
- string literal unions, emitted as JSON Schema `enum`
- mixed unions, emitted as `oneOf`
- arrays with `T[]`, `Array<T>`, or `ReadonlyArray<T>`
- inline object type literals
- `Record<string, T>`

Developers should not write JSON Schema in comments. If extraction fails because a reference is unsupported, inline the component's catalog contract in the marked interface.
Loading
Loading