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,86 @@
import * as validators from './generated-validators.mjs';
import { throwDiagnosticError } from './diagnostics.mjs';
// "/nodes/3/label" reads much better as "/nodes/3 (id: "router") /label" for the
// LLM fixing the JSON; resolve the nearest enclosing element's id or label.
function annotatedPath(instancePath, data) {
if (!instancePath) return { path: '/', identity: null };
let node = data;
let hint = null;
for (const seg of instancePath.split('/').slice(1)) {
if (node == null || typeof node !== 'object') break;
node = node[/^\d+$/.test(seg) ? Number(seg) : seg];
if (node && typeof node === 'object' && !Array.isArray(node)) {
const tag = node.id ?? node.label;
if (tag != null) hint = String(tag);
}
}
return { path: instancePath, identity: hint };
}
function annotatePath(instancePath, data) {
const annotated = annotatedPath(instancePath, data);
return annotated.identity != null
? `${annotated.path} (id/label: ${JSON.stringify(annotated.identity)})`
: annotated.path;
}
function formatErrors(errors, data) {
return errors.map((e) => {
const where = annotatePath(e.instancePath, data);
const detail = e.params && Object.keys(e.params).length
? ' ' + JSON.stringify(e.params)
: '';
return ` ${where} ${e.message}${detail}`;
}).join('\n');
}
export function validateSchema(diagramType, data) {
const validate = validators[diagramType];
if (!validate) {
throw new Error(`validateSchema: unknown diagram type "${diagramType}"`);
}
if (!validate(data)) {
const diagnostics = validate.errors.map((error) => {
const annotated = annotatedPath(error.instancePath, data);
const subject = {
diagramType,
path: annotated.path,
...(annotated.identity != null ? { identity: String(annotated.identity) } : {}),
};
const evidence = {
keyword: error.keyword,
expected: error.schema,
...error.params,
};
const supportedFixes = {
additionalProperties: [`remove unsupported property ${JSON.stringify(error.params?.additionalProperty)}`],
required: [`add required property ${JSON.stringify(error.params?.missingProperty)}`],
type: [`use ${JSON.stringify(error.params?.type)} at ${annotated.path}`],
enum: [`choose one of ${JSON.stringify(error.params?.allowedValues || [])}`],
pattern: [`match the required pattern ${JSON.stringify(error.params?.pattern)}`],
minimum: [`use a value ${error.params?.comparison || '>='} ${error.params?.limit}`],
maximum: [`use a value ${error.params?.comparison || '<='} ${error.params?.limit}`],
minItems: [`provide at least ${error.params?.limit} item(s)`],
maxItems: [`provide at most ${error.params?.limit} item(s)`],
minLength: [`provide at least ${error.params?.limit} character(s)`],
maxLength: [`provide at most ${error.params?.limit} character(s)`],
}[error.keyword] || [];
const detail = error.params && Object.keys(error.params).length
? ` ${JSON.stringify(error.params)}`
: '';
return {
code: `schema/${error.keyword}`,
severity: 'error',
message: `${annotatePath(error.instancePath, data)} ${error.message}${detail}`,
subject,
evidence,
supportedFixes,
};
});
throwDiagnosticError(
`${diagramType} schema validation failed:\n${formatErrors(validate.errors, data)}`,
diagnostics,
);
}
}