@vertesia/build-tools 1.5.0-dev.20260722.120446Z → 1.5.0-dev.20260804.124748Z

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.
Files changed (59) hide show
  1. package/README.md +297 -266
  2. package/lib/bin/build.js +26 -1
  3. package/lib/bin/build.js.map +1 -1
  4. package/lib/bin/config.d.ts +12 -0
  5. package/lib/bin/config.d.ts.map +1 -1
  6. package/lib/bin/config.js +25 -0
  7. package/lib/bin/config.js.map +1 -1
  8. package/lib/core/skill-markdown/load-catalog.d.ts +30 -0
  9. package/lib/core/skill-markdown/load-catalog.d.ts.map +1 -0
  10. package/lib/core/skill-markdown/load-catalog.js +69 -0
  11. package/lib/core/skill-markdown/load-catalog.js.map +1 -0
  12. package/lib/core/skill-markdown/preprocess.d.ts +127 -0
  13. package/lib/core/skill-markdown/preprocess.d.ts.map +1 -0
  14. package/lib/core/skill-markdown/preprocess.js +455 -0
  15. package/lib/core/skill-markdown/preprocess.js.map +1 -0
  16. package/lib/core/skill-markdown/schema-validator.d.ts +109 -0
  17. package/lib/core/skill-markdown/schema-validator.d.ts.map +1 -0
  18. package/lib/core/skill-markdown/schema-validator.js +420 -0
  19. package/lib/core/skill-markdown/schema-validator.js.map +1 -0
  20. package/lib/core/transformers/index.d.ts +1 -1
  21. package/lib/core/transformers/index.d.ts.map +1 -1
  22. package/lib/core/transformers/index.js +1 -1
  23. package/lib/core/transformers/index.js.map +1 -1
  24. package/lib/core/transformers/skill.d.ts +15 -1
  25. package/lib/core/transformers/skill.d.ts.map +1 -1
  26. package/lib/core/transformers/skill.js +82 -60
  27. package/lib/core/transformers/skill.js.map +1 -1
  28. package/lib/import-transform/detector.d.ts +19 -6
  29. package/lib/import-transform/detector.d.ts.map +1 -1
  30. package/lib/import-transform/detector.js +73 -24
  31. package/lib/import-transform/detector.js.map +1 -1
  32. package/lib/import-transform/index.d.ts.map +1 -1
  33. package/lib/import-transform/index.js +10 -1
  34. package/lib/import-transform/index.js.map +1 -1
  35. package/lib/import-transform/patterns.d.ts +7 -8
  36. package/lib/import-transform/patterns.d.ts.map +1 -1
  37. package/lib/import-transform/patterns.js +7 -8
  38. package/lib/import-transform/patterns.js.map +1 -1
  39. package/lib/index.d.ts +7 -3
  40. package/lib/index.d.ts.map +1 -1
  41. package/lib/index.js +9 -3
  42. package/lib/index.js.map +1 -1
  43. package/lib/vite/dev-server.d.ts +10 -0
  44. package/lib/vite/dev-server.d.ts.map +1 -1
  45. package/lib/vite/dev-server.js +36 -4
  46. package/lib/vite/dev-server.js.map +1 -1
  47. package/package.json +9 -12
  48. package/src/bin/build.ts +31 -2
  49. package/src/bin/config.ts +27 -0
  50. package/src/core/skill-markdown/load-catalog.ts +100 -0
  51. package/src/core/skill-markdown/preprocess.ts +632 -0
  52. package/src/core/skill-markdown/schema-validator.ts +517 -0
  53. package/src/core/transformers/index.ts +3 -0
  54. package/src/core/transformers/skill.ts +116 -53
  55. package/src/import-transform/detector.ts +78 -26
  56. package/src/import-transform/index.ts +12 -2
  57. package/src/import-transform/patterns.ts +7 -12
  58. package/src/index.ts +35 -2
  59. package/src/vite/dev-server.ts +52 -4
package/README.md CHANGED
@@ -1,15 +1,22 @@
1
1
  # @vertesia/build-tools
2
2
 
3
- A flexible Rollup plugin for transforming imports with custom compilers and validation. Built for Vertesia but usable in any project.
3
+ Custom import syntaxes for Vertesia packages `?skill`, `?skills`, `?template`, `?templates`,
4
+ `?prompt`, `?raw`, and bare `SKILL.md` / `TEMPLATE.md` — resolved at build time into real
5
+ JavaScript modules, with schema validation.
4
6
 
