@forwardimpact/libsyntheticgen 0.1.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.
@@ -0,0 +1,596 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { tokenize } from "../dsl/tokenizer.js";
4
+ import { parse } from "../dsl/parser.js";
5
+
6
+ /**
7
+ * Helper: tokenize then parse a DSL source string.
8
+ * @param {string} source
9
+ * @returns {import('../dsl/parser.js').UniverseAST}
10
+ */
11
+ function parseDsl(source) {
12
+ return parse(tokenize(source));
13
+ }
14
+
15
+ describe("parse", () => {
16
+ describe("universe declaration", () => {
17
+ test("parses minimal universe with name", () => {
18
+ const ast = parseDsl("universe acme {}");
19
+ assert.strictEqual(ast.name, "acme");
20
+ });
21
+
22
+ test("parses universe with quoted name", () => {
23
+ const ast = parseDsl('universe "Acme Corp" {}');
24
+ assert.strictEqual(ast.name, "Acme Corp");
25
+ });
26
+
27
+ test("parses domain", () => {
28
+ const ast = parseDsl('universe test { domain "engineering" }');
29
+ assert.strictEqual(ast.domain, "engineering");
30
+ });
31
+
32
+ test("parses industry", () => {
33
+ const ast = parseDsl('universe test { industry "pharma" }');
34
+ assert.strictEqual(ast.industry, "pharma");
35
+ });
36
+
37
+ test("parses seed", () => {
38
+ const ast = parseDsl("universe test { seed 123 }");
39
+ assert.strictEqual(ast.seed, 123);
40
+ });
41
+
42
+ test("defaults seed to 42", () => {
43
+ const ast = parseDsl("universe test {}");
44
+ assert.strictEqual(ast.seed, 42);
45
+ });
46
+
47
+ test("parses domain, industry, and seed together", () => {
48
+ const ast = parseDsl(`universe test {
49
+ domain "engineering"
50
+ industry "tech"
51
+ seed 99
52
+ }`);
53
+ assert.strictEqual(ast.domain, "engineering");
54
+ assert.strictEqual(ast.industry, "tech");
55
+ assert.strictEqual(ast.seed, 99);
56
+ });
57
+ });
58
+
59
+ describe("org structure", () => {
60
+ test("parses single org", () => {
61
+ const ast = parseDsl(`universe test {
62
+ org acme {
63
+ name "Acme Corp"
64
+ location "New York"
65
+ }
66
+ }`);
67
+ assert.strictEqual(ast.orgs.length, 1);
68
+ assert.strictEqual(ast.orgs[0].id, "acme");
69
+ assert.strictEqual(ast.orgs[0].name, "Acme Corp");
70
+ assert.strictEqual(ast.orgs[0].location, "New York");
71
+ });
72
+
73
+ test("parses multiple orgs", () => {
74
+ const ast = parseDsl(`universe test {
75
+ org alpha { name "Alpha" }
76
+ org beta { name "Beta" }
77
+ }`);
78
+ assert.strictEqual(ast.orgs.length, 2);
79
+ assert.strictEqual(ast.orgs[0].id, "alpha");
80
+ assert.strictEqual(ast.orgs[1].id, "beta");
81
+ });
82
+ });
83
+
84
+ describe("department and team structures", () => {
85
+ test("parses department with name and headcount", () => {
86
+ const ast = parseDsl(`universe test {
87
+ department eng {
88
+ name "Engineering"
89
+ headcount 50
90
+ }
91
+ }`);
92
+ assert.strictEqual(ast.departments.length, 1);
93
+ assert.strictEqual(ast.departments[0].id, "eng");
94
+ assert.strictEqual(ast.departments[0].name, "Engineering");
95
+ assert.strictEqual(ast.departments[0].headcount, 50);
96
+ });
97
+
98
+ test("parses department with parent", () => {
99
+ const ast = parseDsl(`universe test {
100
+ department sub_eng {
101
+ name "Sub Engineering"
102
+ parent eng
103
+ }
104
+ }`);
105
+ assert.strictEqual(ast.departments[0].parent, "eng");
106
+ });
107
+
108
+ test("parses teams within departments", () => {
109
+ const ast = parseDsl(`universe test {
110
+ department eng {
111
+ name "Engineering"
112
+ team frontend {
113
+ name "Frontend Team"
114
+ size 5
115
+ manager @apollo
116
+ repos ["ui-repo", "design-system"]
117
+ }
118
+ }
119
+ }`);
120
+ assert.strictEqual(ast.teams.length, 1);
121
+ assert.strictEqual(ast.teams[0].id, "frontend");
122
+ assert.strictEqual(ast.teams[0].department, "eng");
123
+ assert.strictEqual(ast.teams[0].name, "Frontend Team");
124
+ assert.strictEqual(ast.teams[0].size, 5);
125
+ assert.strictEqual(ast.teams[0].manager, "apollo");
126
+ assert.deepStrictEqual(ast.teams[0].repos, ["ui-repo", "design-system"]);
127
+ });
128
+
129
+ test("parses multiple teams in a department", () => {
130
+ const ast = parseDsl(`universe test {
131
+ department eng {
132
+ name "Engineering"
133
+ team alpha { name "Alpha" size 3 }
134
+ team beta { name "Beta" size 4 }
135
+ }
136
+ }`);
137
+ assert.strictEqual(ast.teams.length, 2);
138
+ assert.strictEqual(ast.teams[0].id, "alpha");
139
+ assert.strictEqual(ast.teams[1].id, "beta");
140
+ });
141
+ });
142
+
143
+ describe("people section", () => {
144
+ test("parses people with count and names", () => {
145
+ const ast = parseDsl(`universe test {
146
+ people {
147
+ count 100
148
+ names "greek"
149
+ }
150
+ }`);
151
+ assert.strictEqual(ast.people.count, 100);
152
+ assert.strictEqual(ast.people.names, "greek");
153
+ });
154
+
155
+ test("parses people with distribution", () => {
156
+ const ast = parseDsl(`universe test {
157
+ people {
158
+ count 50
159
+ names "greek"
160
+ distribution {
161
+ junior 30%
162
+ mid 50%
163
+ senior 20%
164
+ }
165
+ }
166
+ }`);
167
+ assert.deepStrictEqual(ast.people.distribution, {
168
+ junior: 30,
169
+ mid: 50,
170
+ senior: 20,
171
+ });
172
+ });
173
+
174
+ test("parses people with disciplines", () => {
175
+ const ast = parseDsl(`universe test {
176
+ people {
177
+ count 50
178
+ names "greek"
179
+ disciplines {
180
+ backend 40%
181
+ frontend 30%
182
+ devops 30%
183
+ }
184
+ }
185
+ }`);
186
+ assert.deepStrictEqual(ast.people.disciplines, {
187
+ backend: 40,
188
+ frontend: 30,
189
+ devops: 30,
190
+ });
191
+ });
192
+ });
193
+
194
+ describe("project section", () => {
195
+ test("parses project with all fields", () => {
196
+ const ast = parseDsl(`universe test {
197
+ project alpha {
198
+ name "Project Alpha"
199
+ type "greenfield"
200
+ phase "build"
201
+ teams ["frontend", "backend"]
202
+ timeline_start 2024-01
203
+ timeline_end 2024-06
204
+ prose_topic "Cloud migration"
205
+ prose_tone "technical"
206
+ }
207
+ }`);
208
+ assert.strictEqual(ast.projects.length, 1);
209
+ const proj = ast.projects[0];
210
+ assert.strictEqual(proj.id, "alpha");
211
+ assert.strictEqual(proj.name, "Project Alpha");
212
+ assert.strictEqual(proj.type, "greenfield");
213
+ assert.strictEqual(proj.phase, "build");
214
+ assert.deepStrictEqual(proj.teams, ["frontend", "backend"]);
215
+ assert.strictEqual(proj.timeline_start, "2024-01");
216
+ assert.strictEqual(proj.timeline_end, "2024-06");
217
+ assert.strictEqual(proj.prose_topic, "Cloud migration");
218
+ assert.strictEqual(proj.prose_tone, "technical");
219
+ });
220
+
221
+ test("parses multiple projects", () => {
222
+ const ast = parseDsl(`universe test {
223
+ project a { name "A" type "greenfield" }
224
+ project b { name "B" type "migration" }
225
+ }`);
226
+ assert.strictEqual(ast.projects.length, 2);
227
+ });
228
+ });
229
+
230
+ describe("framework section", () => {
231
+ test("parses proficiencies and maturities as arrays", () => {
232
+ const ast = parseDsl(`universe test {
233
+ framework {
234
+ proficiencies [awareness, foundational, working, practitioner, expert]
235
+ maturities [emerging, developing, practicing, role_modeling, exemplifying]
236
+ }
237
+ }`);
238
+ assert.deepStrictEqual(ast.framework.proficiencies, [
239
+ "awareness",
240
+ "foundational",
241
+ "working",
242
+ "practitioner",
243
+ "expert",
244
+ ]);
245
+ assert.deepStrictEqual(ast.framework.maturities, [
246
+ "emerging",
247
+ "developing",
248
+ "practicing",
249
+ "role_modeling",
250
+ "exemplifying",
251
+ ]);
252
+ });
253
+
254
+ test("parses stages as array", () => {
255
+ const ast = parseDsl(`universe test {
256
+ framework {
257
+ stages [discovery, delivery, optimization]
258
+ }
259
+ }`);
260
+ assert.deepStrictEqual(ast.framework.stages, [
261
+ "discovery",
262
+ "delivery",
263
+ "optimization",
264
+ ]);
265
+ });
266
+
267
+ test("parses levels with title, rank, experience", () => {
268
+ const ast = parseDsl(`universe test {
269
+ framework {
270
+ levels {
271
+ J040 {
272
+ title "Junior Engineer"
273
+ rank 1
274
+ experience "0-2 years"
275
+ }
276
+ J050 {
277
+ title "Engineer"
278
+ roleTitle "Team Lead"
279
+ rank 2
280
+ experience "2-5 years"
281
+ }
282
+ }
283
+ }
284
+ }`);
285
+ assert.strictEqual(ast.framework.levels.length, 2);
286
+ assert.strictEqual(ast.framework.levels[0].id, "J040");
287
+ assert.strictEqual(
288
+ ast.framework.levels[0].professionalTitle,
289
+ "Junior Engineer",
290
+ );
291
+ assert.strictEqual(ast.framework.levels[0].rank, 1);
292
+ assert.strictEqual(ast.framework.levels[0].experience, "0-2 years");
293
+ assert.strictEqual(ast.framework.levels[1].managementTitle, "Team Lead");
294
+ });
295
+
296
+ test("parses capabilities with skills", () => {
297
+ const ast = parseDsl(`universe test {
298
+ framework {
299
+ capabilities {
300
+ coding {
301
+ name "Coding"
302
+ skills [javascript, python, go]
303
+ }
304
+ }
305
+ }
306
+ }`);
307
+ assert.strictEqual(ast.framework.capabilities.length, 1);
308
+ assert.strictEqual(ast.framework.capabilities[0].id, "coding");
309
+ assert.strictEqual(ast.framework.capabilities[0].name, "Coding");
310
+ assert.deepStrictEqual(ast.framework.capabilities[0].skills, [
311
+ "javascript",
312
+ "python",
313
+ "go",
314
+ ]);
315
+ });
316
+
317
+ test("parses behaviours", () => {
318
+ const ast = parseDsl(`universe test {
319
+ framework {
320
+ behaviours {
321
+ collaboration {
322
+ name "Collaboration"
323
+ }
324
+ leadership {
325
+ name "Leadership"
326
+ }
327
+ }
328
+ }
329
+ }`);
330
+ assert.strictEqual(ast.framework.behaviours.length, 2);
331
+ assert.strictEqual(ast.framework.behaviours[0].id, "collaboration");
332
+ assert.strictEqual(ast.framework.behaviours[0].name, "Collaboration");
333
+ });
334
+
335
+ test("parses disciplines with tiers and tracks", () => {
336
+ const ast = parseDsl(`universe test {
337
+ framework {
338
+ disciplines {
339
+ backend_dev {
340
+ roleTitle "Backend Developer"
341
+ specialization "Backend"
342
+ isProfessional true
343
+ core [api_design, databases]
344
+ supporting [testing]
345
+ broad [observability]
346
+ validTracks [null, platform]
347
+ }
348
+ }
349
+ }
350
+ }`);
351
+ const disc = ast.framework.disciplines[0];
352
+ assert.strictEqual(disc.id, "backend_dev");
353
+ assert.strictEqual(disc.roleTitle, "Backend Developer");
354
+ assert.strictEqual(disc.specialization, "Backend");
355
+ assert.strictEqual(disc.isProfessional, true);
356
+ assert.deepStrictEqual(disc.core, ["api_design", "databases"]);
357
+ assert.deepStrictEqual(disc.supporting, ["testing"]);
358
+ assert.deepStrictEqual(disc.broad, ["observability"]);
359
+ assert.deepStrictEqual(disc.validTracks, [null, "platform"]);
360
+ });
361
+
362
+ test("parses tracks", () => {
363
+ const ast = parseDsl(`universe test {
364
+ framework {
365
+ tracks {
366
+ platform {
367
+ name "Platform"
368
+ }
369
+ }
370
+ }
371
+ }`);
372
+ assert.strictEqual(ast.framework.tracks.length, 1);
373
+ assert.strictEqual(ast.framework.tracks[0].id, "platform");
374
+ assert.strictEqual(ast.framework.tracks[0].name, "Platform");
375
+ });
376
+
377
+ test("parses drivers with skills and behaviours", () => {
378
+ const ast = parseDsl(`universe test {
379
+ framework {
380
+ drivers {
381
+ code_quality {
382
+ name "Code Quality"
383
+ skills [testing, code_review]
384
+ behaviours [collaboration]
385
+ }
386
+ }
387
+ }
388
+ }`);
389
+ const driver = ast.framework.drivers[0];
390
+ assert.strictEqual(driver.id, "code_quality");
391
+ assert.strictEqual(driver.name, "Code Quality");
392
+ assert.deepStrictEqual(driver.skills, ["testing", "code_review"]);
393
+ assert.deepStrictEqual(driver.behaviours, ["collaboration"]);
394
+ });
395
+
396
+ test("initializes empty framework arrays by default", () => {
397
+ const ast = parseDsl("universe test { framework {} }");
398
+ assert.deepStrictEqual(ast.framework.proficiencies, []);
399
+ assert.deepStrictEqual(ast.framework.maturities, []);
400
+ assert.deepStrictEqual(ast.framework.levels, []);
401
+ assert.deepStrictEqual(ast.framework.capabilities, []);
402
+ assert.deepStrictEqual(ast.framework.behaviours, []);
403
+ assert.deepStrictEqual(ast.framework.disciplines, []);
404
+ assert.deepStrictEqual(ast.framework.tracks, []);
405
+ assert.deepStrictEqual(ast.framework.drivers, []);
406
+ assert.deepStrictEqual(ast.framework.stages, []);
407
+ });
408
+ });
409
+
410
+ describe("snapshots section", () => {
411
+ test("parses snapshots with quarterly range and account_id", () => {
412
+ const ast = parseDsl(`universe test {
413
+ snapshots {
414
+ quarterly_from 2024-01
415
+ quarterly_to 2024-12
416
+ account_id "acct-123"
417
+ }
418
+ }`);
419
+ assert.strictEqual(ast.snapshots.quarterly_from, "2024-01");
420
+ assert.strictEqual(ast.snapshots.quarterly_to, "2024-12");
421
+ assert.strictEqual(ast.snapshots.account_id, "acct-123");
422
+ });
423
+
424
+ test("parses comments_per_snapshot", () => {
425
+ const ast = parseDsl(`universe test {
426
+ snapshots {
427
+ quarterly_from 2024-01
428
+ quarterly_to 2024-12
429
+ account_id "acct-123"
430
+ comments_per_snapshot 25
431
+ }
432
+ }`);
433
+ assert.strictEqual(ast.snapshots.comments_per_snapshot, 25);
434
+ });
435
+ });
436
+
437
+ describe("scenario section", () => {
438
+ test("parses scenario with affects", () => {
439
+ const ast = parseDsl(`universe test {
440
+ scenario growth {
441
+ name "Growth Phase"
442
+ timerange_start 2024-01
443
+ timerange_end 2024-06
444
+ affect frontend {
445
+ github_commits "high"
446
+ github_prs "medium"
447
+ evidence_skills [javascript, css]
448
+ evidence_floor "foundational"
449
+ }
450
+ }
451
+ }`);
452
+ assert.strictEqual(ast.scenarios.length, 1);
453
+ assert.strictEqual(ast.scenarios[0].id, "growth");
454
+ assert.strictEqual(ast.scenarios[0].name, "Growth Phase");
455
+ assert.strictEqual(ast.scenarios[0].timerange_start, "2024-01");
456
+ assert.strictEqual(ast.scenarios[0].affects.length, 1);
457
+ assert.strictEqual(ast.scenarios[0].affects[0].team_id, "frontend");
458
+ assert.strictEqual(ast.scenarios[0].affects[0].github_commits, "high");
459
+ assert.deepStrictEqual(ast.scenarios[0].affects[0].evidence_skills, [
460
+ "javascript",
461
+ "css",
462
+ ]);
463
+ });
464
+
465
+ test("parses scenario affect with dx_drivers", () => {
466
+ const ast = parseDsl(`universe test {
467
+ scenario test_scenario {
468
+ name "Test"
469
+ affect team_a {
470
+ dx_drivers {
471
+ code_review {
472
+ trajectory "improving"
473
+ magnitude 5
474
+ }
475
+ }
476
+ }
477
+ }
478
+ }`);
479
+ const affect = ast.scenarios[0].affects[0];
480
+ assert.strictEqual(affect.dx_drivers.length, 1);
481
+ assert.strictEqual(affect.dx_drivers[0].driver_id, "code_review");
482
+ assert.strictEqual(affect.dx_drivers[0].trajectory, "improving");
483
+ assert.strictEqual(affect.dx_drivers[0].magnitude, 5);
484
+ });
485
+ });
486
+
487
+ describe("content section", () => {
488
+ test("parses content block", () => {
489
+ const ast = parseDsl(`universe test {
490
+ content kb {
491
+ articles 10
492
+ article_topics ["testing", "deployment"]
493
+ blogs 5
494
+ faqs 20
495
+ howtos 8
496
+ howto_topics ["setup", "config"]
497
+ reviews 3
498
+ comments 15
499
+ courses 2
500
+ events 4
501
+ personas 6
502
+ persona_levels [junior, senior]
503
+ briefings_per_persona 3
504
+ notes_per_persona 5
505
+ }
506
+ }`);
507
+ assert.strictEqual(ast.content.length, 1);
508
+ const c = ast.content[0];
509
+ assert.strictEqual(c.id, "kb");
510
+ assert.strictEqual(c.articles, 10);
511
+ assert.deepStrictEqual(c.article_topics, ["testing", "deployment"]);
512
+ assert.strictEqual(c.blogs, 5);
513
+ assert.strictEqual(c.faqs, 20);
514
+ assert.strictEqual(c.howtos, 8);
515
+ assert.deepStrictEqual(c.howto_topics, ["setup", "config"]);
516
+ assert.strictEqual(c.reviews, 3);
517
+ assert.strictEqual(c.comments, 15);
518
+ assert.strictEqual(c.courses, 2);
519
+ assert.strictEqual(c.events, 4);
520
+ assert.strictEqual(c.personas, 6);
521
+ assert.deepStrictEqual(c.persona_levels, ["junior", "senior"]);
522
+ assert.strictEqual(c.briefings_per_persona, 3);
523
+ assert.strictEqual(c.notes_per_persona, 5);
524
+ });
525
+ });
526
+
527
+ describe("default AST values", () => {
528
+ test("initializes empty arrays and nulls", () => {
529
+ const ast = parseDsl("universe empty {}");
530
+ assert.strictEqual(ast.domain, null);
531
+ assert.strictEqual(ast.industry, null);
532
+ assert.strictEqual(ast.people, null);
533
+ assert.strictEqual(ast.snapshots, null);
534
+ assert.strictEqual(ast.framework, null);
535
+ assert.deepStrictEqual(ast.orgs, []);
536
+ assert.deepStrictEqual(ast.departments, []);
537
+ assert.deepStrictEqual(ast.teams, []);
538
+ assert.deepStrictEqual(ast.projects, []);
539
+ assert.deepStrictEqual(ast.scenarios, []);
540
+ assert.deepStrictEqual(ast.content, []);
541
+ });
542
+ });
543
+
544
+ describe("error handling", () => {
545
+ test("throws on missing universe keyword", () => {
546
+ assert.throws(() => parseDsl("domain {}"), /Expected KEYWORD 'universe'/);
547
+ });
548
+
549
+ test("throws on missing opening brace", () => {
550
+ assert.throws(() => parseDsl("universe test domain"), /Expected LBRACE/);
551
+ });
552
+
553
+ test("throws on unexpected keyword at top level", () => {
554
+ assert.throws(
555
+ () => parseDsl("universe test { manager }"),
556
+ /Unexpected keyword 'manager' at top level/,
557
+ );
558
+ });
559
+
560
+ test("throws on unexpected keyword in org", () => {
561
+ assert.throws(
562
+ () => parseDsl("universe test { org x { size 5 } }"),
563
+ /Unexpected 'size' in org/,
564
+ );
565
+ });
566
+
567
+ test("throws on unexpected keyword in department", () => {
568
+ assert.throws(
569
+ () => parseDsl("universe test { department x { repos [] } }"),
570
+ /Unexpected 'repos' in department/,
571
+ );
572
+ });
573
+
574
+ test("throws on unexpected keyword in team", () => {
575
+ assert.throws(
576
+ () =>
577
+ parseDsl("universe test { department x { team t { headcount 5 } } }"),
578
+ /Unexpected 'headcount' in team/,
579
+ );
580
+ });
581
+
582
+ test("throws when expecting number but getting string", () => {
583
+ assert.throws(
584
+ () => parseDsl('universe test { seed "abc" }'),
585
+ /Expected number/,
586
+ );
587
+ });
588
+
589
+ test("throws when expecting string but getting number", () => {
590
+ assert.throws(
591
+ () => parseDsl("universe test { domain 123 }"),
592
+ /Expected STRING/,
593
+ );
594
+ });
595
+ });
596
+ });