@laitszkin/apollo-toolkit 3.10.0 → 3.11.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 (47) hide show
  1. package/CHANGELOG.md +20 -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 +82 -126
  11. package/init-project-html/agents/openai.yaml +17 -8
  12. package/init-project-html/lib/atlas/assets/architecture.css +140 -0
  13. package/init-project-html/lib/atlas/assets/viewer.client.js +93 -0
  14. package/init-project-html/lib/atlas/cli.js +995 -0
  15. package/init-project-html/lib/atlas/layout.js +229 -0
  16. package/init-project-html/lib/atlas/render.js +485 -0
  17. package/init-project-html/lib/atlas/schema.js +310 -0
  18. package/init-project-html/lib/atlas/state.js +402 -0
  19. package/init-project-html/references/TEMPLATE_SPEC.md +123 -84
  20. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +139 -1058
  21. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +93 -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 +159 -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 +48 -163
  27. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +70 -196
  28. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +64 -163
  29. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +68 -150
  30. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +65 -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 +66 -159
  33. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +63 -143
  34. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +77 -188
  35. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +65 -138
  36. package/init-project-html/sample-demo/resources/project-architecture/index.html +232 -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 +61 -63
  45. package/spec-to-project-html/agents/openai.yaml +14 -8
  46. package/spec-to-project-html/references/TEMPLATE_SPEC.md +96 -83
  47. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -0,0 +1,310 @@