5
- ## Features
7
+ Ships three entry points:
6
8
 
7
- - 🎯 **Pattern-based import transformation** - Match imports by path patterns
8
- - **Built-in Zod validation** - Validate transformed data at build time
9
- - 🔧 **Preset transformers** - Ready-to-use transformers for common cases
10
- - 🎨 **Custom transformers** - Easy to create custom transformation logic
11
- - 📦 **TypeScript-first** - Full type safety with TypeScript
12
- - ⚡ **Fast** - Efficient transformation with minimal overhead
9
+ | Entry point | Used for |
10
+ | ----------- | -------- |
11
+ | `vertesia-build` CLI | Build-time. Runs as a post-`tsc` step over the emitted `lib/`. |
12
+ | `@vertesia/build-tools/vite` | Dev-time. Same transformers applied to sources at request time. |
13
+ | `@vertesia/build-tools` | Programmatic. `transformImports()`, the transformers, the skill preprocessor. |
14
+
15
+ > **Not a bundler plugin.** Earlier versions of this package were a Rollup plugin
16
+ > (`vertesiaImportPlugin`). It is now a standalone post-`tsc` transformer: it reads the JavaScript
17
+ > `tsc` already emitted, writes generated modules beside it, and rewrites the importing files in
18
+ > place. Nothing here depends on Rollup, Rolldown, webpack or Vite at build time — the output is
19
+ > plain ESM that any bundler can consume afterwards.
13
20
 
14
21
  ## Installation
15
22
 
@@ -17,349 +24,374 @@ A flexible Rollup plugin for transforming imports with custom compilers and vali
17
24
  pnpm add -D @vertesia/build-tools
18
25
  ```
19
26
 
20
- ## Quick Start
21
-
22
- ### Using Preset Transformers
27
+ ## Quick start
23
28
 
24
- ```typescript
25
- // rollup.config.js
26
- import { vertesiaImportPlugin, skillTransformer, rawTransformer } from '@vertesia/build-tools';
29
+ Add a `vertesia-build` block to your `package.json` and run the CLI after `tsc`:
27
30
 
28
- export default {
29
- input: 'src/index.ts',
30
- output: {
31
- dir: 'dist',
32
- format: 'es'
33
- },
34
- plugins: [
35
- vertesiaImportPlugin({
36
- transformers: [
37
- skillTransformer, // Handles .md?skill imports
38
- rawTransformer // Handles ?raw imports
39
- ]
40
- })
41
- ]
42
- };
31
+ ```json
32
+ {
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json && vertesia-build"
35
+ },
36
+ "vertesia-build": {
37
+ "libDir": "./lib",
38
+ "srcDir": "./src",
39
+ "transformers": ["skill", "skills", "raw"],
40
+ "assetsDir": "./dist"
41
+ }
42
+ }
43
43
  ```
44
44
 
45
- ### Using in Your Code
45
+ Then use the import syntaxes in your source:
46
46
 
47
47
  ```typescript
48
- // Import a skill definition from markdown
48
+ // A single skill, from a Markdown file with YAML frontmatter
49
49
  import codeReview from './skills/code-review.md?skill';
50
50
 
51
- console.log(codeReview.name); // 'code-review'
52
- console.log(codeReview.title); // 'Code Review Assistant'
53
- console.log(codeReview.description); // 'Skill for reviewing...'
54
- console.log(codeReview.instructions); // Full markdown content
55
- console.log(codeReview.content_type); // 'md' or 'jst'
51
+ codeReview.name; // 'code_review'
52
+ codeReview.title; // 'Code Review Assistant'
53
+ codeReview.description; // 'Reviews a diff for '
54
+ codeReview.instructions; // the Markdown body
55
+ codeReview.content_type; // 'md' | 'jst'
56
+
57
+ // Every SKILL.md in the sibling directories, as an array
58
+ import allSkills from './skills/all?skills';
56
59
 
57
- // Import raw file content
60
+ // Any file as a string
58
61
  import template from './template.html?raw';
59
- console.log(template); // Raw HTML string
60
62
  ```
61
63
 
