refactor: reorganize repo docs and tooling layout

Consolidate the repository into clearer apps, tools, and layered docs areas so contributors can navigate and maintain it more reliably. Align validation, metadata sync, and CI around the same canonical workflow to reduce drift across local checks and GitHub Actions.
This commit is contained in:
sck_0
2026-03-06 15:01:38 +01:00
parent 5d17564608
commit 45844de534
3384 changed files with 13894 additions and 586586 deletions

View File

@@ -0,0 +1,156 @@
const fs = require('fs');
const path = require('path');
const yaml = require('yaml');
const { listSkillIds, parseFrontmatter } = require('../lib/skill-utils');
const { findProjectRoot } = require('../lib/project-root');
const ROOT = findProjectRoot(__dirname);
const SKILLS_DIR = path.join(ROOT, 'skills');
const ALLOWED_FIELDS = new Set([
'name',
'description',
'risk',
'source',
'license',
'compatibility',
'metadata',
'allowed-tools',
'date_added',
'category',
'id',
]);
function isPlainObject(value) {
return value && typeof value === 'object' && !Array.isArray(value);
}
function coerceToString(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value.trim();
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
if (Array.isArray(value)) {
const simple = value.every(item => ['string', 'number', 'boolean'].includes(typeof item));
return simple ? value.map(item => String(item).trim()).filter(Boolean).join(', ') : JSON.stringify(value);
}
if (isPlainObject(value)) {
return JSON.stringify(value);
}
return String(value).trim();
}
function appendMetadata(metadata, key, value) {
const nextValue = coerceToString(value);
if (!nextValue) return;
if (!metadata[key]) {
metadata[key] = nextValue;
return;
}
if (metadata[key].includes(nextValue)) return;
metadata[key] = `${metadata[key]}, ${nextValue}`;
}
function collectAllowedTools(value, toolSet) {
if (!value) return;
if (typeof value === 'string') {
value
.split(/[\s,]+/)
.map(token => token.trim())
.filter(Boolean)
.forEach(token => toolSet.add(token));
return;
}
if (Array.isArray(value)) {
value
.map(token => String(token).trim())
.filter(Boolean)
.forEach(token => toolSet.add(token));
}
}
function normalizeSkill(skillId) {
const skillPath = path.join(SKILLS_DIR, skillId, 'SKILL.md');
const content = fs.readFileSync(skillPath, 'utf8');
const { data, body, hasFrontmatter } = parseFrontmatter(content);
if (!hasFrontmatter) return false;
let modified = false;
const updated = { ...data };
const metadata = isPlainObject(updated.metadata) ? { ...updated.metadata } : {};
if (updated.metadata !== undefined && !isPlainObject(updated.metadata)) {
appendMetadata(metadata, 'legacy_metadata', updated.metadata);
modified = true;
}
const allowedTools = new Set();
collectAllowedTools(updated['allowed-tools'], allowedTools);
collectAllowedTools(updated.tools, allowedTools);
collectAllowedTools(updated.tool_access, allowedTools);
if (updated.tools !== undefined) {
delete updated.tools;
modified = true;
}
if (updated.tool_access !== undefined) {
delete updated.tool_access;
modified = true;
}
for (const key of Object.keys(updated)) {
if (ALLOWED_FIELDS.has(key)) continue;
if (key === 'tags') {
appendMetadata(metadata, 'tags', updated[key]);
} else {
appendMetadata(metadata, key, updated[key]);
}
delete updated[key];
modified = true;
}
if (allowedTools.size) {
updated['allowed-tools'] = Array.from(allowedTools).join(' ');
modified = true;
} else if (updated['allowed-tools'] !== undefined) {
delete updated['allowed-tools'];
modified = true;
}
if (Object.keys(metadata).length) {
updated.metadata = metadata;
modified = true;
} else if (updated.metadata !== undefined) {
delete updated.metadata;
modified = true;
}
if (!modified) return false;
const ordered = {};
const order = ['id', 'name', 'description', 'category', 'risk', 'source', 'license', 'compatibility', 'date_added', 'allowed-tools', 'metadata'];
for (const key of order) {
if (updated[key] !== undefined) {
ordered[key] = updated[key];
}
}
const fm = yaml.stringify(ordered).trimEnd();
const bodyPrefix = body.length && (body.startsWith('\n') || body.startsWith('\r\n')) ? '' : '\n';
const next = `---\n${fm}\n---${bodyPrefix}${body}`;
fs.writeFileSync(skillPath, next);
return true;
}
function run() {
const skillIds = listSkillIds(SKILLS_DIR);
let updatedCount = 0;
for (const skillId of skillIds) {
if (normalizeSkill(skillId)) updatedCount += 1;
}
console.log(`Normalized frontmatter for ${updatedCount} skills.`);
}
if (require.main === module) {
run();
}
module.exports = { run };