@orchestrator-claude/definitions 3.38.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/agents/agentic-architect.md +1 -1
  2. package/agents/architecture-mediator.md +2 -1
  3. package/agents/backend-architect.md +1 -1
  4. package/agents/contract-architect.md +1 -1
  5. package/agents/datamodel-architect.md +1 -1
  6. package/agents/frontend-architect.md +1 -1
  7. package/agents/harness-architect.md +1 -1
  8. package/agents/infra-architect.md +1 -1
  9. package/manifest.json +20 -12
  10. package/package.json +1 -1
  11. package/skills/archify/LICENSE +22 -0
  12. package/skills/archify/SKILL.md +278 -0
  13. package/skills/archify/VENDOR.txt +70 -0
  14. package/skills/archify/assets/template.html +1261 -0
  15. package/skills/archify/bin/archify.mjs +271 -0
  16. package/skills/archify/examples/agent-run.lifecycle.json +64 -0
  17. package/skills/archify/examples/agent-tool-call.workflow.json +88 -0
  18. package/skills/archify/examples/cache-miss-request.sequence.json +75 -0
  19. package/skills/archify/examples/product-analytics.dataflow.json +70 -0
  20. package/skills/archify/examples/web-app.architecture.json +41 -0
  21. package/skills/archify/package-lock.json +80 -0
  22. package/skills/archify/package.json +23 -0
  23. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  24. package/skills/archify/renderers/architecture/render-architecture.mjs +371 -0
  25. package/skills/archify/renderers/dataflow/README.md +82 -0
  26. package/skills/archify/renderers/dataflow/render-dataflow.mjs +295 -0
  27. package/skills/archify/renderers/lifecycle/README.md +91 -0
  28. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +384 -0
  29. package/skills/archify/renderers/sequence/README.md +84 -0
  30. package/skills/archify/renderers/sequence/render-sequence.mjs +257 -0
  31. package/skills/archify/renderers/shared/cli.mjs +44 -0
  32. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  33. package/skills/archify/renderers/shared/geometry.mjs +230 -0
  34. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  35. package/skills/archify/renderers/shared/utils.mjs +88 -0
  36. package/skills/archify/renderers/shared/validator.mjs +40 -0
  37. package/skills/archify/renderers/workflow/README.md +103 -0
  38. package/skills/archify/renderers/workflow/render-workflow.mjs +481 -0
  39. package/skills/archify/schemas/README.md +72 -0
  40. package/skills/archify/schemas/architecture.schema.json +121 -0
  41. package/skills/archify/schemas/common.schema.json +43 -0
  42. package/skills/archify/schemas/dataflow.schema.json +221 -0
  43. package/skills/archify/schemas/lifecycle.schema.json +241 -0
  44. package/skills/archify/schemas/sequence.schema.json +186 -0
  45. package/skills/archify/schemas/workflow.schema.json +332 -0
  46. package/skills/archify/scripts/check-render-output.mjs +297 -0
  47. package/skills/archify/scripts/generate-validators.mjs +66 -0
  48. package/skills/archify/scripts/render-examples.mjs +26 -0
  49. package/skills/archify/test/animation.test.mjs +53 -0
  50. package/skills/archify/test/cli.test.mjs +206 -0
  51. package/skills/archify/test/degraded.test.mjs +157 -0
  52. package/skills/archify/test/generate-validators.test.mjs +40 -0
  53. package/skills/archify/test/geometry.test.mjs +186 -0
  54. package/skills/archify/test/golden.mjs +167 -0
  55. package/skills/archify/test/grid.test.mjs +47 -0
  56. package/skills/archify/test/layout-rules.test.mjs +193 -0
  57. package/skills/archify/test/render-output-checks.test.mjs +75 -0
  58. package/skills/arquitetura-mediada-arquiteto/SKILL.md +20 -3
  59. package/skills/kb-api-pattern/SKILL.md +1 -1