62
- ## Preset Transformers
64
+ `transformers` is required, so every preset is opted into explicitly and a typo fails the build
65
+ rather than silently leaving imports untransformed.
63
66
 
64
- ### Skill Transformer
67
+ ### Vite dev mode
65
68
 
66
- Transforms markdown files with frontmatter into skill definition objects.
69
+ ```typescript
70
+ // vite.config.ts
71
+ import { defineConfig } from 'vite';
72
+ import { vertesiaDevServerPlugin } from '@vertesia/build-tools/vite';
67
73
 
68
- **Pattern:** `.md?skill`
74
+ export default defineConfig({
75
+ plugins: [vertesiaDevServerPlugin()],
76
+ });
77
+ ```
78
+
79
+ Defaults to all built-in transformers; pass `{ transformers: ['skill', 'raw'] }` to restrict the
80
+ set. Asset copying and widget bundling are build-time concerns and are skipped in dev.
81
+
82
+ `apiServerPlugin` from the same entry point mounts a Hono tool server as Vite middleware under
83
+ `/api`, with the dev-server plugin already included.
84
+
85
+ ## Transformers
86
+
87
+ | Name | Matches | Produces |
88
+ | ---- | ------- | -------- |
89
+ | `skill` | `*.md?skill`, `*/SKILL.md` | `SkillDefinition` object |
90
+ | `skills` | `<dir>/<name>?skills` | Array of `SkillDefinition`, one per `SKILL.md` in the subdirectories |
91
+ | `template` | `*.md?template`, `*/TEMPLATE.md` | `RenderingTemplateDefinition` |
92
+ | `templates` | `<dir>/<name>?templates` | Array of rendering templates |
93
+ | `prompt` | `*?prompt` | `PromptDefinition` |
94
+ | `raw` | `*?raw` | The file contents as a string |
95
+
96
+ The two collection transformers are *virtual*: there is no file at `./all?skills`, the name before
97
+ the query is simply the generated module's name. That name is required, so two collections in the
98
+ same directory cannot collide.
99
+
100
+ ## The skill transformer
101
+
102
+ ### Input
69
103
 
70
- **Input:** `my-skill.md`
71
104
  ```markdown
72
105
  ---
73
- name: my-skill
106
+ name: my_skill
74
107
  title: My Skill
75
108
  description: A helpful skill
76
109
  content_type: md
110
+ tools: [tool_one, tool_two]
111
+ supporting_tools: [tool_three]
77
112
  context_triggers:
78
- keywords: [skill, helper]
79
- tools: [tool1, tool2]
113
+ keywords: [skill, helper]
80
114
  ---
81
115
 
82
116
  # My Skill
83
117
 
84
- This is the skill content in markdown.
118
+ The instructions the model receives.
85
119
  ```
86
120
 
87
- **Output:**
121
+ Frontmatter is validated with a **strict** Zod schema — an unknown key is a build error, not a
122
+ silently ignored one. `name` and `description` are required; `content_type` defaults to `md`.
123
+
124
+ `supporting_tools` unlock exactly as `tools` do — the two are merged into `tools` on output, so no
125
+ runtime path sees the distinction. The split exists for validation: consumers that check whether a
126
+ skill documents what it unlocks skip the supporting list, which is meant for grants that carry their
127
+ own description and need no usage guidance.
128
+
129
+ `context_triggers` and `execution` each accept a nested form (as above) or a flat legacy form
130
+ (`keywords:`, `tools:`, `data_patterns:` / `language:`, `packages:`, `system_packages:` at the top
131
+ level). When `execution` is present, the first fenced code block in the body is extracted as
132
+ `execution.template`.
133
+
134
+ ### Output
135
+
88
136
  ```typescript
89
137
  {
90
- name: 'my-skill',
91
- title: 'My Skill',
92
- description: 'A helpful skill',
93
- instructions: '# My Skill\n\nThis is the skill content...',
94
- content_type: 'md',
95
- context_triggers: {
96
- keywords: ['skill', 'helper']
97
- },
98
- tools: ['tool1', 'tool2'],
99
- scripts: ['helper.js', 'script.py'], // If .js/.py files exist in skill dir
100
- widgets: ['chart', 'user-select'] // If .tsx files exist in skill dir
138
+ name: 'my_skill',
139
+ title: 'My Skill',
140
+ description: 'A helpful skill',
141
+ instructions: '# My Skill\n\nThe instructions the model receives.',
142
+ content_type: 'md',
143
+ context_triggers: { keywords: ['skill', 'helper'] },
144
+ tools: ['tool_one', 'tool_two', 'tool_three'],
145
+ scripts: ['helper.js'], // present only if discovered
146
+ widgets: ['chart'], // present only if discovered
101
147
  }
102
148
  ```
