# How to Add Support for a New AI Provider to the Impeccable Transformer Architecture

> Learn to add a new AI provider to the Impeccable transformer architecture. Follow simple steps to integrate custom AI support and extend its capabilities.

- Repository: [Paul Bakaus/impeccable](https://github.com/pbakaus/impeccable)
- Tags: how-to-guide
- Published: 2026-03-09

---

**To add a new AI provider to Impeccable, create a transformer module in `scripts/lib/transformers/`, export it in [`scripts/lib/transformers/index.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/index.js), and register the provider in the `providerMappings` array within [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js).**

Impeccable (available at `pbakaus/impeccable`) converts universal skill markdown files in `source/skills/` into provider-specific bundles for AI coding assistants like Cursor, Claude Code, Gemini, and Codex. The transformer architecture allows you to extend this support to any new AI provider by implementing a standardized transformation interface that handles frontmatter conversion, placeholder substitution, and file output.

## Create the Transformer File

Start by adding a new JavaScript file at `scripts/lib/transformers/<new-provider>.js`. This module must export a `transform<NewProvider>` function that follows the signature used by existing transformers such as `transformCursor` in [`scripts/lib/transformers/cursor.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/cursor.js) or `transformClaudeCode` in [`scripts/lib/transformers/claude-code.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/claude-code.js).

```javascript
// scripts/lib/transformers/<new-provider>.js
import path from 'path';
import {
  cleanDir,
  ensureDir,
  writeFile,
  generateYamlFrontmatter,
  replacePlaceholders,
  prefixSkillReferences,
} from '../utils.js';

/**
 * <NewProvider> Transformer (Skills Only)
 *
 * – All skills output to .<new-provider>/skills/{name}/SKILL.md
 * – Frontmatter fields can be customised per provider
 * – Handles user‑invokable skills, optional prefixing, and reference files
 *
 * @param {Array}   skills   – Array of skill objects from readSourceFiles()
 * @param {string}  distDir  – Root distribution directory (e.g. “dist”)
 * @param {Object}  patterns – Design‑pattern data (unused, kept for interface consistency)
 * @param {Object}  options  – { prefix: string, outputSuffix: string }
 */
export function transform<NewProvider>(skills, distDir, patterns = null, options = {}) {
  const { prefix = '', outputSuffix = '' } = options;
  const providerDir = path.join(distDir, `<new-provider>${outputSuffix}`);
  const skillsDir   = path.join(providerDir, `.<new-provider>/skills`);

  // Fresh start
  cleanDir(providerDir);
  ensureDir(skillsDir);

  const allSkillNames = skills.map(s => s.name);
  const commandNames  = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
  let   refCount      = 0;

  for (const skill of skills) {
    const skillName = `${prefix}${skill.name}`;
    const skillDir  = path.join(skillsDir, skillName);

    // ---------- Frontmatter ----------
    const frontmatterObj = {
      name:        skillName,
      description: skill.description,
      // Add any provider‑specific keys here, e.g. version, tags, etc.
    };
    if (skill.license)          frontmatterObj.license          = skill.license;
    if (skill.allowedTools)    frontmatterObj['allowed-tools'] = skill.allowedTools;

    const frontmatter = generateYamlFrontmatter(frontmatterObj);

    // ---------- Body ----------
    let skillBody = replacePlaceholders(skill.body, '<new-provider>', commandNames);
    if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);

    // Provider‑specific body tweaks go here (e.g. placeholder conversion)

    const content = `${frontmatter}\n\n${skillBody}`;
    const outPath = path.join(skillDir, 'SKILL.md');
    writeFile(outPath, content);

    // ---------- Reference files ----------
    if (skill.references && skill.references.length > 0) {
      const refDir = path.join(skillDir, 'reference');
      ensureDir(refDir);
      for (const ref of skill.references) {
        const refOutPath = path.join(refDir, `${ref.name}.md`);
        const refContent = replacePlaceholders(ref.content, '<new-provider>');
        writeFile(refOutPath, refContent);
        refCount++;
      }
    }
  }

  const userInvokableCount = skills.filter(s => s.userInvokable).length;
  const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
  const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
  console.log(`✓ <NewProvider>${prefixInfo}: ${skills.length} skills (${userInvokableCount} user‑invokable)${refInfo}`);
}

```

### Key Implementation Details

- **Output paths**: Use the dot-folder convention `.<new-provider>/skills` to match the structure used by Cursor (`.cursor`), Claude Code (`.claude`), and other providers.
- **Frontmatter customization**: Adapt the `frontmatterObj` to include provider-specific metadata fields. The `generateYamlFrontmatter` utility in [`scripts/lib/utils.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/utils.js) handles YAML serialization.
- **Placeholder handling**: Call `replacePlaceholders` to convert generic `{{arg}}` syntax into the provider's expected format (for example, `$ARG` for Codex or `{{args}}` for Gemini).
- **Reference copying**: The `references` array contains additional markdown files that must travel with the skill; write these to a `reference/` subdirectory within each skill folder.

## Export the New Transformer

