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,329 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-v1-compat-'));
function render(mode, doc) {
const input = path.join(tmp, `${mode}.json`);
const output = path.join(tmp, `${mode}.html`);
fs.writeFileSync(input, JSON.stringify(doc));
try {
execFileSync('node', [
path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
input,
output,
], { stdio: ['ignore', 'ignore', 'pipe'] });
return { code: 0, stderr: '', output };
} catch (error) {
return { code: error.status ?? 1, stderr: String(error.stderr || ''), output };
}
}
function validate(mode, doc) {
const input = path.join(tmp, `${mode}-validate.json`);
fs.writeFileSync(input, JSON.stringify(doc));
try {
execFileSync('node', [
path.join(skillRoot, 'bin/archify.mjs'),
'validate',
mode,
input,
'--json',
], { stdio: ['ignore', 'ignore', 'pipe'] });
return { code: 0, stderr: '' };
} catch (error) {
return { code: error.status ?? 1, stderr: String(error.stderr || error.stdout || '') };
}
}
function check(output) {
const stdout = execFileSync('node', [
path.join(skillRoot, 'scripts/check-render-output.mjs'),
output,
], { encoding: 'utf8' });
return JSON.parse(stdout);
}
function legacyDataflowDocument() {
return {
schema_version: 1,
diagram_type: 'dataflow',
meta: { title: 'Legacy data flow', viewBox: [1080, 760] },
stages: [{ label: 'Sources' }, { label: 'Ingest' }],
nodes: [
{ id: 'web', type: 'frontend', label: 'Web App', stage: 0, row: 0 },
{ id: 'edge', type: 'cloud', label: 'Edge API', stage: 1, row: 1 },
],
flows: [
{
from: 'web',
to: 'edge',
label: 'clickstream',
fromSide: 'right',
toSide: 'left',
via: [[184, 157], [184, 271]],
labelAt: [204, 190],
},
],
};
}
const OFFICIAL_V1_EXAMPLES = {
architecture: 'web-app.architecture.json',
workflow: 'agent-tool-call.workflow.json',
sequence: 'cache-miss-request.sequence.json',
dataflow: 'product-analytics.dataflow.json',
lifecycle: 'agent-run.lifecycle.json',
};
for (const [mode, filename] of Object.entries(OFFICIAL_V1_EXAMPLES)) {
test(`official v1 ${mode} baseline remains renderable and valid`, () => {
const doc = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/v1-baseline', filename), 'utf8'));
assert.equal(doc.schema_version, 1);
assert.equal(doc.meta.quality_profile, undefined);
const rendered = render(mode, doc);
assert.equal(rendered.code, 0, rendered.stderr);
assert.ok(fs.statSync(rendered.output).size > 0);
const validated = validate(mode, doc);
assert.equal(validated.code, 0, validated.stderr);
});
}
test('quality-profile lifecycle keeps the checked-in authored via authoritative', () => {
const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-run.lifecycle.json'), 'utf8'));
const transition = doc.transitions.find(({ id }) => id === 'approval-cancelled');
assert.deepEqual(transition.via, [[480, 336], [480, 432], [402, 432]]);
const rendered = render('lifecycle', doc);
assert.equal(rendered.code, 0, rendered.stderr);
const html = fs.readFileSync(rendered.output, 'utf8');
assert.match(
html,
/data-edge-id="approval-cancelled"[^>]*data-composition-points="[^"]*480,336;480,432;402,432[^"]*"/,
);
const validated = validate('lifecycle', doc);
assert.equal(validated.code, 0, validated.stderr);
});
test('legacy v1 architecture auto viewBox accommodates all seven implicit auto legend kinds', () => {
const types = ['frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external'];
const doc = {
schema_version: 1,
diagram_type: 'architecture',
meta: { title: 'Narrow legacy architecture' },
components: types.map((type, index) => ({
id: `component_${index}`,
type,
label: type,
pos: [0, 40 + index * 76],
size: [120, 52],
})),
connections: [],
};
const rendered = render('architecture', doc);
assert.equal(rendered.code, 0, rendered.stderr);
const html = fs.readFileSync(rendered.output, 'utf8');
const svg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
const viewBox = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/)?.slice(1).map(Number);
const baselines = [...svg.matchAll(/data-legend-baseline="([\d.]+)"/g)].map((match) => Number(match[1]));
assert.deepEqual([...svg.matchAll(/data-legend-semantic-kind="([^"]+)"/g)].map((match) => match[1]), types);
assert.ok(viewBox && baselines.length === types.length);
assert.ok(viewBox[0] > 160, 'auto viewBox should widen for its widest measured legend entry');
assert.ok(Math.max(...baselines) < viewBox[1]);
assert.ok(Math.min(...baselines) > 40 + (types.length - 1) * 76 + 52);
const validated = validate('architecture', doc);
assert.equal(validated.code, 0, validated.stderr);
});
function narrowExplicitViewBoxDocuments() {
const componentTypes = ['frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external'];
return {
architecture: {
schema_version: 1,
diagram_type: 'architecture',
meta: { title: 'Legacy narrow architecture', viewBox: [320, 800] },
components: componentTypes.map((type, index) => ({
id: `component_${index}`,
type,
label: type,
pos: [40, 90 + index * 90],
size: [120, 52],
})),
connections: [],
},
workflow: {
schema_version: 1,
diagram_type: 'workflow',
meta: { title: 'Legacy narrow workflow', viewBox: [700, 400] },
lanes: [{ id: 'first', label: 'First' }, { id: 'second', label: 'Second' }],
nodes: componentTypes.map((type, index) => ({
id: `node_${index}`,
lane: index < 3 ? 'first' : 'second',
col: index < 3 ? index * 2 : [0, 2, 4, 5][index - 3],
type,
label: type,
})),
edges: [],
},
sequence: {
schema_version: 1,
diagram_type: 'sequence',
meta: { title: 'Legacy narrow sequence', viewBox: [480, 480] },
participants: [
{ id: 'left', type: 'frontend', label: 'Left' },
{ id: 'right', type: 'backend', label: 'Right' },
],
messages: ['emphasis', 'return', 'security', 'dashed', 'default'].map((variant, index) => ({
from: index % 2 ? 'right' : 'left',
to: index % 2 ? 'left' : 'right',
y: 170 + index * 40,
label: variant,
variant,
})),
},
dataflow: {
schema_version: 1,
diagram_type: 'dataflow',
meta: { title: 'Legacy narrow dataflow', viewBox: [423, 720] },
stages: [{ label: 'Input' }, { label: 'Output' }],
nodes: [
{ id: 'in_0', type: 'backend', label: 'In 0', stage: 0, row: 0 },
{ id: 'out_0', type: 'database', label: 'Out 0', stage: 1, row: 0 },
{ id: 'in_1', type: 'backend', label: 'In 1', stage: 0, row: 1 },
{ id: 'out_1', type: 'backend', label: 'Out 1', stage: 1, row: 1 },
{ id: 'in_2', type: 'backend', label: 'In 2', stage: 0, row: 2 },
{ id: 'out_2', type: 'backend', label: 'Out 2', stage: 1, row: 2 },
{ id: 'in_3', type: 'backend', label: 'In 3', stage: 0, row: 3 },
{ id: 'out_3', type: 'backend', label: 'Out 3', stage: 1, row: 3 },
],
flows: ['emphasis', 'security', 'dashed', 'default'].map((variant, index) => ({
from: `in_${index}`,
to: `out_${index}`,
label: variant,
variant,
route: 'straight',
})),
},
lifecycle: {
schema_version: 1,
diagram_type: 'lifecycle',
meta: { title: 'Legacy narrow lifecycle', viewBox: [420, 800] },
lanes: [{ id: 'main', label: 'Lifecycle' }],
states: ['start', 'active', 'waiting', 'decision', 'success', 'failure', 'neutral', 'external'].map((type, index) => ({
id: `state_${index}`,
type,
label: type,
lane: 'main',
col: index % 2,
yOffset: Math.floor(index / 2) * 72,
})),
transitions: [],
},
};
}
test('legacy v1 explicit narrow viewBoxes never hard-fail on an implicit auto legend', () => {
const expectedNodeCounts = { architecture: 7, workflow: 7, sequence: 2, dataflow: 8, lifecycle: 8 };
for (const [mode, doc] of Object.entries(narrowExplicitViewBoxDocuments())) {
assert.equal(doc.meta.legend, undefined);
const rendered = render(mode, doc);
assert.equal(rendered.code, 0, `${mode}: ${rendered.stderr}`);
const svg = fs.readFileSync(rendered.output, 'utf8').match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
assert.equal((svg.match(/data-node-id=/g) || []).length, expectedNodeCounts[mode], `${mode}: topology must remain intact`);
if (mode === 'lifecycle') {
assert.match(svg, />Legend</, 'a fitting implicit legend should remain visible');
assert.equal((svg.match(/data-legend-semantic-kind=/g) || []).length, 8);
} else {
assert.doesNotMatch(svg, />Legend</, `${mode}: an unfit implicit legend should degrade without overlap`);
}
const validated = validate(mode, doc);
assert.equal(validated.code, 0, `${mode}: ${validated.stderr}`);
}
});
test('legacy v1 architecture geometry remains renderable without an explicit quality profile', () => {
const doc = {
schema_version: 1,
diagram_type: 'architecture',
meta: { title: 'Legacy architecture' },
components: [
{ id: 'auth', type: 'security', label: 'Auth Provider', pos: [40, 110], size: [120, 64] },
{ id: 'lb', type: 'cloud', label: 'Load Balancer', pos: [460, 300], size: [130, 60] },
{ id: 'api', type: 'backend', label: 'API Server', pos: [670, 300], size: [130, 60] },
],
connections: [
{
from: 'auth',
to: 'api',
label: 'verify JWT',
fromSide: 'right',
toSide: 'left',
via: [[620, 142], [620, 330]],
},
],
};
const result = render('architecture', doc);
assert.equal(result.code, 0, result.stderr);
assert.ok(fs.statSync(result.output).size > 0);
});
test('legacy v1 data-flow geometry remains renderable without an explicit quality profile', () => {
const result = render('dataflow', legacyDataflowDocument());
assert.equal(result.code, 0, result.stderr);
assert.ok(fs.statSync(result.output).size > 0);
});
test('legacy v1 data-flow artifact remains valid without an explicit quality profile', () => {
const result = validate('dataflow', legacyDataflowDocument());
assert.equal(result.code, 0, result.stderr);
});
test('legacy v1 composition findings remain visible as advisory warnings', () => {
const rendered = render('dataflow', legacyDataflowDocument());
assert.equal(rendered.code, 0, rendered.stderr);
const receipt = check(rendered.output);
assert.equal(receipt.ok, true);
assert.equal(receipt.composition.metrics.containerBorderRuns, 1);
assert.equal(receipt.composition.summary.errors, 0);
assert.equal(receipt.composition.issues[0].severity, 'warning');
});
test('legacy v1 lifecycle geometry remains renderable without an explicit quality profile', () => {
const doc = {
schema_version: 1,
diagram_type: 'lifecycle',
meta: { title: 'Legacy lifecycle', viewBox: [980, 660] },
lanes: [
{ id: 'main', label: 'Lifecycle phases' },
{ id: 'waiting', label: 'Interruptions' },
{ id: 'exceptions', label: 'Recovery loop' },
{ id: 'terminal', label: 'Terminal exits' },
],
states: [
{ id: 'executing', type: 'active', label: 'Executing', lane: 'main', col: 2 },
{ id: 'approval', type: 'waiting', label: 'Needs Approval', lane: 'waiting', col: 0 },
{ id: 'failed', type: 'failure', label: 'Failed', lane: 'exceptions', col: 0, yOffset: 78 },
{ id: 'cancelled', type: 'failure', label: 'Cancelled', lane: 'terminal', col: 0 },
],
transitions: [
{ from: 'executing', to: 'failed', fromSide: 'left', toSide: 'top', via: [[320, 157], [320, 342], [402, 342]] },
{ from: 'approval', to: 'cancelled', fromSide: 'bottom', toSide: 'top', via: [[320, 336], [320, 430], [402, 430]] },
],
};
const result = render('lifecycle', doc);
assert.equal(result.code, 0, result.stderr);
assert.ok(fs.statSync(result.output).size > 0);
});
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));