103
149
 
104
- **Type:** `SkillDefinition` (exported from package)
150
+ Typed as `SkillDefinition`, exported from the package. The output schema is `passthrough`, so
151
+ fields added by `properties.ts` survive validation.
105
152
 
106
- **Asset Discovery:** The skill transformer automatically discovers:
107
- - Script files (`.js`, `.py`) in the skill directory → added to `scripts` array
108
- - Widget files (`.tsx`) in the skill directory → added to `widgets` array (without extension)
153
+ ### Asset discovery
109
154
 
110
- **Asset Copying:** Script files are automatically copied to `{assetsDir}/scripts/` during build. Widget files are automatically compiled to `{assetsDir}/widgets/` during build.
155
+ Files sitting beside the skill are picked up automatically:
111
156
 
112
- **SkillDefinition Schema:**
113
- ```typescript
114
- {
115
- name: string; // Required: Unique skill name (kebab-case)
116
- title?: string; // Optional: Display title
117
- description: string; // Required: Short description
118
- instructions: string; // Required: Skill instructions (markdown)
119
- content_type: 'md' | 'jst'; // Required: Content type
120
- input_schema?: { // Optional: JSON Schema for parameters
121
- type: 'object';
122
- properties?: Record<string, any>;
123
- required?: string[];
124
- };
125
- context_triggers?: { // Optional: Auto-injection triggers
126
- keywords?: string[]; // Keywords to trigger this skill
127
- tool_names?: string[]; // Tools that suggest this skill
128
- data_patterns?: string[]; // Regex patterns for data matching
129
- };
130
- execution?: { // Optional: Code execution config
131
- language: string; // Programming language
132
- packages?: string[]; // Required packages
133
- system_packages?: string[]; // System-level packages
134
- template?: string; // Code template
135
- };
136
- tools?: string[]; // Optional: Related tool names
137
- scripts?: string[]; // Optional: Script files in skill dir
138
- widgets?: string[]; // Optional: Widget names in skill dir
139
- isEnabled?: (context: any) => Promise<boolean>; // Optional: Runtime filter function
140
- }
157
+ ```text
158
+ my-skill/
159
+ ├── SKILL.md # frontmatter + instructions
160
+ ├── properties.ts # runtime properties (optional)
161
+ ├── helper.js # skill.scripts, copied to {assetsDir}/scripts/
162
+ └── chart.tsx # skill.widgets, bundled to {assetsDir}/{widgetsDir}/
141
163
  ```
142
164
 
143
- ### Runtime Properties (`properties.ts`)
165
+ `.js` and `.py` files become `scripts` and are copied verbatim. `.tsx` files become `widgets` and
166
+ are bundled with esbuild. Both are skipped entirely when `assetsDir` is `false`.
167
+
168
+ ### Runtime properties (`properties.ts`)
144
169
 
145
- For properties that cannot be defined in YAML frontmatter (like functions), create a `properties.ts` file in your skill directory:
170
+ For anything that cannot be expressed in YAML functions, most of all — add a `properties.ts`
171
+ next to the `SKILL.md`:
146
172
 
147
173
  ```typescript
148
174
  // my-skill/properties.ts
149
175
  import type { ToolUseContext } from '@vertesia/tools-sdk';
150
176
 
151
177
  export default {
152
- // Function to check if skill is enabled
153
- isEnabled: async (context: ToolUseContext): Promise<boolean> => {
154
- return context.project?.settings?.myFeature === true;
155
- },
156
-
157
- // You can override any frontmatter property
158
- description: 'Dynamically set description',
159
-
160
- // Add any other SkillDefinition properties
178
+ isEnabled: async (context: ToolUseContext): Promise<boolean> => {
179
+ return context.project?.settings?.myFeature === true;
180
+ },
181
+ description: 'Overrides the frontmatter description',
161
182
  };
