0.11.0: arc42 and ArchiMate examples; ref in every diagram kind

Six example models next to the three C4 ones: arc42 (Cinema Tickets, IoT
Telemetry, Team Wiki) by section, ArchiMate (Insurance Claims, HR
Onboarding, Logistics Tracking) by layer, each validated with 0 errors and
0 warnings. resolveRefs and the editor schemas now take ref: in workflow
and dataflow nodes, sequence participants and lifecycle states, as the
skill already did. Example tests check each framework's folders.
This commit is contained in:
root-at-skic committed 2026-11-08 22:54:23 +02:00
1 parent f4df6a0834
commit 0745de822a
85 files changed
+2945 -67

No files matched your search

+23 -11
View File
@@ -163,13 +163,21 @@ export interface DecisionRef {
/** Collections whose items may carry `decisions` (and `diagram` links), per diagram kind. */
const NODE_COLLECTIONS = ['components', 'nodes', 'participants', 'states'] as const;
/** The list that holds a diagram kind's nodes, where `ref` is resolved. */
const NODE_COLLECTION_OF: Record<string, (typeof NODE_COLLECTIONS)[number]> = {
architecture: 'components',
workflow: 'nodes',
dataflow: 'nodes',
sequence: 'participants',
lifecycle: 'states',
};
const EDGE_COLLECTIONS = ['connections', 'edges', 'flows', 'transitions', 'messages'] as const;
export interface ResolvedDiagram {
/** The diagram as Archify expects it: refs expanded, model block and links removed. */
diagram: Obj;
problems: ModelProblem[];
/** Component index → the registry element it came from. */
/** Node index (in the kind's node list) → the registry element it came from. */
refs: Map<number, RegistryElement>;
/** Node id → diagram path (relative to the model root) it links to. */
links: Map<string, string>;
@@ -253,10 +261,14 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
delete diagram.model;
const decisionRefs = takeDecisionRefs(diagram, options, problems);
const { registry } = options;
const components = Array.isArray(diagram.components) ? diagram.components : undefined;
if (!components) return { diagram, problems, refs, links, used, descriptions, decisionRefs };
// Each kind keeps its nodes in its own list; sequence participants have no tag.
const kind = str(diagram.diagram_type);
const collection = (kind && NODE_COLLECTION_OF[kind]) ?? NODE_COLLECTIONS.find((key) => Array.isArray(diagram[key]));
const nodes = collection && Array.isArray(diagram[collection]) ? (diagram[collection] as unknown[]) : undefined;
if (!collection || !nodes) return { diagram, problems, refs, links, used, descriptions, decisionRefs };
const copied: readonly string[] = kind === 'sequence' ? COPIED_FIELDS.filter((key) => key !== 'tag') : COPIED_FIELDS;
diagram.components = components.map((raw, index) => {
diagram[collection] = nodes.map((raw, index) => {
if (!isObj(raw)) return raw;
const ref = str(raw.ref);
const linkFromComponent = str(raw.diagram);
@@ -275,7 +287,7 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
? `Unknown element "${ref}": no registry of this model defines it.`
: `"${ref}" cannot be resolved: ${options.fileName.split(/[\\/]/).pop()} is not inside a model (no ${MAIN_FILE} above it).`,
severity: 'error',
pointer: `/components/${index}/ref`,
pointer: `/${collection}/${index}/ref`,
});
return { type: 'external', label: ref, ...own, id };
}
@@ -283,19 +295,19 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
refs.set(index, element);
if (element.description) descriptions.set(id, element.description.trim());
for (const key of Object.keys(own)) {
if ((COPIED_FIELDS as readonly string[]).includes(key) && !(OVERRIDABLE_FIELDS as readonly string[]).includes(key)) {
if (copied.includes(key) && !(OVERRIDABLE_FIELDS as readonly string[]).includes(key)) {
problems.push({
message: `"${key}" comes from the registry element "${ref}" and cannot be changed in a diagram.`,
severity: 'error',
pointer: `/components/${index}/${key}`,
pointer: `/${collection}/${index}/${key}`,
});
}
}
const merged: Obj = { id };
for (const key of COPIED_FIELDS) if (element[key] !== undefined) merged[key] = element[key];
for (const key of copied) if (element[key as (typeof COPIED_FIELDS)[number]] !== undefined) merged[key] = element[key as (typeof COPIED_FIELDS)[number]];
for (const [key, value] of Object.entries(own)) {
if (key === 'id') continue;
if (!(COPIED_FIELDS as readonly string[]).includes(key) || (OVERRIDABLE_FIELDS as readonly string[]).includes(key)) merged[key] = value;
if (!copied.includes(key) || (OVERRIDABLE_FIELDS as readonly string[]).includes(key)) merged[key] = value;
}
const link = linkFromComponent ?? element.diagram;
if (link) links.set(id, link);
@@ -353,8 +365,8 @@ export function summarizeDiagram(text: string, relativePath: string): DiagramSum
}
const obj = isObj(doc) ? doc : {};
const meta = isObj(obj.meta) ? obj.meta : {};
const components = Array.isArray(obj.components) ? obj.components : [];
const used = [...new Set(components.map((c) => (isObj(c) ? str(c.ref) : undefined)).filter((r): r is string => !!r))];
const items = NODE_COLLECTIONS.flatMap((key) => (Array.isArray(obj[key]) ? (obj[key] as unknown[]) : []));
const used = [...new Set(items.map((c) => (isObj(c) ? str(c.ref) : undefined)).filter((r): r is string => !!r))];
const decisions = takeDecisionRefs({ ...obj }, { registry: { elements: new Map(), files: [], problems: [] }, fileName: relativePath }, []);
const nodes: DiagramNode[] = [];
const seen = new Set<string>();