- 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.
280 lines
9.0 KiB
JavaScript
280 lines
9.0 KiB
JavaScript
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`;
|
|
}
|