162
183
  ```
163
184
 
164
- **How it works:**
165
- 1. The `properties.ts` file must export a default object of type `Partial<SkillDefinition>`
166
- 2. Properties from `properties.ts` **override** those from frontmatter
167
- 3. During build, the SKILL.md transformer generates code that imports `./properties.js`
168
- 4. Rollup automatically transpiles `properties.ts` to `properties.js` (via TypeScript plugin)
169
- 5. Runtime validation ensures `isEnabled` (if present) is a function
170
- 6. Build fails with clear errors if validation fails
185
+ The default export is a `Partial<SkillDefinition>` and its properties **override** frontmatter. When
186
+ the file exists, the generated chunk imports `./properties.js` the JavaScript `tsc` emitted from
187
+ it and spreads it over the skill object, so the merge happens at runtime rather than at build
188
+ time. A generated guard throws if `isEnabled` is present but is not a function.
171
189
 
172
- **Directory structure:**
173
- ```
174
- my-skill/
175
- ├── SKILL.md # Declarative properties (frontmatter + markdown)
176
- ├── properties.ts # Runtime properties (functions, overrides)
177
- ├── helper.js # Script files (auto-discovered)
178
- └── chart.tsx # Widget files (auto-discovered)
179
- ```
190
+ ## Skill Markdown preprocessing
180
191
 
181
- ### Raw Transformer
192
+ Skill bodies routinely name tools and other skills, and embed example payloads. Left as prose,
193
+ those drift out of sync with the tool definitions they describe and nothing catches it. The
194
+ preprocessor makes the references explicit so the build can verify them.
182
195
 
183
- Imports any file as a raw string.
196
+ Four constructs, all optional:
184
197
 
185
- **Pattern:** `?raw`
198
+ | Written | Rendered | Verified |
199
+ | ------- | -------- | -------- |
200
+ | `{@tool fetch_document}` | `` `fetch_document` `` | tool exists, name unambiguous |
201
+ | `{@skill web_search}` | `` `learn_web_search` `` | skill exists, name unambiguous |
202
+ | `{@param fetch_document.format}` | `` `format` `` | that tool's schema declares the field |
203
+ | ` ```json tool=fetch_document ` | plain ` ```json ` fence | payload validates against the tool's schema |
186
204
 
187
- **Usage:**
188
- ```typescript
189
- import html from './template.html?raw';
190
- import css from './styles.css?raw';
191
- import txt from './data.txt?raw';
205
+ ````markdown
206
+ Call {@tool fetch_document} to load the object — {@param fetch_document.format} selects the
207
+ representation then read it with {@skill artifacts}.
208
+
209
+ ```json tool=fetch_document
210
+ { "id": "abc123", "format": "text" }
192
211
  ```
212
+ ````
193
213
 
194
- ## Custom Transformers
214
+ A `{@param …}` path is spelled like a `dispatch` descriptor: dots nest and `[]` walks an array, so
215
+ `{@param batch_execute.inputs[].input}` resolves. Only the path is rendered.
195
216
 
196
- Create your own transformers for specific use cases:
217
+ Everything fails closed: an unknown tool, an ambiguous name, an unterminated `{@`, a `tool=` tag on
218
+ a non-JSON fence, a duplicate tag, or a payload the schema rejects all stop the build with the file
219
+ and the offending name in the message. Inline code spans and untagged fences are left untouched.
197
220
 
