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:
@@ -0,0 +1,181 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const skillRoot = path.resolve(here, '..');
|
||||
const cli = path.join(skillRoot, 'bin/archify.mjs');
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-repair-receipt-'));
|
||||
|
||||
function run(args) {
|
||||
return spawnSync(process.execPath, [cli, ...args], {
|
||||
cwd: skillRoot,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
|
||||
function writeFixture(name, source) {
|
||||
const file = path.join(tmp, name);
|
||||
fs.writeFileSync(file, JSON.stringify(source, null, 2));
|
||||
return file;
|
||||
}
|
||||
|
||||
function receipt(result) {
|
||||
assert.doesNotThrow(() => JSON.parse(result.stdout), result.stdout || result.stderr);
|
||||
return JSON.parse(result.stdout);
|
||||
}
|
||||
|
||||
test('repair receipt: malformed JSON is one clean machine object without a Node stack', () => {
|
||||
const input = path.join(tmp, 'malformed.workflow.json');
|
||||
fs.writeFileSync(input, '{broken json');
|
||||
|
||||
const result = run(['validate', 'workflow', input, '--json']);
|
||||
assert.equal(result.status, 1);
|
||||
assert.equal(result.stderr, '');
|
||||
const failure = receipt(result);
|
||||
assert.equal(failure.schemaVersion, 1);
|
||||
assert.equal(failure.ok, false);
|
||||
assert.equal(failure.command, 'validate');
|
||||
assert.equal(failure.stage, 'input');
|
||||
assert.equal(failure.diagnostics.length, 1);
|
||||
assert.deepEqual(failure.diagnostics[0].subject, { input });
|
||||
assert.equal(failure.diagnostics[0].code, 'input/json-parse');
|
||||
assert.equal(failure.diagnostics[0].severity, 'error');
|
||||
assert.match(failure.diagnostics[0].evidence.reason, /JSON/);
|
||||
assert.deepEqual(failure.diagnostics[0].supportedFixes, ['repair the JSON syntax and run validation again']);
|
||||
assert.doesNotMatch(result.stdout, /\n\s+at\s|file:\/\//);
|
||||
});
|
||||
|
||||
test('repair receipt: all five modes identify schema subjects and supported fixes', () => {
|
||||
const cases = {
|
||||
architecture: ['web-app.architecture.json', 'components'],
|
||||
workflow: ['agent-tool-call.workflow.json', 'nodes'],
|
||||
sequence: ['cache-miss-request.sequence.json', 'participants'],
|
||||
dataflow: ['product-analytics.dataflow.json', 'nodes'],
|
||||
lifecycle: ['agent-run.lifecycle.json', 'states'],
|
||||
};
|
||||
|
||||
for (const [type, [example, collection]] of Object.entries(cases)) {
|
||||
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
|
||||
source[collection][0].unexpected = true;
|
||||
const identity = source[collection][0].id;
|
||||
const input = writeFixture(`schema-${type}.json`, source);
|
||||
const result = run(['validate', type, input, '--json']);
|
||||
|
||||
assert.equal(result.status, 1, `${type}: ${result.stderr || result.stdout}`);
|
||||
assert.equal(result.stderr, '', type);
|
||||
const failure = receipt(result);
|
||||
const repair = failure.diagnostics.find((entry) => entry.code === 'schema/additionalProperties');
|
||||
assert.ok(repair, type);
|
||||
assert.deepEqual(repair.subject, {
|
||||
diagramType: type,
|
||||
path: `/${collection}/0`,
|
||||
identity,
|
||||
});
|
||||
assert.equal(repair.evidence.additionalProperty, 'unexpected');
|
||||
assert.deepEqual(repair.supportedFixes, ['remove unsupported property "unexpected"']);
|
||||
}
|
||||
});
|
||||
|
||||
test('repair receipt: human validation formats the same rule without a stack', () => {
|
||||
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
|
||||
source.nodes[0].unexpected = true;
|
||||
const input = writeFixture('human-schema.workflow.json', source);
|
||||
const result = run(['validate', 'workflow', input]);
|
||||
|
||||
assert.equal(result.status, 1);
|
||||
assert.equal(result.stdout, '');
|
||||
assert.match(result.stderr, /\[schema\/additionalProperties\]/);
|
||||
assert.match(result.stderr, /Fix: remove unsupported property "unexpected"/);
|
||||
assert.doesNotMatch(result.stderr, /\n\s+at\s|file:\/\//);
|
||||
});
|
||||
|
||||
test('repair receipt: validate and deliver share exact Clean Flow evidence while delivery preserves the trusted artifact', () => {
|
||||
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
|
||||
source.connections[0] = {
|
||||
...source.connections[0],
|
||||
fromSide: 'right',
|
||||
toSide: 'left',
|
||||
via: [[100, 140], [220, 140]],
|
||||
};
|
||||
const input = writeFixture('blocked-route.architecture.json', source);
|
||||
const output = path.join(tmp, 'trusted.html');
|
||||
const trusted = '<!doctype html><title>trusted prior artifact</title>\n';
|
||||
fs.writeFileSync(output, trusted);
|
||||
|
||||
const validated = run(['validate', 'architecture', input, '--quality', 'showcase', '--json']);
|
||||
const delivered = run(['deliver', 'architecture', input, output, '--quality', 'showcase', '--json']);
|
||||
assert.equal(validated.status, 1, validated.stderr || validated.stdout);
|
||||
assert.equal(delivered.status, 1, delivered.stderr || delivered.stdout);
|
||||
assert.equal(validated.stderr, '');
|
||||
assert.equal(delivered.stderr, '');
|
||||
|
||||
const validateRepair = receipt(validated).diagnostics.find((entry) => entry.code === 'clean-flow/edge-through-node');
|
||||
const deliverRepair = receipt(delivered).diagnostics.find((entry) => entry.code === 'clean-flow/edge-through-node');
|
||||
assert.ok(validateRepair);
|
||||
assert.ok(deliverRepair);
|
||||
assert.deepEqual(deliverRepair, validateRepair);
|
||||
assert.equal(validateRepair.subject.id, 'users-to-cdn');
|
||||
assert.equal(validateRepair.evidence.obstacleId, 'auth');
|
||||
assert.equal(validateRepair.evidence.segmentIndex, 0);
|
||||
assert.equal(validateRepair.evidence.clearancePx, 2);
|
||||
assert.ok(validateRepair.supportedFixes.some((fix) => fix.includes('route/via')));
|
||||
assert.equal(fs.readFileSync(output, 'utf8'), trusted);
|
||||
assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-delivery-')), []);
|
||||
});
|
||||
|
||||
test('repair receipt: repository evidence failures retain a stable rule and exact repair', () => {
|
||||
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
|
||||
source.meta.repository = {
|
||||
url: 'https://github.com/example/repository',
|
||||
revision: '0123456789abcdef0123456789abcdef01234567',
|
||||
};
|
||||
source.components[0].sources = [{ path: 'src/index.js', line: 1 }];
|
||||
const input = writeFixture('evidence-root-required.architecture.json', source);
|
||||
const result = run(['validate', 'architecture', input, '--json']);
|
||||
|
||||
assert.equal(result.status, 1);
|
||||
assert.equal(result.stderr, '');
|
||||
const repair = receipt(result).diagnostics[0];
|
||||
assert.equal(repair.code, 'repository-evidence/root-required');
|
||||
assert.deepEqual(repair.subject, { surface: 'repository-evidence', path: '/meta/repository' });
|
||||
assert.deepEqual(repair.supportedFixes, ['pass --repo-root with the matching local Git checkout']);
|
||||
});
|
||||
|
||||
test('repair receipt: public validate reports borderline desktop readability with a supported fix', () => {
|
||||
const input = writeFixture('borderline-readability.architecture.json', {
|
||||
schema_version: 1,
|
||||
diagram_type: 'architecture',
|
||||
meta: {
|
||||
title: 'Borderline desktop readability',
|
||||
quality_profile: 'showcase',
|
||||
viewBox: [1826, 804],
|
||||
},
|
||||
components: [{
|
||||
id: 'tool-runtime',
|
||||
type: 'security',
|
||||
label: 'ToolRuntime',
|
||||
sublabel: 'OpenAI · Anthropic · provider gateways',
|
||||
pos: [100, 100],
|
||||
size: [194, 70],
|
||||
}],
|
||||
connections: [],
|
||||
});
|
||||
const result = run(['validate', 'architecture', input, '--quality', 'showcase', '--json']);
|
||||
|
||||
assert.equal(result.status, 1, result.stderr || result.stdout);
|
||||
assert.equal(result.stderr, '');
|
||||
const repair = receipt(result).diagnostics.find(
|
||||
(entry) => entry.code === 'composition/desktop-readability',
|
||||
);
|
||||
assert.ok(repair);
|
||||
assert.deepEqual(repair.subject, { check: 'composition' });
|
||||
assert.ok(repair.evidence.projectedFontPx < repair.evidence.minimumProjectedFontPx);
|
||||
assert.ok(repair.supportedFixes.some((fix) => fix.includes('reduce the viewBox width')));
|
||||
});
|
||||
|
||||
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
|
||||
Reference in New Issue
Block a user