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,279 @@
|
||||
import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';
|
||||
import {
|
||||
createMappedWorkflowCandidate,
|
||||
intrinsicWorkflow,
|
||||
planningWorkflow,
|
||||
} from '../renderers/workflow/workflow-migration-geometry.mjs';
|
||||
import { validateSchema } from '../renderers/shared/validator.mjs';
|
||||
|
||||
export { createHorizontalRankMapper } from '../renderers/workflow/workflow-migration-geometry.mjs';
|
||||
|
||||
const TARGET_SCHEMA_VERSION = 2;
|
||||
|
||||
function clone(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function diagnostic({ code, message, subject = {}, evidence = {}, supportedFixes = [] }) {
|
||||
return {
|
||||
code,
|
||||
severity: 'error',
|
||||
message,
|
||||
subject,
|
||||
evidence,
|
||||
supportedFixes,
|
||||
};
|
||||
}
|
||||
|
||||
function schemaDiagnostics(workflow) {
|
||||
try {
|
||||
validateSchema('workflow', workflow);
|
||||
return [];
|
||||
} catch (error) {
|
||||
if (Array.isArray(error?.archifyDiagnostics)) {
|
||||
return error.archifyDiagnostics.map((entry) => ({ ...entry }));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function legacyLayoutProbe(workflow, qualityProfile) {
|
||||
// The probe discovers fixed-v1 rank centers, not authored canvas capacity.
|
||||
// Omitting viewBox lets a capacity-only legacy failure reach the v2 compiler,
|
||||
// which can measure and monotonically expand the real migrated document.
|
||||
const probe = {
|
||||
schema_version: 1,
|
||||
diagram_type: 'workflow',
|
||||
meta: {
|
||||
title: workflow.meta.title,
|
||||
...(workflow.meta.locale ? { locale: workflow.meta.locale } : {}),
|
||||
legend: { mode: 'hidden' },
|
||||
},
|
||||
lanes: clone(workflow.lanes),
|
||||
nodes: [{
|
||||
id: 'migration_probe',
|
||||
lane: workflow.lanes[0].id,
|
||||
col: 0,
|
||||
type: 'backend',
|
||||
label: 'Probe',
|
||||
}],
|
||||
edges: [],
|
||||
};
|
||||
return compileWorkflow({ workflow: probe, qualityProfile });
|
||||
}
|
||||
|
||||
function legacyRequirementProbe(workflow, qualityProfile) {
|
||||
// Measure the complete fixed-v1 document without treating an authored
|
||||
// viewBox as its intrinsic requirement. The authored viewBox remains a
|
||||
// migration capacity and is preserved separately on the migrated document.
|
||||
const probe = clone(workflow);
|
||||
delete probe.meta.viewBox;
|
||||
return compileWorkflow({ workflow: probe, qualityProfile });
|
||||
}
|
||||
|
||||
function requiredViewBoxFrom(result) {
|
||||
if (Array.isArray(result?.receipt?.requiredViewBox)) {
|
||||
return [...result.receipt.requiredViewBox];
|
||||
}
|
||||
const required = result?.diagnostics
|
||||
?.map((entry) => entry?.evidence?.requiredViewBox)
|
||||
.find((candidate) => Array.isArray(candidate) && candidate.length === 2);
|
||||
return required ? [...required] : null;
|
||||
}
|
||||
|
||||
function expandableViewBox(result) {
|
||||
if (result.ok || !result.diagnostics?.length) return null;
|
||||
if (!result.diagnostics.every((entry) => entry.code === 'workflow/viewbox-capacity')) return null;
|
||||
return requiredViewBoxFrom(result);
|
||||
}
|
||||
|
||||
function result({
|
||||
ok,
|
||||
document,
|
||||
fromSchemaVersion = 1,
|
||||
preExistingDiagnostics = [],
|
||||
migrationDiagnostics = [],
|
||||
newSchemaDiagnostics = [],
|
||||
changedCoordinates = [],
|
||||
oldRequiredViewBox = null,
|
||||
newRequiredViewBox = null,
|
||||
}) {
|
||||
return {
|
||||
ok,
|
||||
...(document ? { document } : {}),
|
||||
fromSchemaVersion,
|
||||
toSchemaVersion: TARGET_SCHEMA_VERSION,
|
||||
preExistingDiagnostics,
|
||||
migrationDiagnostics,
|
||||
newSchemaDiagnostics,
|
||||
changedCoordinates,
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox,
|
||||
};
|
||||
}
|
||||
|
||||
export function migrateWorkflowDocument(inputWorkflow) {
|
||||
if (!inputWorkflow || typeof inputWorkflow !== 'object' || Array.isArray(inputWorkflow)) {
|
||||
return result({
|
||||
ok: false,
|
||||
migrationDiagnostics: [diagnostic({
|
||||
code: 'migration/source-document',
|
||||
message: 'Workflow migration requires one parsed JSON object.',
|
||||
supportedFixes: ['provide one workflow schema v1 JSON document'],
|
||||
})],
|
||||
});
|
||||
}
|
||||
// Migration has no quality override: the authored policy (or effective
|
||||
// standard default) must validate the document after it leaves this process.
|
||||
const qualityProfile = inputWorkflow.meta?.quality_profile || 'standard';
|
||||
|
||||
const workflow = clone(inputWorkflow);
|
||||
const preExistingDiagnostics = schemaDiagnostics(workflow);
|
||||
if (preExistingDiagnostics.length) {
|
||||
return result({
|
||||
ok: false,
|
||||
fromSchemaVersion: workflow.schema_version,
|
||||
preExistingDiagnostics,
|
||||
});
|
||||
}
|
||||
if (workflow.schema_version === TARGET_SCHEMA_VERSION) {
|
||||
const compiled = compileWorkflow({ workflow: clone(workflow), qualityProfile });
|
||||
const requiredViewBox = requiredViewBoxFrom(compiled);
|
||||
if (!compiled.ok) {
|
||||
return result({
|
||||
ok: false,
|
||||
fromSchemaVersion: TARGET_SCHEMA_VERSION,
|
||||
preExistingDiagnostics: compiled.diagnostics,
|
||||
oldRequiredViewBox: requiredViewBox,
|
||||
newRequiredViewBox: requiredViewBox,
|
||||
});
|
||||
}
|
||||
return result({
|
||||
ok: true,
|
||||
document: workflow,
|
||||
fromSchemaVersion: TARGET_SCHEMA_VERSION,
|
||||
oldRequiredViewBox: requiredViewBox,
|
||||
newRequiredViewBox: requiredViewBox,
|
||||
});
|
||||
}
|
||||
if (workflow.schema_version !== 1) {
|
||||
return result({
|
||||
ok: false,
|
||||
fromSchemaVersion: workflow.schema_version,
|
||||
migrationDiagnostics: [diagnostic({
|
||||
code: 'migration/source-schema-version',
|
||||
message: 'Workflow migration to schema v2 requires a schema v1 or v2 source.',
|
||||
subject: { path: '/schema_version' },
|
||||
evidence: { actual: workflow.schema_version, expected: [1, 2] },
|
||||
supportedFixes: ['use an unchanged schema v1 workflow or an already migrated schema v2 workflow as the source'],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
const legacy = compileWorkflow({ workflow: clone(workflow), qualityProfile });
|
||||
const legacyProbe = legacyLayoutProbe(workflow, qualityProfile);
|
||||
if (!legacyProbe.ok) {
|
||||
return result({
|
||||
ok: false,
|
||||
preExistingDiagnostics: legacy.ok ? [] : legacy.diagnostics,
|
||||
migrationDiagnostics: legacyProbe.diagnostics,
|
||||
});
|
||||
}
|
||||
const legacyRequirement = legacyRequirementProbe(workflow, qualityProfile);
|
||||
const oldRequiredViewBox = requiredViewBoxFrom(legacyRequirement)
|
||||
|| requiredViewBoxFrom(legacyProbe)
|
||||
|| requiredViewBoxFrom(legacy);
|
||||
const preExistingLayoutDiagnostics = legacy.ok ? [] : legacy.diagnostics;
|
||||
|
||||
let planned = compileWorkflow({ workflow: intrinsicWorkflow(workflow), qualityProfile });
|
||||
if (!planned.ok) {
|
||||
// Old absolute pins can be invalid at the new rank centers before their X
|
||||
// coordinates are mapped. Obtain the same rank plan from an automatic-route
|
||||
// projection, then validate every authored pin again after mapping.
|
||||
planned = compileWorkflow({ workflow: planningWorkflow(workflow), qualityProfile });
|
||||
}
|
||||
if (!planned.ok) {
|
||||
return result({
|
||||
ok: false,
|
||||
preExistingDiagnostics: preExistingLayoutDiagnostics,
|
||||
newSchemaDiagnostics: planned.diagnostics,
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox: requiredViewBoxFrom(planned),
|
||||
});
|
||||
}
|
||||
|
||||
let mappedCandidate;
|
||||
try {
|
||||
mappedCandidate = createMappedWorkflowCandidate(
|
||||
workflow,
|
||||
legacyProbe.receipt.columns,
|
||||
planned.receipt.columns,
|
||||
);
|
||||
} catch (error) {
|
||||
return result({
|
||||
ok: false,
|
||||
preExistingDiagnostics: preExistingLayoutDiagnostics,
|
||||
migrationDiagnostics: [diagnostic({
|
||||
code: 'migration/rank-mapping',
|
||||
message: 'Could not construct a stable horizontal rank mapping.',
|
||||
evidence: { reason: error.message },
|
||||
supportedFixes: ['report the workflow and compiler receipts to the Archify maintainers'],
|
||||
})],
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox: requiredViewBoxFrom(planned),
|
||||
});
|
||||
}
|
||||
|
||||
const { document: migrated, changedCoordinates } = mappedCandidate;
|
||||
|
||||
let compiled = compileWorkflow({ workflow: migrated, qualityProfile });
|
||||
const requiredExpansion = migrated.meta.viewBox ? expandableViewBox(compiled) : null;
|
||||
if (requiredExpansion) {
|
||||
const current = migrated.meta.viewBox;
|
||||
const expanded = [
|
||||
Math.max(current[0], requiredExpansion[0]),
|
||||
Math.max(current[1], requiredExpansion[1]),
|
||||
];
|
||||
if (expanded[0] > current[0] || expanded[1] > current[1]) {
|
||||
migrated.meta.viewBox = expanded;
|
||||
compiled = compileWorkflow({ workflow: migrated, qualityProfile });
|
||||
}
|
||||
}
|
||||
|
||||
const newRequiredViewBox = requiredViewBoxFrom(compiled) || requiredViewBoxFrom(planned);
|
||||
if (!compiled.ok) {
|
||||
return result({
|
||||
ok: false,
|
||||
preExistingDiagnostics: preExistingLayoutDiagnostics,
|
||||
newSchemaDiagnostics: compiled.diagnostics,
|
||||
changedCoordinates,
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox,
|
||||
});
|
||||
}
|
||||
|
||||
const migratedSchemaDiagnostics = schemaDiagnostics(migrated);
|
||||
if (migratedSchemaDiagnostics.length) {
|
||||
return result({
|
||||
ok: false,
|
||||
preExistingDiagnostics: preExistingLayoutDiagnostics,
|
||||
newSchemaDiagnostics: migratedSchemaDiagnostics,
|
||||
changedCoordinates,
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox,
|
||||
});
|
||||
}
|
||||
|
||||
return result({
|
||||
ok: true,
|
||||
document: migrated,
|
||||
preExistingDiagnostics: preExistingLayoutDiagnostics,
|
||||
changedCoordinates,
|
||||
oldRequiredViewBox,
|
||||
newRequiredViewBox,
|
||||
});
|
||||
}
|
||||
|
||||
export function serializeMigratedWorkflow(workflow) {
|
||||
return `${JSON.stringify(workflow, null, 2)}\n`;
|
||||
}
|
||||
Reference in New Issue
Block a user