198
- ```typescript
199
- import { vertesiaImportPlugin } from '@vertesia/build-tools';
200
- import { z } from 'zod';
221
+ ### Wiring the catalog
201
222
 
202
- // Define your schema
203
- const InteractionSchema = z.object({
204
- name: z.string(),
205
- type: z.enum(['form', 'modal', 'dialog']),
206
- fields: z.array(z.object({
207
- name: z.string(),
208
- type: z.string()
209
- }))
210
- });
223
+ The preprocessor is pure — it never reads a registry itself. The consuming package names a module
224
+ that supplies one:
211
225
 
212
- export default {
213
- plugins: [
214
- vertesiaImportPlugin({
215
- transformers: [
216
- {
217
- // Match pattern
218
- pattern: /\.interaction\.json$/,
219
-
220
- // Optional validation schema
221
- schema: InteractionSchema,
222
-
223
- // Transform function
224
- transform: (content, filePath) => {
225
- const json = JSON.parse(content);
226
-
227
- // Add computed fields
228
- json.timestamp = Date.now();
229
- json.source = filePath;
230
-
231
- return {
232
- data: json
233
- };
234
- }
235
- }
236
- ]
237
- })
238
- ]
239
- };
226
+ ```json
227
+ {
228
+ "vertesia-build": {
229
+ "libDir": "./lib",
230
+ "srcDir": "./src",
231
+ "transformers": ["skill", "skills", "raw"],
232
+ "skillCatalog": "./lib/skill-catalog.js"
233
+ }
234
+ }
240
235
  ```
241
236
 
242
- ## API
237
+ That module exports the catalog (arrays are accepted anywhere a set is):
243
238
 
244
- ### `vertesiaImportPlugin(config)`
239
+ ```typescript
240
+ export const tools = new Set(['fetch_document', 'batch_execute']);
241
+ export const skills = new Set(['web_search', 'artifacts']);
242
+
243
+ // Optional
244
+ export const ambiguousTools = new Set<string>(); // names with >1 provider — always an error
245
+ export const ambiguousSkills = new Set<string>();
246
+ export const unvalidatableTools = new Set<string>(); // known, but no schema available here
247
+ export const validateExample = createSchemaExampleValidator(entries); // returns string[] of errors
248
+ export const validateField = createSchemaFieldValidator(entries); // resolves {@param …} paths
249
+ export const skillToolPrefix = 'learn_'; // default
250
+ export const exampleLanguages = new Set(['json']); // default
251
+ ```
245
252
 
246
- Main plugin factory.
253
+ `createSchemaExampleValidator` (also exported from this package) validates a payload against the
254
+ tool's AJV schema, and additionally resolves *dispatcher* fields — a `string` parameter that carries
255
+ another tool's name, such as `batch_execute.tool_name` — which JSON Schema alone cannot check.
256
+ `createSchemaFieldValidator` walks a `{@param …}` path through the same schemas, descending into
257
+ `anyOf`/`oneOf`/`allOf` branches and following local `$ref`s (resolved against the innermost
258
+ embedded resource, so a nested `$defs` block wins), and reports the declared fields alongside an
259
+ unknown one. A tool with no schema, a tool absent from the entries, and a path crossing an
260
+ unresolvable reference are all errors rather than passes; so is a catalog that omits
261
+ `validateField` while a skill uses `{@param …}`.
247
262
 
248
- **Parameters:**
249
- - `config.transformers` - Array of transformer rules
250
- - `config.assetsDir` - Root directory for asset output (default: `'./dist'`, use `false` to disable)
251
- - `config.scriptsDir` - Directory for script files relative to assetsDir (default: `'scripts'`)
252
- - `config.widgetsDir` - Directory for widget files relative to assetsDir (default: `'widgets'`)
263
+ If `skillCatalog` is configured but `skill` is not among the `transformers`, the build fails. If a
264
+ skill body uses any of the constructs while **no** catalog is configured, the build also
265
+ fails, rather than shipping a raw `{@tool …}` to the model.
253
266
 
254
- **Returns:** Rollup Plugin
267
+ ## Configuration reference
255
268
 
256
- **Asset Management:**
257
- - When `assetsDir` is configured, script files (`.js`, `.py`) discovered in skill directories are automatically copied to `{assetsDir}/{scriptsDir}/`
258
- - Widget files (`.tsx`) are tracked in the skill definition but not copied (compile them separately)
259
- - Set `assetsDir: false` to disable asset copying
269
+ All paths are resolved relative to the package directory.
260
270
 
261
- ### `TransformerRule`
271
+ | Key | Type | Default | Meaning |
272
+ | --- | ---- | ------- | ------- |
273
+ | `libDir` | string | *required* | Root of the compiled output to transform. |
274
+ | `srcDir` | string | *required* | Root of the sources, mirroring `libDir`. |
275
+ | `transformers` | string[] | *required* | Names from the table above. Non-empty. |
276
+ | `assetsDir` | string \| false | `libDir` | Where scripts and widgets are emitted; `false` disables both. |
277
+ | `widgetsDir` | string | `'widgets'` | Sub-directory of `assetsDir` for widget bundles. |
278
+ | `widgetConfig` | object | — | Options forwarded to the esbuild widget bundler, e.g. `{ "minify": true }`. |
279
+ | `skillCatalog` | string | — | Module supplying `{ tools, skills, … }` for the preprocessor. |
262
280
 
263
- Configuration for a single transformer.
281
+ ## Programmatic API
264
282
 
265
- ```typescript
266
- interface TransformerRule {
267
- pattern: RegExp; // Pattern to match imports
268
- transform: TransformFunction; // Transform function
269
- schema?: z.ZodType<any>; // Optional Zod schema
270
- options?: Record<string, unknown>; // Optional custom options
271
- }
272
- ```
273
-
274
- ### `TransformFunction`
283
+ ### `transformImports(options)`
275
284
 
276
- Function that transforms file content.
285
+ Runs the pipeline directly, with the same options as the config block:
277
286
 
278
287
  ```typescript
