mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
Merge main (factory refactor) and adapt Trae to use the config-driven transformer system instead of a standalone trae.js file. Two provider entries (trae-cn, trae) replace the custom dual-directory logic. Also adds placeholderProvider support to the factory for providers that share placeholder configs but need separate output directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
116 lines
3.9 KiB
JavaScript
116 lines
3.9 KiB
JavaScript
import path from 'path';
|
|
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences, PROVIDER_PLACEHOLDERS } from '../utils.js';
|
|
|
|
/**
|
|
* Map from frontmatter field name to extraction spec.
|
|
*
|
|
* - sourceKey: property name on the skill object
|
|
* - yamlKey: key name in YAML frontmatter
|
|
* - condition: if provided, field is only emitted when this returns true
|
|
* - value: if provided, use this instead of skill[sourceKey]
|
|
*/
|
|
const FIELD_SPECS = {
|
|
'user-invocable': {
|
|
sourceKey: 'userInvocable',
|
|
yamlKey: 'user-invocable',
|
|
condition: (skill) => skill.userInvocable,
|
|
value: () => true,
|
|
},
|
|
'argument-hint': {
|
|
sourceKey: 'argumentHint',
|
|
yamlKey: 'argument-hint',
|
|
condition: (skill) => skill.userInvocable && skill.argumentHint,
|
|
},
|
|
license: {
|
|
sourceKey: 'license',
|
|
yamlKey: 'license',
|
|
},
|
|
compatibility: {
|
|
sourceKey: 'compatibility',
|
|
yamlKey: 'compatibility',
|
|
},
|
|
metadata: {
|
|
sourceKey: 'metadata',
|
|
yamlKey: 'metadata',
|
|
},
|
|
'allowed-tools': {
|
|
sourceKey: 'allowedTools',
|
|
yamlKey: 'allowed-tools',
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Create a transformer function for a given provider config.
|
|
*
|
|
* @param {Object} config - Provider configuration from providers.js
|
|
* @returns {Function} transform(skills, distDir, options?)
|
|
*/
|
|
export function createTransformer(config) {
|
|
const { provider, configDir, displayName, frontmatterFields = [], bodyTransform, placeholderProvider } = config;
|
|
const placeholderKey = placeholderProvider || provider;
|
|
|
|
const activeFields = frontmatterFields
|
|
.map((name) => FIELD_SPECS[name])
|
|
.filter(Boolean);
|
|
|
|
return function transform(skills, distDir, options = {}) {
|
|
const { prefix = '', outputSuffix = '' } = options;
|
|
const providerDir = path.join(distDir, `${provider}${outputSuffix}`);
|
|
const skillsDir = path.join(providerDir, `${configDir}/skills`);
|
|
|
|
cleanDir(providerDir);
|
|
ensureDir(skillsDir);
|
|
|
|
const allSkillNames = skills.map((s) => s.name);
|
|
const commandNames = skills
|
|
.filter((s) => s.userInvocable)
|
|
.map((s) => `${prefix}${s.name}`);
|
|
|
|
let refCount = 0;
|
|
|
|
for (const skill of skills) {
|
|
const skillName = `${prefix}${skill.name}`;
|
|
const skillDir = path.join(skillsDir, skillName);
|
|
|
|
// Build frontmatter
|
|
const frontmatterObj = {
|
|
name: skillName,
|
|
description: skill.description,
|
|
};
|
|
|
|
for (const spec of activeFields) {
|
|
if (spec.condition && !spec.condition(skill)) continue;
|
|
const val = spec.value ? spec.value(skill) : skill[spec.sourceKey];
|
|
if (val) frontmatterObj[spec.yamlKey] = val;
|
|
}
|
|
|
|
const frontmatter = generateYamlFrontmatter(frontmatterObj);
|
|
|
|
// Build body
|
|
const cmdPrefix = (PROVIDER_PLACEHOLDERS[placeholderKey] || {}).command_prefix || '/';
|
|
let skillBody = replacePlaceholders(skill.body, placeholderKey, commandNames, allSkillNames);
|
|
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames, cmdPrefix);
|
|
if (bodyTransform) skillBody = bodyTransform(skillBody, skill);
|
|
|
|
const content = `${frontmatter}\n\n${skillBody}`;
|
|
writeFile(path.join(skillDir, 'SKILL.md'), content);
|
|
|
|
// Copy reference files
|
|
if (skill.references && skill.references.length > 0) {
|
|
const refDir = path.join(skillDir, 'reference');
|
|
ensureDir(refDir);
|
|
for (const ref of skill.references) {
|
|
const refContent = replacePlaceholders(ref.content, placeholderKey, [], allSkillNames);
|
|
writeFile(path.join(refDir, `${ref.name}.md`), refContent);
|
|
refCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
const userInvocableCount = skills.filter((s) => s.userInvocable).length;
|
|
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
|
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
|
console.log(`✓ ${displayName}${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
|
};
|
|
}
|