@laitszkin/apollo-toolkit 3.10.0 → 3.11.1

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 (47) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  3. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  4. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  5. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  6. package/generate-spec/SKILL.md +17 -15
  7. package/generate-spec/agents/openai.yaml +1 -1
  8. package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
  9. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  10. package/init-project-html/SKILL.md +117 -125
  11. package/init-project-html/agents/openai.yaml +18 -9
  12. package/init-project-html/lib/atlas/assets/architecture.css +161 -0
  13. package/init-project-html/lib/atlas/assets/viewer.client.js +136 -0
  14. package/init-project-html/lib/atlas/cli.js +1023 -0
  15. package/init-project-html/lib/atlas/layout.js +330 -0
  16. package/init-project-html/lib/atlas/render.js +583 -0
  17. package/init-project-html/lib/atlas/schema.js +347 -0
  18. package/init-project-html/lib/atlas/state.js +402 -0
  19. package/init-project-html/references/TEMPLATE_SPEC.md +140 -83
  20. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +160 -1058
  21. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +136 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +172 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
  26. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +64 -163
  27. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +102 -196
  28. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +82 -163
  29. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +88 -150
  30. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +83 -138
  31. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
  32. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +84 -159
  33. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +81 -143
  34. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +98 -188
  35. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +83 -138
  36. package/init-project-html/sample-demo/resources/project-architecture/index.html +256 -335
  37. package/init-project-html/scripts/architecture.js +65 -247
  38. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  39. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  40. package/package.json +6 -2
  41. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  42. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  43. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  44. package/spec-to-project-html/SKILL.md +74 -67
  45. package/spec-to-project-html/agents/openai.yaml +14 -8
  46. package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -83
  47. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -0,0 +1,330 @@