279
- type TransformFunction = (
280
- content: string,
281
- filePath: string
282
- ) => TransformResult | Promise<TransformResult>;
283
-
284
- interface TransformResult {
285
- data: unknown; // Data to export (serialized to JSON)
286
- imports?: string[]; // Optional imports to inject
287
- code?: string; // Optional custom code generation
288
- }
289
- ```
290
-
291
- ## Advanced Usage
288
+ import { transformImports } from '@vertesia/build-tools';
292
289
 
293
- ### Custom Code Generation
294
-
295
- Instead of JSON export, generate custom code:
296
-
297
- ```typescript
298
- {
299
- pattern: /\.template\.ts$/,
300
- transform: (content, filePath) => {
301
- return {
302
- data: null,
303
- code: `
304
- export function render() {
305
- return ${JSON.stringify(content)};
306
- }
307
- export const filePath = ${JSON.stringify(filePath)};
308
- `
309
- };
310
- }
311
- }
290
+ const result = await transformImports({
291
+ libDir: './lib',
292
+ srcDir: './src',
293
+ transformers: [skillTransformer, rawTransformer],
294
+ assetsDir: './dist',
295
+ });
296
+ // → { filesProcessed, chunksEmitted, assetsCopied, widgetsCompiled }
312
297
  ```
313
298
 
314
- ### Adding Imports
299
+ Note that `transformers` here takes resolved `TransformerRule` objects, not names; use
300
+ `resolveTransformerNames()` to go from one to the other.
315
301
 
316
- Inject imports into the generated module:
302
+ ### `preprocessSkillMarkdown(markdown, options)`
317
303
 
318
- ```typescript
319
- {
320
- pattern: /\.config\.yaml$/,
321
- transform: (content) => {
322
- const config = parseYaml(content);
323
- return {
324
- data: config,
325
- imports: [
326
- "import { validateConfig } from './validator.js';",
327
- "validateConfig(config);"
328
- ]
329
- };
330
- }
331
- }
332
- ```
304
+ The resolver on its own. Never throws and reads no files — it returns
305
+ `{ markdown, references, examples, errors }` so callers decide what to do with problems.
306
+ `assertSkillMarkdown(markdown, options, source)` is the fail-closed wrapper that throws once,
307
+ listing every problem found.
333
308
 
334
- ## Type Safety
309
+ ### Custom transformers
335
310
 
336
- The plugin exports TypeScript types for use in your code:
311
+ A transformer is a pattern plus a function:
337
312
 
338
313
  ```typescript
339
- import type { SkillDefinition } from '@vertesia/build-tools';
314
+ import { transformImports } from '@vertesia/build-tools';
315
+ import { z } from 'zod';
340
316
 