Make the transformer available to the build system by adding a named export to [`scripts/lib/transformers/index.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/index.js):

```javascript
// scripts/lib/transformers/index.js
export { transformCursor } from './cursor.js';
export { transformClaudeCode } from './claude-code.js';
export { transformGemini } from './gemini.js';
export { transformCodex } from './codex.js';
export { transformAgents } from './agents.js';
export { transformKiro } from './kiro.js';
export { transform<NewProvider> } from './<new-provider>.js';

```

This central export hub allows [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js) to import your transformer by name using dynamic or static imports.

## Wire the Provider into the Build Pipeline

Register the new provider in the universal bundle assembly by editing [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js). Locate the `assembleUniversal()` function (around line 42) and add your provider to the `providerMappings` array:

```javascript
// scripts/build.js – inside assembleUniversal()
const providerMappings = [
  { provider: 'cursor',      configDir: '.cursor' },
  { provider: 'claude-code', configDir: '.claude' },
  { provider: 'gemini',      configDir: '.gemini' },
  { provider: 'codex',       configDir: '.codex' },
  { provider: 'agents',      configDir: '.agents' },
  { provider: 'kiro',        configDir: '.kiro' },
  // 👇 new entry
  { provider: '<new-provider>', configDir: '.<new-provider>' },
];

```

The build orchestrator ([`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js)) performs the following steps automatically:

1. **Read source files**: `readSourceFiles()` parses each `source/skills/**/SKILL.md`.
2. **Transform per provider**: Each `transform<Provider>()` writes a provider-specific directory under `dist/`.
3. **Assemble universal bundle**: `assembleUniversal()` copies every provider's `.provider/` folder into `dist/universal/`.
4. **Create ZIPs**: `createAllZips()` in [`scripts/lib/zip.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/zip.js) packages each provider bundle for distribution.

## (Optional) Add Tests

Verify your implementation by creating a Jest test suite under `tests/lib/transformers/<new-provider>.test.js`. Use existing tests such as [`cursor.test.js`](https://github.com/pbakaus/impeccable/blob/main/cursor.test.js) as a template:

```javascript
// tests/lib/transformers/<new-provider>.test.js
import { transform<NewProvider> } from '../../../scripts/lib/transformers/<new-provider>.js';
import { readSourceFiles } from '../../../scripts/lib/utils.js';
import path from 'path';
import fs from 'fs';

test('<NewProvider> transformer produces expected files', async () => {
  const root = path.resolve(__dirname, '../../../'); // project root
  const { skills } = await readSourceFiles(root);
  const dist = path.join(root, 'dist-test');
  transform<NewProvider>(skills, dist);

  // Example assertion – ensure a known skill got written
  const skillPath = path.join(dist, '<new-provider>', '.<new-provider>', 'skills', 'teach-impeccable', 'SKILL.md');
  expect(fs.existsSync(skillPath)).toBe(true);
});

```

Run `bun test` to execute the test suite and confirm that your transformer correctly processes skills, handles references, and writes files to the expected directory structure.

## Summary

- **Create** a transformer module at `scripts/lib/transformers/<new-provider>.js` using the `transform<NewProvider>` function signature and utility helpers from [`scripts/lib/utils.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/utils.js).
- **Export** the transformer function in [`scripts/lib/transformers/index.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/index.js) to make it available to the build system.
- **Register** the provider in the `providerMappings` array within [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js) to include it in universal bundles.
- **Test** your implementation by copying an existing test suite in `tests/lib/transformers/` and adjusting the assertions for your provider's output format.

## Frequently Asked Questions

### What is the minimum code required to add a new AI provider to Impeccable?

You need three changes: a transformer file in `scripts/lib/transformers/<new-provider>.js` that exports a `transform<NewProvider>` function, an export statement in [`scripts/lib/transformers/index.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/transformers/index.js), and an entry in the `providerMappings` array in [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js). The transformer can reuse utilities like `cleanDir`, `writeFile`, and `replacePlaceholders` from [`scripts/lib/utils.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/utils.js) to minimize boilerplate.

### How does Impeccable handle placeholder substitution for different providers?

The `replacePlaceholders` utility in [`scripts/lib/utils.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/lib/utils.js) swaps generic `{{arg}}` syntax with provider-specific formats. For example, the Codex transformer converts placeholders to `$ARG` variables, while the Gemini transformer uses `{{args}}`. Your transformer should call `replacePlaceholders(skill.body, '<new-provider>', commandNames)` with the appropriate target syntax for your AI provider.

### Where does the build output go for a new provider?

The `transform<NewProvider>` function receives a `distDir` parameter (typically `dist`). It should create a subdirectory at `dist/<new-provider>/<new-provider>/skills/` containing the transformed SKILL.md files and any reference materials. The `assembleUniversal()` function in [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js) then copies these files into `dist/universal/<new-provider>/skills/` for the combined bundle.

### Can I add custom frontmatter fields for my AI provider?

Yes. Before calling `generateYamlFrontmatter`, modify the `frontmatterObj` to include provider-specific keys. The Claude Code transformer demonstrates extensive frontmatter customization, adding fields like `allowed-tools` and license information. Any properties you add to this object will be serialized into YAML frontmatter in the output SKILL.md files.