1
+ 'use strict';
2
+
3
+ // schema.js — single source of truth for atlas component shapes,
4
+ // enum vocabularies, and validation. The CLI, render layer, and tests
5
+ // all consult this file so DOM/CSS hooks stay aligned with the
6
+ // declarative state.
7
+
8
+ const SUBMODULE_KINDS = Object.freeze([
9
+ 'ui',
10
+ 'api',
11
+ 'service',
12
+ 'db',
13
+ 'pure-fn',
14
+ 'queue',
15
+ 'external',
16
+ ]);
17
+
18
+ const SIDE_EFFECTS = Object.freeze([
19
+ 'pure',
20
+ 'io',
21
+ 'write',
22
+ 'tx',
23
+ 'lock',
24
+ 'network',
25
+ ]);
26
+
27
+ const VARIABLE_SCOPES = Object.freeze([
28
+ 'call',
29
+ 'tx',
30
+ 'persist',
31
+ 'instance',
32
+ 'loop',
33
+ ]);
34
+
35
+ const EDGE_KINDS = Object.freeze([
36
+ 'call',
37
+ 'return',
38
+ 'data-row',
39
+ 'failure',
40
+ ]);
41
+
42
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
43
+
44
+ function isSlug(value) {
45
+ return typeof value === 'string' && SLUG_PATTERN.test(value);
46
+ }
47
+
48
+ function isNonEmptyString(value) {
49
+ return typeof value === 'string' && value.trim().length > 0;
50
+ }
51
+
52
+ function requireField(errors, where, name, value, predicate, hint) {
53
+ if (!predicate(value)) {
54
+ errors.push(`${where}: invalid or missing "${name}"${hint ? ` (${hint})` : ''}`);
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+
60
+ function validateMeta(meta, errors) {
61
+ if (!meta || typeof meta !== 'object') {
62
+ errors.push('meta: missing object');
63
+ return;
64
+ }
65
+ if (meta.title !== undefined) requireField(errors, 'meta', 'title', meta.title, isNonEmptyString);
66
+ if (meta.summary !== undefined && typeof meta.summary !== 'string') {
67
+ errors.push('meta: "summary" must be a string when present');
68
+ }
69
+ }
70
+
71
+ function validateActor(actor, errors, idx) {
72
+ const where = `actors[${idx}]`;
73
+ requireField(errors, where, 'id', actor && actor.id, isSlug, 'kebab-case slug');
74
+ requireField(errors, where, 'label', actor && actor.label, isNonEmptyString);
75
+ }
76
+
77
+ function validateFunction(fn, errors, where) {
78
+ requireField(errors, where, 'name', fn && fn.name, isNonEmptyString);
79
+ if (fn && fn.in !== undefined && typeof fn.in !== 'string') errors.push(`${where}: "in" must be a string`);
80
+ if (fn && fn.out !== undefined && typeof fn.out !== 'string') errors.push(`${where}: "out" must be a string`);
81
+ if (fn && fn.side !== undefined && !SIDE_EFFECTS.includes(fn.side)) {
82
+ errors.push(`${where}: "side" must be one of ${SIDE_EFFECTS.join('|')}`);
83
+ }
84
+ if (fn && fn.purpose !== undefined && typeof fn.purpose !== 'string') {
85
+ errors.push(`${where}: "purpose" must be a string`);
86
+ }
87
+ }
88
+
89
+ function validateVariable(v, errors, where) {
90
+ requireField(errors, where, 'name', v && v.name, isNonEmptyString);
91
+ if (v && v.type !== undefined && typeof v.type !== 'string') errors.push(`${where}: "type" must be a string`);
92
+ if (v && v.scope !== undefined && !VARIABLE_SCOPES.includes(v.scope)) {
93
+ errors.push(`${where}: "scope" must be one of ${VARIABLE_SCOPES.join('|')}`);
94
+ }
95
+ if (v && v.purpose !== undefined && typeof v.purpose !== 'string') {
96
+ errors.push(`${where}: "purpose" must be a string`);
97
+ }
98
+ }
99
+
100
+ function validateError(err, errors, where) {
101
+ requireField(errors, where, 'name', err && err.name, isNonEmptyString);
102
+ if (err && err.when !== undefined && typeof err.when !== 'string') errors.push(`${where}: "when" must be a string`);
103
+ if (err && err.means !== undefined && typeof err.means !== 'string') errors.push(`${where}: "means" must be a string`);
104
+ }
105
+
106
+ function validateSubmodule(sub, errors, where) {
107
+ requireField(errors, where, 'slug', sub && sub.slug, isSlug, 'kebab-case slug');
108
+ if (sub && sub.kind !== undefined && !SUBMODULE_KINDS.includes(sub.kind)) {
109
+ errors.push(`${where}: "kind" must be one of ${SUBMODULE_KINDS.join('|')}`);
110
+ }
111
+ if (sub && sub.role !== undefined && typeof sub.role !== 'string') {
112
+ errors.push(`${where}: "role" must be a string`);
113
+ }
114
+
115
+ if (sub && sub.functions) {
116
+ if (!Array.isArray(sub.functions)) {
117
+ errors.push(`${where}: "functions" must be an array`);
118
+ } else {
119
+ sub.functions.forEach((fn, i) => validateFunction(fn, errors, `${where}.functions[${i}]`));
120
+ }
121
+ }
122
+ if (sub && sub.variables) {
123
+ if (!Array.isArray(sub.variables)) {
124
+ errors.push(`${where}: "variables" must be an array`);
125
+ } else {
126
+ sub.variables.forEach((v, i) => validateVariable(v, errors, `${where}.variables[${i}]`));
127
+ }
128
+ }
129
+ if (sub && sub.dataflow) {
130
+ if (!Array.isArray(sub.dataflow)) {
131
+ errors.push(`${where}: "dataflow" must be an array of step strings`);
132
+ } else {
133
+ sub.dataflow.forEach((step, i) => {
134
+ if (typeof step !== 'string') errors.push(`${where}.dataflow[${i}]: must be a string`);
135
+ });
136
+ }
137
+ }
138
+ if (sub && sub.errors) {
139
+ if (!Array.isArray(sub.errors)) {
140
+ errors.push(`${where}: "errors" must be an array`);
141
+ } else {
142
+ sub.errors.forEach((err, i) => validateError(err, errors, `${where}.errors[${i}]`));
143
+ }
144
+ }
145
+ }
146
+
147
+ function validateEdgeEndpoint(endpoint, errors, where, allowSelf = false) {
148
+ if (typeof endpoint === 'string') {
149
+ if (allowSelf) {
150
+ if (!isSlug(endpoint)) errors.push(`${where}: endpoint slug must be kebab-case`);
151
+ return;
152
+ }
153
+ errors.push(`${where}: cross-feature endpoint must be an object {feature, submodule}`);
154
+ return;
155
+ }
156
+ if (!endpoint || typeof endpoint !== 'object') {
157
+ errors.push(`${where}: endpoint missing`);
158
+ return;
159
+ }
160
+ if (!isSlug(endpoint.feature)) errors.push(`${where}: endpoint.feature must be a kebab-case slug`);
161
+ if (endpoint.submodule !== undefined && endpoint.submodule !== null && !isSlug(endpoint.submodule)) {
162
+ errors.push(`${where}: endpoint.submodule must be a kebab-case slug when present`);
163
+ }
164
+ }
165
+
166
+ function validateEdge(edge, errors, where, { allowSelf = false } = {}) {
167
+ if (edge && edge.id !== undefined && !isSlug(edge.id)) {
168
+ errors.push(`${where}: "id" must be a kebab-case slug`);
169
+ }
170
+ if (edge && edge.kind !== undefined && !EDGE_KINDS.includes(edge.kind)) {
171
+ errors.push(`${where}: "kind" must be one of ${EDGE_KINDS.join('|')}`);
172
+ }
173
+ validateEdgeEndpoint(edge && edge.from, errors, `${where}.from`, allowSelf);
174
+ validateEdgeEndpoint(edge && edge.to, errors, `${where}.to`, allowSelf);
175
+ if (edge && edge.label !== undefined && typeof edge.label !== 'string') {
176
+ errors.push(`${where}: "label" must be a string`);
177
+ }
178
+ }
179
+
180
+ function validateFeature(feature, errors, where) {
181
+ requireField(errors, where, 'slug', feature && feature.slug, isSlug, 'kebab-case slug');
182
+ if (feature && feature.title !== undefined) requireField(errors, where, 'title', feature.title, isNonEmptyString);
183
+ if (feature && feature.story !== undefined && typeof feature.story !== 'string') {
184
+ errors.push(`${where}: "story" must be a string`);
185
+ }
186
+ if (feature && feature.dependsOn) {
187
+ if (!Array.isArray(feature.dependsOn)) errors.push(`${where}: "dependsOn" must be a list of feature slugs`);
188
+ else feature.dependsOn.forEach((slug, i) => {
189
+ if (!isSlug(slug)) errors.push(`${where}.dependsOn[${i}]: must be kebab-case slug`);
190
+ });
191
+ }
192
+ if (feature && feature.submodules) {
193
+ if (!Array.isArray(feature.submodules)) errors.push(`${where}: "submodules" must be an array`);
194
+ else {
195
+ const slugs = new Set();
196
+ feature.submodules.forEach((sub, i) => {
197
+ validateSubmodule(sub, errors, `${where}.submodules[${i}]`);
198
+ if (sub && isSlug(sub.slug)) {
199
+ if (slugs.has(sub.slug)) errors.push(`${where}: duplicate submodule slug "${sub.slug}"`);
200
+ slugs.add(sub.slug);
201
+ }
202
+ });
203
+ }
204
+ }
205
+ if (feature && feature.edges) {
206
+ if (!Array.isArray(feature.edges)) errors.push(`${where}: "edges" must be an array`);
207
+ else feature.edges.forEach((edge, i) => validateEdge(edge, errors, `${where}.edges[${i}]`, { allowSelf: true }));
208
+ }
209
+ }
210
+
211
+ // validate(state) checks structural shape, enum membership, and
212
+ // referential integrity (every edge endpoint resolves to a known
213
+ // feature/submodule). Returns an array of error strings; empty = ok.
214
+ function validate(state) {
215
+ const errors = [];
216
+ if (!state || typeof state !== 'object') {
217
+ return ['state: must be an object'];
218
+ }
219
+
220
+ validateMeta(state.meta, errors);
221
+
222
+ if (state.actors) {
223
+ if (!Array.isArray(state.actors)) errors.push('actors: must be an array');
224
+ else state.actors.forEach((actor, i) => validateActor(actor, errors, i));
225
+ }
226
+
227
+ if (!Array.isArray(state.features)) {
228
+ errors.push('features: must be an array');
229
+ } else {
230
+ const featureSlugs = new Set();
231
+ state.features.forEach((feature, i) => {
232
+ validateFeature(feature, errors, `features[${i}]`);
233
+ if (feature && isSlug(feature.slug)) {
234
+ if (featureSlugs.has(feature.slug)) errors.push(`features: duplicate feature slug "${feature.slug}"`);
235
+ featureSlugs.add(feature.slug);
236
+ }
237
+ });
238
+
239
+ // referential integrity for intra-feature edges
240
+ for (const feature of state.features) {
241
+ if (!feature || !Array.isArray(feature.edges)) continue;
242
+ const subSlugs = new Set((feature.submodules || []).map((s) => s && s.slug).filter(Boolean));
243
+ feature.edges.forEach((edge, i) => {
244
+ const where = `features[${feature.slug}].edges[${i}]`;
245
+ for (const [side, ep] of [['from', edge && edge.from], ['to', edge && edge.to]]) {
246
+ if (typeof ep === 'string') {
247
+ if (!subSlugs.has(ep)) errors.push(`${where}.${side}: unknown submodule "${ep}" in feature "${feature.slug}"`);
248
+ } else if (ep && typeof ep === 'object' && ep.feature && ep.feature !== feature.slug) {
249
+ errors.push(`${where}.${side}: intra-feature edge cannot point at another feature "${ep.feature}"`);
250
+ } else if (ep && ep.submodule && !subSlugs.has(ep.submodule)) {
251
+ errors.push(`${where}.${side}: unknown submodule "${ep.submodule}"`);
252
+ }
253
+ }
254
+ });
255
+ }
256
+ }
257
+
258
+ if (state.edges) {
259
+ if (!Array.isArray(state.edges)) errors.push('edges: must be an array');
260
+ else state.edges.forEach((edge, i) => validateEdge(edge, errors, `edges[${i}]`));
261
+ }
262
+
263
+ // referential integrity for cross-feature edges
264
+ if (Array.isArray(state.edges) && Array.isArray(state.features)) {
265
+ const featureMap = new Map();
266
+ for (const feature of state.features) {
267
+ if (!feature || !isSlug(feature.slug)) continue;
268
+ featureMap.set(feature.slug, new Set((feature.submodules || []).map((s) => s && s.slug).filter(Boolean)));
269
+ }
270
+ state.edges.forEach((edge, i) => {
271
+ const where = `edges[${i}]`;
272
+ for (const [side, ep] of [['from', edge && edge.from], ['to', edge && edge.to]]) {
273
+ if (!ep || typeof ep !== 'object') continue;
274
+ if (!featureMap.has(ep.feature)) errors.push(`${where}.${side}: unknown feature "${ep.feature}"`);
275
+ else if (ep.submodule && !featureMap.get(ep.feature).has(ep.submodule)) {
276
+ errors.push(`${where}.${side}: unknown submodule "${ep.submodule}" in feature "${ep.feature}"`);
277
+ }
278
+ }
279
+ });
280
+ }
281
+
282
+ return errors;
283
+ }
284
+
285
+ // emptyState() returns a minimal valid in-memory state. Used by the
286
+ // CLI when no atlas exists yet.
287
+ function emptyState({ title = 'Project architecture' } = {}) {
288
+ return {
289
+ meta: {
290
+ title,
291
+ summary: '',
292
+ updatedAt: null,
293
+ },
294
+ actors: [],
295
+ features: [],
296
+ edges: [],
297
+ };
298
+ }
299
+
300
+ module.exports = {
301
+ SUBMODULE_KINDS,
302
+ SIDE_EFFECTS,
303
+ VARIABLE_SCOPES,
304
+ EDGE_KINDS,
305
+ SLUG_PATTERN,
306
+ isSlug,
307
+ isNonEmptyString,
308
+ validate,
309
+ emptyState,
310
+ };