341
- function processSkill(skill: SkillDefinition) {
342
- console.log(skill.name);
343
- }
317
+ const InteractionSchema = z.object({
318
+ name: z.string(),
319
+ type: z.enum(['form', 'modal', 'dialog']),
320
+ });
321
+
322
+ await transformImports({
323
+ libDir: './lib',
324
+ srcDir: './src',
325
+ transformers: [
326
+ {
327
+ pattern: /\.interaction\.json$/,
328
+ schema: InteractionSchema, // optional, validated at build time
329
+ transform: (content, filePath) => ({
330
+ data: { ...JSON.parse(content), source: filePath },
331
+ }),
332
+ },
333
+ ],
334
+ });
344
335
  ```
345
336
 
346
- ## How It Works
337
+ `transform` returns a `TransformResult`:
338
+
339
+ | Field | Meaning |
340
+ | ----- | ------- |
341
+ | `data` | Value to export as the module default (serialized to JSON). |
342
+ | `code` | Custom module source, used *instead* of the JSON export. |
343
+ | `imports` | Extra import lines to inject at the top of the generated module. |
344
+ | `assets` | Files to copy into `assetsDir`. |
345
+ | `widgets` | Widget entries to bundle. |
346
+
347
+ Set `virtual: true` on the rule when the specifier does not name a real file, as the collection
348
+ transformers do.
349
+
350
+ ## How it works
351
+
352
+ 1. **Scan** — walk `libDir` for `.js` files containing a query-import marker.
353
+ 2. **Detect** — lex each file with `es-module-lexer` and match the specifiers against the
354
+ transformers. A real lexer rather than a regex, because a regex matches anything that *looks*
355
+ like a specifier: a doc comment mentioning `` `?skill` ``, or a plain constant holding a path,
356
+ were both picked up as imports.
357
+ 3. **Resolve** — map the specifier back to its source asset under `srcDir`.
358
+ 4. **Emit** — run the transformer, validate against the rule's Zod schema if it has one, and write
359
+ the generated module as a sibling chunk in `libDir`.
360
+ 5. **Rewrite** — splice the new chunk's path over the original specifier, in place.
361
+ 6. **Recurse** — emitted chunks are re-scanned, so a `?skills` collection chunk that imports
362
+ `SKILL.md` siblings is itself transformed.
363
+ 7. **Finish** — copy discovered assets and bundle discovered widgets with esbuild.
364
+
365
+ A file that cannot be lexed stops the build with `Failed to parse imports in <file>: …`. It only
366
+ reached the queue because it contains a marker, so a swallowed failure would mean a real import
367
+ ships untransformed.
368
+
369
+ ## Error handling
370
+
371
+ Failures name the file and the specific problem. Frontmatter and output-schema failures list every
372
+ offending field:
373
+
374
+ ```text
375
+ Invalid frontmatter in /abs/src/skills/bad/SKILL.md:
376
+ - description: Invalid input: expected string, received undefined
377
+ - frontmatter: Unrecognized key: "descriptoin"
378
+ ```
347
379
 
348
- 1. **Pattern Matching:** Plugin intercepts imports matching configured patterns
349
- 2. **File Loading:** Reads the actual file from disk
350
- 3. **Transformation:** Runs the transform function on file content
351
- 4. **Validation:** If schema provided, validates the result with Zod
352
- 5. **Code Generation:** Generates JavaScript module with the data
353
- 6. **Build Fails:** If validation fails, build stops with clear error messages
380
+ Preprocessor failures are collected and reported together, with line numbers, rather than one per
381
+ run:
354
382
 
355
- ## Error Handling
383
+ ```text
384
+ Skill markdown errors in /abs/src/skills/fetch/SKILL.md:
385
+ - line 12: '{@tool fetch_documnet}' refers to a tool no provider registers
386
+ - line 31: example / must NOT have additional properties ('document_id')
387
+ - line 44: example is tagged 'tool=fetch_document' on a 'bash' fence; only json fences can be validated
388
+ ```
356
389
 
357
- The plugin provides detailed error messages when validation fails:
390
+ Configuration failures are prefixed with the CLI name and exit non-zero:
358
391
 
359
- ```
360
- Error: Validation failed for ./skills/bad-skill.md?skill:
361
- - name: Required
362
- - description: String must contain at least 1 character(s)
392
+ ```text
393
+ vertesia-build: skillCatalog is configured but "skill" is not in vertesia-build.transformers.
394
+ vertesia-build: "vertesia-build.libDir" must be a non-empty string.
363
395
  ```
364
396
 
365
397
  ## License
@@ -368,6 +400,5 @@ Apache-2.0
368
400
 
369
401
  ## Repository
370
402
 
371
- https://github.com/vertesia/composableai
372
-
373
- Part of the Vertesia LLM Studio monorepo.
403
+ <https://github.com/vertesia/composableai> — `packages/build-tools`, part of the Vertesia LLM Studio
404
+ monorepo.