feat: introduce archify skill for generating architecture diagrams

- Added a new Archify skill, enabling users to create polished architecture, workflow, sequence, data-flow, and lifecycle diagrams.
- Implemented comprehensive functionality including rendering, validation, and delivery of diagrams in various formats.
- Integrated a user-friendly command-line interface for generating and previewing diagrams.
- Developed supporting files including package.json, LICENSE, and SKILL.md for documentation and licensing.
- Added unit tests to ensure reliability and functionality of the new skill.

These changes enhance the application by providing a structured approach to visualizing system architecture and workflows, improving user experience and data representation.
This commit is contained in:
shancheas
2026-08-31 11:31:29 +07:00
parent 050fafd731
commit 166e0d40ac
221 changed files with 191228 additions and 1 deletions
@@ -0,0 +1,141 @@
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as simpleIcons from 'simple-icons';
const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, '..');
const catalogPath = path.join(root, 'brand-marks', 'catalog.json');
const outputPath = path.join(root, 'renderers', 'shared', 'generated-brand-marks.mjs');
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
const simpleIconsVersion = JSON.parse(fs.readFileSync(
path.join(root, 'node_modules', 'simple-icons', 'package.json'),
'utf8',
)).version;
const simpleBySlug = new Map(Object.values(simpleIcons)
.filter((icon) => icon && typeof icon === 'object' && icon.slug && icon.path)
.map((icon) => [icon.slug, icon]));
function normalizedList(value) {
return [...new Set((Array.isArray(value) ? value : [])
.map((item) => String(item).trim())
.filter(Boolean))];
}
function lookupForms(value) {
const raw = String(value ?? '').trim().toLocaleLowerCase('en-US');
if (!raw) return [];
return [...new Set([
raw,
raw.replace(/[\s_]+/g, '-'),
raw.replace(/[\s_.-]+/g, ''),
])];
}
function fail(message) {
console.error(`brand catalog: ${message}`);
process.exit(1);
}
if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.marks) || catalog.marks.length === 0) {
fail('catalog.json must contain a non-empty schemaVersion 1 marks array');
}
const ids = new Set();
const lookupKeys = new Map();
const domains = new Map();
const generated = catalog.marks.map((entry, index) => {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.id || '')) fail(`marks[${index}] has an invalid id`);
if (ids.has(entry.id)) fail(`duplicate id ${entry.id}`);
ids.add(entry.id);
const aliases = normalizedList(entry.aliases);
const entryDomains = normalizedList(entry.domains).map((domain) => domain.toLowerCase());
for (const key of [entry.id, ...aliases]) {
for (const form of lookupForms(key)) {
if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) {
fail(`lookup key ${JSON.stringify(key)} is shared by ${lookupKeys.get(form)} and ${entry.id}`);
}
lookupKeys.set(form, entry.id);
}
}
for (const domain of entryDomains) {
if (domains.has(domain) && domains.get(domain) !== entry.id) {
fail(`domain ${domain} is shared by ${domains.get(domain)} and ${entry.id}`);
}
domains.set(domain, entry.id);
}
let mark;
if (entry.simpleIcon) {
const icon = simpleBySlug.get(entry.simpleIcon);
if (!icon) fail(`${entry.id} references missing Simple Icons slug ${entry.simpleIcon}`);
mark = {
id: entry.id,
title: entry.title || icon.title,
category: entry.category,
aliases,
domains: entryDomains,
viewBox: 24,
hex: icon.hex,
path: icon.path,
provenance: {
provider: 'Simple Icons',
providerVersion: simpleIconsVersion,
source: icon.source,
...(icon.guidelines ? { guidelines: icon.guidelines } : {}),
...(icon.license ? { license: icon.license } : {}),
},
};
} else if (entry.custom) {
const custom = entry.custom;
if (!entry.title || !custom.path || !custom.source || !/^[0-9A-F]{6}$/i.test(custom.hex || '')) {
fail(`${entry.id} custom mark requires title, path, source, and six-digit hex`);
}
mark = {
id: entry.id,
title: entry.title,
category: entry.category,
aliases,
domains: entryDomains,
viewBox: custom.viewBox || 24,
hex: custom.hex.toUpperCase(),
path: custom.path,
provenance: {
provider: 'Official brand asset',
source: custom.source,
...(custom.guidelines ? { guidelines: custom.guidelines } : {}),
},
};
} else {
fail(`${entry.id} must provide simpleIcon or custom`);
}
if (!mark.category || !mark.title) fail(`${entry.id} is missing category or title`);
for (const form of lookupForms(mark.title)) {
if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) {
fail(`title ${JSON.stringify(mark.title)} is shared by ${lookupKeys.get(form)} and ${entry.id}`);
}
lookupKeys.set(form, entry.id);
}
return mark;
}).sort((left, right) => left.id.localeCompare(right.id));
const banner = `// Generated by scripts/generate-brand-marks.mjs from brand-marks/catalog.json.\n// Simple Icons ${simpleIconsVersion}. Do not edit by hand.\n`;
const source = `${banner}export const BRAND_MARKS = Object.freeze(${JSON.stringify(generated, null, 2)});\n`;
if (process.argv.includes('--check')) {
const current = fs.existsSync(outputPath)
? fs.readFileSync(outputPath, 'utf8').replace(/\r\n?/g, '\n')
: '';
if (current !== source) {
console.error('generated brand marks are stale — run npm run generate:brand-marks');
process.exit(1);
}
} else {
const temporary = `${outputPath}.${process.pid}.tmp`;
fs.writeFileSync(temporary, source);
fs.renameSync(temporary, outputPath);
console.log(`generated ${path.relative(root, outputPath)} (${generated.length} marks)`);
}