@@ -0,0 +1,257 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs';
4
+ import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs';
5
+ import { componentFill, arrowClassMap, rectsOverlap, asArray, isFinitePoint } from '../shared/geometry.mjs';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const { diagram: sequence, template, outPath } = loadDiagram({
9
+ rendererDir: __dirname,
10
+ diagramType: 'sequence',
11
+ defaultExample: 'cache-miss-request.sequence.json'
12
+ });
13
+
14
+ const viewBox = sequence.meta?.viewBox || [920, 760];
15
+ // The timeline scales with viewBox height: a taller viewBox gains message room,
16
+ // a shorter one shrinks the readable band (validated below) instead of clipping.
17
+ const layout = {
18
+ topY: 72,
19
+ participantW: 86,
20
+ participantH: 54,
21
+ lifelineTop: 142,
22
+ lifelineBottom: viewBox[1] - 65,
23
+ legendY: viewBox[1] - 54,
24
+ leftX: 62,
25
+ colGap: 108,
26
+ labelH: 16
27
+ };
28
+
29
+ const arrowClass = {
30
+ ...arrowClassMap,
31
+ return: ['a-default', 'arrowhead']
32
+ };
33
+
34
+ function participantX(index) {
35
+ return layout.leftX + index * layout.colGap;
36
+ }
37
+
38
+ const participants = new Map(asArray(sequence.participants).map((participant, index) => [
39
+ participant.id,
40
+ {
41
+ ...participant,
42
+ index,
43
+ cx: participantX(index),
44
+ x: participantX(index) - layout.participantW / 2
45
+ }
46
+ ]));
47
+
48
+ function validateSequence() {
49
+ const problems = [];
50
+ if (sequence.schema_version !== 1) problems.push('Sequence files must set "schema_version": 1.');
51
+ if (sequence.diagram_type !== 'sequence') problems.push('Sequence files must set "diagram_type": "sequence".');
52
+ if (!sequence.meta?.title) problems.push('Sequence files must include meta.title.');
53
+ if (!Array.isArray(sequence.participants) || sequence.participants.length < 2) {
54
+ problems.push('Sequence diagrams need at least two participants.');
55
+ }
56
+ if (participants.size !== asArray(sequence.participants).length) problems.push('Participant ids must be unique.');
57
+ if (!Array.isArray(sequence.messages) || sequence.messages.length < 1) {
58
+ problems.push('Sequence diagrams need at least one message.');
59
+ }
60
+ if (sequence.cards !== undefined && !Array.isArray(sequence.cards)) problems.push('Sequence "cards" must be an array.');
61
+ for (const arr of ['segments', 'activations']) {
62
+ if (sequence[arr] !== undefined && !Array.isArray(sequence[arr])) problems.push(`Sequence "${arr}" must be an array.`);
63
+ }
64
+
65
+ if (layout.lifelineBottom - layout.lifelineTop < 120) {
66
+ problems.push(`viewBox height ${viewBox[1]} leaves under 120px of timeline — set meta.viewBox[1] to at least ${layout.lifelineTop + 120 + 65}.`);
67
+ }
68
+
69
+ for (const participant of participants.values()) {
70
+ const estLabelW = textUnits(participant.label) * 6.8;
71
+ if (estLabelW > layout.participantW + 6) {
72
+ problems.push(`Label "${participant.label}" (~${Math.round(estLabelW)}px) is wider than the ${layout.participantW}px participant box — shorten it or move detail to sublabel.`);
73
+ }
74
+ }
75
+
76
+ for (const message of asArray(sequence.messages)) {
77
+ if (!participants.has(message.from)) problems.push(`Message "${message.label}" references unknown source "${message.from}".`);
78
+ if (!participants.has(message.to)) problems.push(`Message "${message.label}" references unknown target "${message.to}".`);
79
+ if (typeof message.y !== 'number') problems.push(`Message "${message.label}" must provide a numeric y.`);
80
+ if (message.y < layout.lifelineTop + 18 || message.y > layout.lifelineBottom - 18) {
81
+ problems.push(`Message "${message.label}" sits outside the readable timeline — keep y between ${layout.lifelineTop + 18} and ${layout.lifelineBottom - 18}.`);
82
+ }
83
+ if (participants.has(message.from) && participants.has(message.to)) {
84
+ const distance = Math.abs(participants.get(message.to).cx - participants.get(message.from).cx);
85
+ if (distance < 60) problems.push(`Message "${message.label}" spans ${Math.round(distance)}px (minimum 60px) — give its participants more column distance.`);
86
+ }
87
+ }
88
+
89
+ // Vertical crowding only matters when the arrows share horizontal space;
90
+ // disjoint arrows may legitimately run in parallel rows.
91
+ const placed = asArray(sequence.messages)
92
+ .filter((m) => participants.has(m.from) && participants.has(m.to))
93
+ .map((m) => ({
94
+ label: m.label,
95
+ y: m.y,
96
+ x1: Math.min(participants.get(m.from).cx, participants.get(m.to).cx),
97
+ x2: Math.max(participants.get(m.from).cx, participants.get(m.to).cx)
98
+ }))
99
+ .sort((a, b) => a.y - b.y);
100
+ for (let i = 0; i < placed.length; i += 1) {
101
+ for (let j = i + 1; j < placed.length && placed[j].y - placed[i].y < 28; j += 1) {
102
+ if (placed[i].x1 < placed[j].x2 && placed[j].x1 < placed[i].x2) {
103
+ problems.push(`Messages "${placed[i].label}" and "${placed[j].label}" are less than 28px apart and share horizontal space — spread their y values.`);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Label masks can extend well past the arrow span, so check the actual
109
+ // label rectangles too — tangent arrows with long labels still collide.
110
+ const labelRects = asArray(sequence.messages)
111
+ .filter((m) => participants.has(m.from) && participants.has(m.to) && typeof m.y === 'number')
112
+ .map((m) => {
113
+ const x1 = participants.get(m.from).cx;
114
+ const x2 = participants.get(m.to).cx;
115
+ const width = Math.max(34, textUnits(m.label) * 5.2 + 12);
116
+ return { label: m.label, x: (x1 + x2) / 2 - width / 2, y: m.y - 20, width, height: layout.labelH };
117
+ });
118
+ for (let i = 0; i < labelRects.length; i += 1) {
119
+ for (let j = i + 1; j < labelRects.length; j += 1) {
120
+ if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
121
+ problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — spread their message y values or shorten the labels.`);
122
+ }
123
+ }
124
+ }
125
+
126
+ for (const segment of asArray(sequence.segments)) {
127
+ if (segment.to <= segment.from) {
128
+ problems.push(`Segment "${segment.label}" has invalid y range (from ${segment.from} to ${segment.to}) — "to" must be greater than "from".`);
129
+ }
130
+ if (segment.from < layout.topY || segment.to > layout.lifelineBottom + 20) {
131
+ problems.push(`Segment "${segment.label}" extends outside the canvas — keep its y range between ${layout.topY} and ${layout.lifelineBottom + 20}.`);
132
+ }
133
+ }
134
+
135
+ for (const activation of asArray(sequence.activations)) {
136
+ if (!participants.has(activation.participant)) problems.push(`Activation references unknown participant "${activation.participant}".`);
137
+ if (activation.to <= activation.from) problems.push(`Activation for "${activation.participant}" has invalid time range — "to" must be greater than "from".`);
138
+ }
139
+
140
+ const lastParticipant = asArray(sequence.participants)[asArray(sequence.participants).length - 1];
141
+ if (lastParticipant && participants.get(lastParticipant.id).cx + layout.participantW / 2 > viewBox[0] - 40) {
142
+ const requiredWidth = Math.ceil(participants.get(lastParticipant.id).cx + layout.participantW / 2 + 40);
143
+ problems.push(`Participants exceed viewBox width — set meta.viewBox[0] to at least ${requiredWidth} or remove a participant.`);
144
+ }
145
+
146
+ if (problems.length) {
147
+ throw new Error(`Sequence layout validation failed:\n- ${problems.join('\n- ')}`);
148
+ }
149
+ }
150
+
151
+ function renderParticipant(participant) {
152
+ const fill = componentFill[participant.type] || 'c-external';
153
+ return ` <rect x="${participant.x}" y="${layout.topY}" width="${layout.participantW}" height="${layout.participantH}" rx="6" class="c-mask"/>
154
+ <rect x="${participant.x}" y="${layout.topY}" width="${layout.participantW}" height="${layout.participantH}" rx="6" class="${fill}"${animateAttr(sequence.meta, 'node', participant.index)} stroke-width="1.5"/>
155
+ <text x="${participant.cx}" y="${layout.topY + 22}" class="t-primary" font-size="11" font-weight="600" text-anchor="middle">${esc(participant.label)}</text>
156
+ <text x="${participant.cx}" y="${layout.topY + 39}" class="t-muted" font-size="7" text-anchor="middle">${esc(participant.sublabel)}</text>`;
157
+ }
158
+
159
+ function renderLifeline(participant) {
160
+ return ` <path d="M ${participant.cx} ${layout.lifelineTop} L ${participant.cx} ${layout.lifelineBottom}" class="a-default" stroke-width="0.8" stroke-dasharray="3,7"/>`;
161
+ }
162
+
163
+ function renderSegment(segment) {
164
+ return ` <rect x="48" y="${segment.from}" width="${viewBox[0] - 96}" height="${segment.to - segment.from}" rx="10" class="c-lane" stroke-width="1"/>
165
+ <text x="62" y="${segment.from + 18}" class="t-dim" font-size="9" font-weight="600">${esc(segment.label)}</text>`;
166
+ }
167
+
168
+ function renderActivation(activation) {
169
+ const participant = participants.get(activation.participant);
170
+ const fill = componentFill[activation.type] || componentFill[participant.type] || 'c-external';
171
+ const x = participant.cx - 5;
172
+ const height = activation.to - activation.from;
173
+ return ` <rect x="${x}" y="${activation.from}" width="10" height="${height}" rx="3" class="c-mask"/>
174
+ <rect x="${x}" y="${activation.from}" width="10" height="${height}" rx="3" class="${fill}" stroke-width="1"/>`;
175
+ }
176
+
177
+ function messageLabel(message, x1, x2) {
178
+ const center = (x1 + x2) / 2;
179
+ const y = message.y - 10;
180
+ const labelW = Math.max(34, textUnits(message.label) * 5.2 + 12);
181
+ const accent = message.variant === 'security'
182
+ ? 't-security'
183
+ : message.variant === 'dashed'
184
+ ? 't-messagebus'
185
+ : message.variant === 'return'
186
+ ? 't-muted'
187
+ : 't-backend';
188
+ return ` <rect x="${center - labelW / 2}" y="${y - 10}" width="${labelW}" height="${layout.labelH}" rx="3" class="c-mask"/>
189
+ <text x="${center}" y="${y}" class="${accent}" font-size="9" text-anchor="middle">${esc(message.label)}</text>`;
190
+ }
191
+
192
+ function renderMessage(message, index) {
193
+ const from = participants.get(message.from);
194
+ const to = participants.get(message.to);
195
+ const direction = to.cx > from.cx ? 1 : -1;
196
+ const start = from.cx + direction * 7;
197
+ const end = to.cx - direction * 7;
198
+ const [cls, marker] = arrowClass[message.variant || 'default'] || arrowClass.default;
199
+ const strokeWidth = message.variant === 'emphasis' ? 1.8 : 1.4;
200
+ const dash = message.variant === 'return' ? ' stroke-dasharray="3,5"' : '';
201
+ const note = message.note
202
+ ? `\n <text x="${Math.min(start, end) + 12}" y="${message.y + 18}" class="t-dim" font-size="7">${esc(message.note)}</text>`
203
+ : '';
204
+ return ` <path d="M ${start} ${message.y} L ${end} ${message.y}" class="${cls}"${animateAttr(sequence.meta, 'edge', index)} stroke-width="${strokeWidth}"${dash} marker-end="url(#${marker})"/>
205
+ ${messageLabel(message, start, end)}${note}`;
206
+ }
207
+
208
+ function renderLegend() {
209
+ const y = layout.legendY;
210
+ return ` <text x="150" y="${y - 20}" class="t-primary" font-size="10" font-weight="600">Legend</text>
211
+ <path d="M 150 ${y} L 184 ${y}" class="a-emphasis" stroke-width="1.8" marker-end="url(#arrowhead-emphasis)"/>
212
+ <text x="193" y="${y + 3}" class="t-muted" font-size="8">request</text>
213
+ <path d="M 270 ${y} L 304 ${y}" class="a-default" stroke-width="1.4" stroke-dasharray="3,5" marker-end="url(#arrowhead)"/>
214
+ <text x="313" y="${y + 3}" class="t-muted" font-size="8">return</text>
215
+ <path d="M 385 ${y} L 419 ${y}" class="a-security" stroke-width="1.4" marker-end="url(#arrowhead-security)"/>
216
+ <text x="428" y="${y + 3}" class="t-muted" font-size="8">security</text>
217
+ <path d="M 530 ${y} L 564 ${y}" class="a-dashed" stroke-width="1.4" marker-end="url(#arrowhead-dashed)"/>
218
+ <text x="573" y="${y + 3}" class="t-muted" font-size="8">async trace</text>`;
219
+ }
220
+
221
+ function renderSvg() {
222
+ const participantList = [...participants.values()];
223
+ return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(sequence.meta, 'sequence diagram')}>
224
+ ${renderDefinitions()}
225
+
226
+ <!-- Background Grid -->
227
+ <rect width="100%" height="100%" fill="url(#grid)" />
228
+
229
+ <!-- Time Segments -->
230
+ ${asArray(sequence.segments).map(renderSegment).join('\n\n')}
231
+
232
+ <!-- Lifelines -->
233
+ ${participantList.map(renderLifeline).join('\n')}
234
+
235
+ <!-- Messages -->
236
+ ${asArray(sequence.messages).map(renderMessage).join('\n\n')}
237
+
238
+ <!-- Activations -->
239
+ ${asArray(sequence.activations).map(renderActivation).join('\n')}
240
+
241
+ <!-- Participants -->
242
+ ${participantList.map(renderParticipant).join('\n\n')}
243
+
244
+ <!-- Legend -->
245
+ ${renderLegend()}
246
+ </svg>`;
247
+ }
248
+
249
+ validateSequence();
250
+ writeDiagram({
251
+ outPath,
252
+ template,
253
+ meta: sequence.meta,
254
+ footerLabel: 'Sequence diagram',
255
+ svg: renderSvg(),
256
+ cards: sequence.cards,
257
+ });
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { applyTemplate, renderCards, esc } from './utils.mjs';
4
+ import { validateSchema } from './validator.mjs';
5
+
6
+ // Common CLI head: node render-<type>.mjs [input.json] [output.html]
7
+ export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = process.argv }) {
8
+ const skillRoot = path.resolve(rendererDir, '../..');
9
+ const inputPath = path.resolve(argv[2] || path.join(skillRoot, 'examples', defaultExample));
10
+ const diagram = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
11
+ validateSchema(diagramType, diagram);
12
+ const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
13
+ // Optional chaining: in degraded mode (no ajv) malformed input must still
14
+ // reach the renderer's friendly layout checks instead of crashing here.
15
+ const outPath = path.resolve(process.cwd(), argv[3] || diagram.meta?.output || `${diagramType}.html`);
16
+ return { diagram, template, outPath };
17
+ }
18
+
19
+ // Common CLI tail: fill the template and write the standalone HTML file.
20
+ // The keyboard hint is screen-only — it means nothing on paper.
21
+ export function writeDiagram({ outPath, template, meta, footerLabel, svg, cards }) {
22
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
23
+ fs.writeFileSync(outPath, applyTemplate(template, {
24
+ title: meta.title,
25
+ subtitle: meta.subtitle,
26
+ footer: `${footerLabel} &bull; Built with Archify<span class="no-print"> &bull; Press <kbd>T</kbd> for theme and <kbd>E</kbd> for export</span>`,
27
+ svg,
28
+ cards: renderCards(cards),
29
+ }));
30
+ console.log(outPath);
31
+ }
32
+
33
+ // Accessible name for the generated diagram SVG.
34
+ export function svgRootAttrs(meta, kind) {
35
+ const name = meta.subtitle ? `${meta.title} — ${meta.subtitle}` : meta.title;
36
+ const animation = meta.animation === 'trace' ? ' data-animation="trace"' : '';
37
+ return `role="img" aria-label="${esc(`${name} (${kind})`)}"${animation}`;
38
+ }
39
+
40
+ export function animateAttr(meta, kind, step) {
41
+ if (meta.animation !== 'trace') return '';
42
+ const safeStep = Number.isFinite(step) && step >= 0 ? Math.floor(step) : 0;
43
+ return ` data-animate="${kind}" style="--step:${safeStep}"`;
44
+ }