1
+ 'use strict';
2
+
3
+ // layout.js — wraps elkjs to lay out the macro atlas. Features are
4
+ // nested compound nodes; submodules are leaf nodes inside them.
5
+ // hierarchyHandling=INCLUDE_CHILDREN lets cross-cluster edges route
6
+ // past intermediate nodes. The flatten step rebases every node and
7
+ // edge section into absolute (root-relative) coordinates so render.js
8
+ // can emit SVG without further math.
9
+ //
10
+ // Layout is async because elkjs returns a Promise even in Node.
11
+
12
+ const ELK = require('elkjs');
13
+
14
+ // Default fallback box. The actual width/height for each sub-module
15
+ // is computed per node by measureSubmodule() so the role/description
16
+ // fits without overflowing the rectangle.
17
+ const SUB_WIDTH = 240;
18
+ const SUB_HEIGHT = 92;
19
+
20
+ // Box-sizing knobs (intrinsic SVG coordinates).
21
+ const SUB_WIDTH_MIN = 220;
22
+ const SUB_WIDTH_MAX = 360;
23
+ const SUB_HEIGHT_MIN = 92;
24
+ const SUB_HEIGHT_MAX = 220;
25
+ const SUB_SIDE_PAD = 16;
26
+ const SUB_TOP_PAD = 14;
27
+ const SUB_BOTTOM_PAD = 14;
28
+ const TITLE_LINE = 22; // slug line
29
+ const KIND_LINE = 16; // kind chip line
30
+ const ROLE_LINE = 16; // each role line
31
+ const KIND_GAP = 4;
32
+ const ROLE_GAP = 8;
33
+ const MAX_ROLE_LINES = 4;
34
+
35
+ const CLUSTER_PAD_TOP = 60;
36
+ const CLUSTER_PAD_SIDE = 24;
37
+ const CLUSTER_PAD_BOTTOM = 28;
38
+ const EDGE_LABEL_HEIGHT = 18;
39
+
40
+ const KIND_LABELS = {
41
+ ui: 'UI',
42
+ api: 'API',
43
+ service: 'service',
44
+ db: 'database',
45
+ 'pure-fn': 'pure function',
46
+ queue: 'queue',
47
+ external: 'external',
48
+ };
49
+
50
+ function estimateLabelWidth(text) {
51
+ if (!text) return 0;
52
+ return Math.min(220, Math.max(40, String(text).length * 7 + 16));
53
+ }
54
+
55
+ // Approximate render width of a string in the target font. The 0.6
56
+ // factor is a deliberate over-estimate for our sans-serif stack so
57
+ // the chosen width almost always leaves a little breathing room.
58
+ function approxTextWidth(text, fontPx) {
59
+ return String(text || '').length * fontPx * 0.6;
60
+ }
61
+
62
+ function wrapToLines(text, maxChars) {
63
+ if (!text) return [];
64
+ const words = String(text).split(/\s+/).filter(Boolean);
65
+ const lines = [];
66
+ let current = '';
67
+ for (const word of words) {
68
+ if (!current) { current = word; continue; }
69
+ if ((current.length + 1 + word.length) <= maxChars) current = `${current} ${word}`;
70
+ else { lines.push(current); current = word; }
71
+ }
72
+ if (current) lines.push(current);
73
+ return lines;
74
+ }
75
+
76
+ // measureSubmodule picks a width + height that fit the slug, the kind
77
+ // chip, and the wrapped role text. Both layout.js (when telling elkjs
78
+ // how much room each node needs) and render.js (when actually drawing
79
+ // the text inside the box) call it so the rendered text never spills
80
+ // outside the rectangle the layout engine reserved.
81
+ function measureSubmodule(sub) {
82
+ const slug = (sub && sub.slug) || '';
83
+ const kindLabel = KIND_LABELS[sub && sub.kind] || (sub && sub.kind) || 'service';
84
+ const role = (sub && sub.role) || '';
85
+
86
+ const slugW = approxTextWidth(slug, 14);
87
+ const kindW = approxTextWidth(kindLabel, 11);
88
+ const baseInner = Math.max(slugW, kindW);
89
+
90
+ // Aim to keep the role within ~3 lines: choose a width whose text
91
+ // area can hold ceil(roleLen / 3) characters, then clamp.
92
+ const roleLen = role.length;
93
+ let chosenInner;
94
+ if (!role) {
95
+ chosenInner = baseInner;
96
+ } else {
97
+ const targetCharsPerLine = Math.max(20, Math.ceil(roleLen / 3));
98
+ chosenInner = Math.max(baseInner, targetCharsPerLine * 11 * 0.55);
99
+ }
100
+ const width = Math.max(SUB_WIDTH_MIN, Math.min(SUB_WIDTH_MAX, Math.ceil(chosenInner + SUB_SIDE_PAD * 2)));
101
+
102
+ // With the chosen width fixed, wrap the role for real and count lines.
103
+ const innerW = width - SUB_SIDE_PAD * 2;
104
+ const charsPerLine = Math.max(12, Math.floor(innerW / (11 * 0.55)));
105
+ let roleLines = role ? wrapToLines(role, charsPerLine) : [];
106
+ if (roleLines.length > MAX_ROLE_LINES) {
107
+ roleLines = roleLines.slice(0, MAX_ROLE_LINES);
108
+ const last = roleLines[MAX_ROLE_LINES - 1];
109
+ roleLines[MAX_ROLE_LINES - 1] = last.length > 3 ? `${last.slice(0, -1)}…` : `${last}…`;
110
+ }
111
+
112
+ const roleBlock = roleLines.length > 0 ? ROLE_GAP + roleLines.length * ROLE_LINE : 0;
113
+ const intrinsicH = SUB_TOP_PAD + TITLE_LINE + KIND_GAP + KIND_LINE + roleBlock + SUB_BOTTOM_PAD;
114
+ const height = Math.max(SUB_HEIGHT_MIN, Math.min(SUB_HEIGHT_MAX, Math.ceil(intrinsicH)));
115
+
116
+ return { width, height, roleLines, kindLabel };
117
+ }
118
+
119
+ function endpointId(endpoint, ownerFeature) {
120
+ if (typeof endpoint === 'string') {
121
+ return `submodule::${ownerFeature}::${endpoint}`;
122
+ }
123
+ if (endpoint && endpoint.submodule) {
124
+ return `submodule::${endpoint.feature}::${endpoint.submodule}`;
125
+ }
126
+ if (endpoint && endpoint.feature) {
127
+ return `feature::${endpoint.feature}`;
128
+ }
129
+ return null;
130
+ }
131
+
132
+ function buildGraph(state) {
133
+ const children = (state.features || []).map((feature) => ({
134
+ id: `feature::${feature.slug}`,
135
+ labels: [{
136
+ id: `feature::${feature.slug}::label`,
137
+ text: feature.title || feature.slug,
138
+ width: estimateLabelWidth(feature.title || feature.slug),
139
+ height: 24,
140
+ }],
141
+ layoutOptions: {
142
+ 'elk.padding': `[top=${CLUSTER_PAD_TOP},left=${CLUSTER_PAD_SIDE},bottom=${CLUSTER_PAD_BOTTOM},right=${CLUSTER_PAD_SIDE}]`,
143
+ 'elk.spacing.nodeNode': '24',
144
+ 'elk.algorithm': 'layered',
145
+ 'elk.direction': 'DOWN',
146
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '36',
147
+ 'elk.nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
148
+ },
149
+ children: (feature.submodules || []).map((sub) => {
150
+ const box = measureSubmodule(sub);
151
+ return {
152
+ id: `submodule::${feature.slug}::${sub.slug}`,
153
+ width: box.width,
154
+ height: box.height,
155
+ labels: [{
156
+ id: `submodule::${feature.slug}::${sub.slug}::label`,
157
+ text: sub.slug,
158
+ width: estimateLabelWidth(sub.slug),
159
+ height: 18,
160
+ }],
161
+ };
162
+ }),
163
+ }));
164
+
165
+ let nextEdgeId = 0;
166
+ const rootEdges = [];
167
+ const nestedEdges = new Map(); // feature slug → edges[]
168
+
169
+ function pushEdge(list, raw, sourceId, targetId) {
170
+ if (!sourceId || !targetId) return;
171
+ list.push({
172
+ id: raw.id || `e-${nextEdgeId++}`,
173
+ sources: [sourceId],
174
+ targets: [targetId],
175
+ labels: raw.label ? [{
176
+ id: `${raw.id || `e-${nextEdgeId}`}::label`,
177
+ text: raw.label,
178
+ width: estimateLabelWidth(raw.label),
179
+ height: EDGE_LABEL_HEIGHT,
180
+ }] : [],
181
+ });
182
+ }
183
+
184
+ for (const feature of state.features || []) {
185
+ const list = [];
186
+ for (const edge of feature.edges || []) {
187
+ pushEdge(list, edge, endpointId(edge.from, feature.slug), endpointId(edge.to, feature.slug));
188
+ }
189
+ if (list.length > 0) nestedEdges.set(feature.slug, list);
190
+ }
191
+ for (const edge of state.edges || []) {
192
+ pushEdge(rootEdges, edge, endpointId(edge.from), endpointId(edge.to));
193
+ }
194
+
195
+ for (const child of children) {
196
+ const slug = child.id.replace(/^feature::/, '');
197
+ if (nestedEdges.has(slug)) child.edges = nestedEdges.get(slug);
198
+ }
199
+
200
+ return {
201
+ id: 'root',
202
+ layoutOptions: {
203
+ 'elk.algorithm': 'layered',
204
+ 'elk.direction': 'RIGHT',
205
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
206
+ 'elk.spacing.nodeNode': '60',
207
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '100',
208
+ 'elk.padding': '[top=40,left=40,bottom=40,right=40]',
209
+ 'elk.edgeRouting': 'ORTHOGONAL',
210
+ 'elk.edgeLabels.inline': 'false',
211
+ 'elk.edgeLabels.placement': 'CENTER',
212
+ },
213
+ children,
214
+ edges: rootEdges,
215
+ };
216
+ }
217
+
218
+ function collectAbsolute(node, offsetX, offsetY, acc) {
219
+ // node may be root, a cluster, or a leaf.
220
+ const absX = offsetX + (node.x || 0);
221
+ const absY = offsetY + (node.y || 0);
222
+
223
+ if (node.id && node.id.startsWith('feature::')) {
224
+ acc.features.push({
225
+ id: node.id,
226
+ slug: node.id.replace(/^feature::/, ''),
227
+ x: absX,
228
+ y: absY,
229
+ width: node.width || 0,
230
+ height: node.height || 0,
231
+ labels: (node.labels || []).map((l) => ({
232
+ text: l.text,
233
+ x: absX + (l.x || 0),
234
+ y: absY + (l.y || 0),
235
+ width: l.width || 0,
236
+ height: l.height || 0,
237
+ })),
238
+ });
239
+ } else if (node.id && node.id.startsWith('submodule::')) {
240
+ const parts = node.id.split('::');
241
+ acc.submodules.push({
242
+ id: node.id,
243
+ featureSlug: parts[1],
244
+ slug: parts[2],
245
+ x: absX,
246
+ y: absY,
247
+ width: node.width || SUB_WIDTH,
248
+ height: node.height || SUB_HEIGHT,
249
+ labels: (node.labels || []).map((l) => ({
250
+ text: l.text,
251
+ x: absX + (l.x || 0),
252
+ y: absY + (l.y || 0),
253
+ width: l.width || 0,
254
+ height: l.height || 0,
255
+ })),
256
+ });
257
+ }
258
+
259
+ for (const edge of node.edges || []) {
260
+ const sections = (edge.sections || []).map((section) => ({
261
+ startPoint: { x: section.startPoint.x + absX, y: section.startPoint.y + absY },
262
+ endPoint: { x: section.endPoint.x + absX, y: section.endPoint.y + absY },
263
+ bendPoints: (section.bendPoints || []).map((p) => ({ x: p.x + absX, y: p.y + absY })),
264
+ }));
265
+ const labels = (edge.labels || []).map((label) => ({
266
+ text: label.text,
267
+ x: absX + (label.x || 0),
268
+ y: absY + (label.y || 0),
269
+ width: label.width || 0,
270
+ height: label.height || 0,
271
+ }));
272
+ acc.edges.push({ id: edge.id, sections, labels });
273
+ }
274
+
275
+ for (const child of node.children || []) {
276
+ collectAbsolute(child, absX, absY, acc);
277
+ }
278
+ }
279
+
280
+ function assertNoOverlap(layout) {
281
+ const boxes = [];
282
+ for (const sub of layout.submodules) {
283
+ boxes.push({ id: sub.id, x: sub.x, y: sub.y, w: sub.width, h: sub.height });
284
+ }
285
+ for (let i = 0; i < boxes.length; i += 1) {
286
+ for (let j = i + 1; j < boxes.length; j += 1) {
287
+ const a = boxes[i];
288
+ const b = boxes[j];
289
+ const overlapX = a.x < b.x + b.w && b.x < a.x + a.w;
290
+ const overlapY = a.y < b.y + b.h && b.y < a.y + a.h;
291
+ if (overlapX && overlapY) {
292
+ throw new Error(`atlas layout: submodule rectangles overlap: ${a.id} vs ${b.id}`);
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ async function layoutMacro(state) {
299
+ if (!state.features || state.features.length === 0) {
300
+ return { width: 320, height: 160, features: [], submodules: [], edges: [], empty: true };
301
+ }
302
+ const elk = new ELK();
303
+ const graph = buildGraph(state);
304
+ const laidOut = await elk.layout(graph);
305
+ const acc = { features: [], submodules: [], edges: [] };
306
+ collectAbsolute(laidOut, 0, 0, acc);
307
+ const layout = {
308
+ width: laidOut.width || 0,
309
+ height: laidOut.height || 0,
310
+ features: acc.features,
311
+ submodules: acc.submodules,
312
+ edges: acc.edges,
313
+ empty: false,
314
+ };
315
+ assertNoOverlap(layout);
316
+ return layout;
317
+ }
318
+
319
+ module.exports = {
320
+ SUB_WIDTH,
321
+ SUB_HEIGHT,
322
+ SUB_WIDTH_MIN,
323
+ SUB_WIDTH_MAX,
324
+ SUB_HEIGHT_MIN,
325
+ SUB_HEIGHT_MAX,
326
+ layoutMacro,
327
+ assertNoOverlap,
328
+ buildGraph,
329
+ measureSubmodule,
330
+ };