@synap-core/workspace-templates 0.7.1 → 0.7.3

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 (50) hide show
  1. package/dist/index.d.ts +266 -8
  2. package/dist/index.js +2314 -1440
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -3
  5. package/src/agent-fleet.yaml +666 -108
  6. package/src/blockchain-ecosystem.yaml +1 -0
  7. package/src/brand-library.yaml +127 -12
  8. package/src/builder.yaml +116 -12
  9. package/src/business-developer.yaml +110 -18
  10. package/src/catalog.test.ts +2 -2
  11. package/src/catalog.ts +13 -3
  12. package/src/communication.yaml +23 -453
  13. package/src/composition-graph.test.ts +28 -2
  14. package/src/concept-guard.test.ts +176 -0
  15. package/src/concept-guard.ts +219 -0
  16. package/src/content-os.yaml +103 -10
  17. package/src/content-studio.yaml +1 -0
  18. package/src/crm.yaml +1 -0
  19. package/src/define.ts +8 -8
  20. package/src/dev-dashboard.yaml +43 -9
  21. package/src/domains.test.ts +117 -0
  22. package/src/domains.ts +102 -0
  23. package/src/ecosystem.yaml +25 -5
  24. package/src/enterprise-os.yaml +1 -0
  25. package/src/finance.yaml +46 -5
  26. package/src/foundation.yaml +13 -1
  27. package/src/generate.d.mts +15 -0
  28. package/src/generate.mjs +86 -0
  29. package/src/grants.yaml +1 -0
  30. package/src/hr.yaml +61 -8
  31. package/src/index.ts +40 -0
  32. package/src/internal-runbook.yaml +36 -5
  33. package/src/legal.yaml +97 -12
  34. package/src/life-os.yaml +188 -27
  35. package/src/marketing.yaml +15 -2
  36. package/src/meetings-knowledge.yaml +143 -23
  37. package/src/operations.yaml +347 -0
  38. package/src/outreach-comms.yaml +11 -3
  39. package/src/project-management.yaml +840 -111
  40. package/src/research-base.yaml +516 -0
  41. package/src/research-decision.yaml +158 -0
  42. package/src/shared-role-ssot.test.ts +268 -0
  43. package/src/social.yaml +105 -37
  44. package/src/templates-freshness.test.ts +55 -0
  45. package/src/templates.ts +4620 -4278
  46. package/src/the-arch.yaml +1 -0
  47. package/src/types.ts +9 -0
  48. package/src/validate.test.ts +81 -0
  49. package/src/validate.ts +561 -0
  50. package/src/personal.yaml +0 -177
@@ -14,6 +14,7 @@
14
14
 
15
15
  meta:
16
16
  slug: blockchain-ecosystem
17
+ domain: strategy
17
18
  name: Blockchain Ecosystem
18
19
  description: "Web3 market actors — market-makers, auditors, dev shops, KOLs — and how they connect. Overlays the Ecosystem workspace."
19
20
  icon: boxes
@@ -4,6 +4,7 @@
4
4
 
5
5
  meta:
6
6
  slug: brand-library
7
+ domain: content
7
8
  name: Brand Library
8
9
  description: Reusable source of truth for brand identity, colors, typography, assets, tokens, templates, and generation rules.
9
10
  icon: palette
@@ -40,7 +41,16 @@ profiles:
40
41
  label: Brand type
41
42
  valueType: string
42
43
  inputType: select
43
- enumValues: [product-digital, product-physical, agency, personal-brand, nonprofit, institutional, universal]
44
+ enumValues:
45
+ [
46
+ product-digital,
47
+ product-physical,
48
+ agency,
49
+ personal-brand,
50
+ nonprofit,
51
+ institutional,
52
+ universal,
53
+ ]
44
54
  constraints: { defaultValue: universal }
45
55
  - slug: brand-status
46
56
  label: Status
@@ -92,7 +102,25 @@ profiles:
92
102
  label: Role
93
103
  valueType: string
94
104
  inputType: select
95
- enumValues: [primary, secondary, accent, background, surface, surface-elevated, text, text-muted, border, success, warning, error, info, neutral, campaign, custom]
105
+ enumValues:
106
+ [
107
+ primary,
108
+ secondary,
109
+ accent,
110
+ background,
111
+ surface,
112
+ surface-elevated,
113
+ text,
114
+ text-muted,
115
+ border,
116
+ success,
117
+ warning,
118
+ error,
119
+ info,
120
+ neutral,
121
+ campaign,
122
+ custom,
123
+ ]
96
124
  constraints: { defaultValue: custom }
97
125
  - slug: color-group
98
126
  label: Group
@@ -160,7 +188,8 @@ profiles:
160
188
  label: License
161
189
  valueType: string
162
190
  inputType: select
163
- enumValues: [open-source, google-fonts, adobe-fonts, licensed, custom, system]
191
+ enumValues:
192
+ [open-source, google-fonts, adobe-fonts, licensed, custom, system]
164
193
  constraints: { defaultValue: google-fonts }
165
194
  - slug: font-weights
166
195
  label: Weights
@@ -196,12 +225,44 @@ profiles:
196
225
  label: Kind
197
226
  valueType: string
198
227
  inputType: select
199
- enumValues: [logo, wordmark, symbol, monogram, pattern, texture, icon-set, illustration, photo, video, document, font, 3d-model, print-ready, reference, other]
228
+ enumValues:
229
+ [
230
+ logo,
231
+ wordmark,
232
+ symbol,
233
+ monogram,
234
+ pattern,
235
+ texture,
236
+ icon-set,
237
+ illustration,
238
+ photo,
239
+ video,
240
+ document,
241
+ font,
242
+ 3d-model,
243
+ print-ready,
244
+ reference,
245
+ other,
246
+ ]
200
247
  - slug: asset-variant
201
248
  label: Variant
202
249
  valueType: string
203
250
  inputType: select
204
- enumValues: [primary, secondary, horizontal, stacked, mono-dark, mono-light, white, reversed, favicon, social-avatar, og-image, other]
251
+ enumValues:
252
+ [
253
+ primary,
254
+ secondary,
255
+ horizontal,
256
+ stacked,
257
+ mono-dark,
258
+ mono-light,
259
+ white,
260
+ reversed,
261
+ favicon,
262
+ social-avatar,
263
+ og-image,
264
+ other,
265
+ ]
205
266
  - slug: asset-status
206
267
  label: Status
207
268
  valueType: string
@@ -244,7 +305,17 @@ profiles:
244
305
  label: Kind
245
306
  valueType: string
246
307
  inputType: select
247
- enumValues: [website, competitor, campaign, moodboard, copy-example, design-example, customer-example, other]
308
+ enumValues:
309
+ [
310
+ website,
311
+ competitor,
312
+ campaign,
313
+ moodboard,
314
+ copy-example,
315
+ design-example,
316
+ customer-example,
317
+ other,
318
+ ]
248
319
  - slug: reference-url
249
320
  label: URL
250
321
  valueType: string
@@ -307,7 +378,17 @@ profiles:
307
378
  label: Context
308
379
  valueType: string
309
380
  inputType: select
310
- enumValues: [general, marketing, product, support, social, legal, editorial, investor]
381
+ enumValues:
382
+ [
383
+ general,
384
+ marketing,
385
+ product,
386
+ support,
387
+ social,
388
+ legal,
389
+ editorial,
390
+ investor,
391
+ ]
311
392
  constraints: { defaultValue: general }
312
393
  - slug: voice-status
313
394
  label: Status
@@ -351,12 +432,38 @@ profiles:
351
432
  label: Kind
352
433
  valueType: string
353
434
  inputType: select
354
- enumValues: [visual, voice, legal, accessibility, sizing, clearance, color-usage, typography-usage, do, dont, generation, other]
435
+ enumValues:
436
+ [
437
+ visual,
438
+ voice,
439
+ legal,
440
+ accessibility,
441
+ sizing,
442
+ clearance,
443
+ color-usage,
444
+ typography-usage,
445
+ do,
446
+ dont,
447
+ generation,
448
+ other,
449
+ ]
355
450
  - slug: rule-subject
356
451
  label: Subject
357
452
  valueType: string
358
453
  inputType: select
359
- enumValues: [logo, color, typography, pattern, asset, voice, general, digital, print, packaging]
454
+ enumValues:
455
+ [
456
+ logo,
457
+ color,
458
+ typography,
459
+ pattern,
460
+ asset,
461
+ voice,
462
+ general,
463
+ digital,
464
+ print,
465
+ packaging,
466
+ ]
360
467
  - slug: rule-severity
361
468
  label: Severity
362
469
  valueType: string
@@ -387,7 +494,8 @@ profiles:
387
494
  label: Kind
388
495
  valueType: string
389
496
  inputType: select
390
- enumValues: [artboard, deck, carousel, banner, document, html, social, other]
497
+ enumValues:
498
+ [artboard, deck, carousel, banner, document, html, social, other]
391
499
  - slug: template-status
392
500
  label: Status
393
501
  valueType: string
@@ -419,7 +527,8 @@ profiles:
419
527
  label: Kind
420
528
  valueType: string
421
529
  inputType: select
422
- enumValues: [hero, card, cta, quote, banner, section, layout, html, other]
530
+ enumValues:
531
+ [hero, card, cta, quote, banner, section, layout, html, other]
423
532
  - slug: component-status
424
533
  label: Status
425
534
  valueType: string
@@ -562,7 +671,13 @@ onboarding:
562
671
  - profileSlug: brand-voice-guide
563
672
  what: Voice guides — tone, personality traits, vocabulary, and do/don't examples per context.
564
673
  cardinality: few
565
- keyFields: [voice-personality-traits, voice-tone-descriptors, voice-example-do, voice-example-dont]
674
+ keyFields:
675
+ [
676
+ voice-personality-traits,
677
+ voice-tone-descriptors,
678
+ voice-example-do,
679
+ voice-example-dont,
680
+ ]
566
681
  - profileSlug: brand-rule
567
682
  what: Usage rules and constraints (mandatory / important / guideline).
568
683
  cardinality: several
package/src/builder.yaml CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  meta:
6
6
  slug: builder-workspace
7
+ domain: build
7
8
  name: Builder
8
9
  description: "Builder workspace — apps, features, services, packages, environments, deployments, recipes, decisions, incidents, plus delegated agent tasks/projects/skills/providers."
9
10
  icon: wrench
@@ -120,7 +121,20 @@ profiles:
120
121
  label: Agent Status
121
122
  valueType: string
122
123
  inputType: select
123
- enumValues: [idea, gathering_context, dispatched, planning, plan_ready, executing, verifying, debugging, awaiting_review, done, error]
124
+ enumValues:
125
+ [
126
+ idea,
127
+ gathering_context,
128
+ dispatched,
129
+ planning,
130
+ plan_ready,
131
+ executing,
132
+ verifying,
133
+ debugging,
134
+ awaiting_review,
135
+ done,
136
+ error,
137
+ ]
124
138
  constraints: { defaultValue: idea }
125
139
  - slug: agent_phase
126
140
  label: Agent Phase (current dispatch)
@@ -205,7 +219,19 @@ profiles:
205
219
  label: Agent Status
206
220
  valueType: string
207
221
  inputType: select
208
- enumValues: [idle, gathering_context, planning, awaiting_plan_approval, executing, verifying, awaiting_deploy_approval, deploying, done, blocked]
222
+ enumValues:
223
+ [
224
+ idle,
225
+ gathering_context,
226
+ planning,
227
+ awaiting_plan_approval,
228
+ executing,
229
+ verifying,
230
+ awaiting_deploy_approval,
231
+ deploying,
232
+ done,
233
+ blocked,
234
+ ]
209
235
  constraints: { defaultValue: idle }
210
236
  - slug: context_doc_id
211
237
  label: Context Doc ID
@@ -794,7 +820,16 @@ profiles:
794
820
  label: Category
795
821
  valueType: string
796
822
  inputType: select
797
- enumValues: [architecture, testing, security, performance, style, tooling, workflow]
823
+ enumValues:
824
+ [
825
+ architecture,
826
+ testing,
827
+ security,
828
+ performance,
829
+ style,
830
+ tooling,
831
+ workflow,
832
+ ]
798
833
  - slug: tech_stack
799
834
  label: Tech Stack
800
835
  valueType: string
@@ -874,7 +909,8 @@ profiles:
874
909
  label: Status
875
910
  valueType: string
876
911
  inputType: select
877
- enumValues: [Backlog, Queued, In Progress, Blocked, Completed, Cancelled]
912
+ enumValues:
913
+ [Backlog, Queued, In Progress, Blocked, Completed, Cancelled]
878
914
  - slug: project-priority
879
915
  label: Priority
880
916
  valueType: string
@@ -899,7 +935,17 @@ profiles:
899
935
  label: Type
900
936
  valueType: string
901
937
  inputType: select
902
- enumValues: [Development, Research, Content, Automation, DevOps, Data, Outreach, Monitoring]
938
+ enumValues:
939
+ [
940
+ Development,
941
+ Research,
942
+ Content,
943
+ Automation,
944
+ DevOps,
945
+ Data,
946
+ Outreach,
947
+ Monitoring,
948
+ ]
903
949
  - slug: project-goal
904
950
  label: Goal
905
951
  valueType: string
@@ -933,7 +979,18 @@ profiles:
933
979
  label: Type
934
980
  valueType: string
935
981
  inputType: select
936
- enumValues: [Code, Research, Content, Ops, Data, Outreach, Review, Monitor, Custom]
982
+ enumValues:
983
+ [
984
+ Code,
985
+ Research,
986
+ Content,
987
+ Ops,
988
+ Data,
989
+ Outreach,
990
+ Review,
991
+ Monitor,
992
+ Custom,
993
+ ]
937
994
  - slug: task-due-date
938
995
  label: Due Date
939
996
  valueType: date
@@ -969,7 +1026,19 @@ profiles:
969
1026
  label: Category
970
1027
  valueType: string
971
1028
  inputType: select
972
- enumValues: [Productivity, Development, Communication, Data, Security, Storage, Browser, Voice, IoT, Custom]
1029
+ enumValues:
1030
+ [
1031
+ Productivity,
1032
+ Development,
1033
+ Communication,
1034
+ Data,
1035
+ Security,
1036
+ Storage,
1037
+ Browser,
1038
+ Voice,
1039
+ IoT,
1040
+ Custom,
1041
+ ]
973
1042
  - slug: skill-source-url
974
1043
  label: Source URL
975
1044
  valueType: string
@@ -1007,7 +1076,8 @@ profiles:
1007
1076
  label: Type
1008
1077
  valueType: string
1009
1078
  inputType: select
1010
- enumValues: [LLM Cloud, LLM Local, API Gateway, Vector DB, Storage, Custom]
1079
+ enumValues:
1080
+ [LLM Cloud, LLM Local, API Gateway, Vector DB, Storage, Custom]
1011
1081
  - slug: provider-key-health
1012
1082
  label: Key Health
1013
1083
  valueType: string
@@ -1046,7 +1116,20 @@ profiles:
1046
1116
  label: Type
1047
1117
  valueType: string
1048
1118
  inputType: select
1049
- enumValues: [frontend, backend, database, cache, queue, external-api, cdn, storage, auth, monitoring, other]
1119
+ enumValues:
1120
+ [
1121
+ frontend,
1122
+ backend,
1123
+ database,
1124
+ cache,
1125
+ queue,
1126
+ external-api,
1127
+ cdn,
1128
+ storage,
1129
+ auth,
1130
+ monitoring,
1131
+ other,
1132
+ ]
1050
1133
  - slug: componentStatus
1051
1134
  label: Status
1052
1135
  valueType: string
@@ -1102,7 +1185,16 @@ profiles:
1102
1185
  label: Category
1103
1186
  valueType: string
1104
1187
  inputType: select
1105
- enumValues: [user-flow, data-pipeline, deploy-pipeline, auth-flow, payment-flow, notification-flow, other]
1188
+ enumValues:
1189
+ [
1190
+ user-flow,
1191
+ data-pipeline,
1192
+ deploy-pipeline,
1193
+ auth-flow,
1194
+ payment-flow,
1195
+ notification-flow,
1196
+ other,
1197
+ ]
1106
1198
  - slug: trigger
1107
1199
  label: Trigger
1108
1200
  valueType: string
@@ -1138,7 +1230,18 @@ profiles:
1138
1230
  label: Type
1139
1231
  valueType: string
1140
1232
  inputType: select
1141
- enumValues: [rendering, data, auth, ai, infra, ui, workflow, integration, external]
1233
+ enumValues:
1234
+ [
1235
+ rendering,
1236
+ data,
1237
+ auth,
1238
+ ai,
1239
+ infra,
1240
+ ui,
1241
+ workflow,
1242
+ integration,
1243
+ external,
1244
+ ]
1142
1245
  - slug: maturity
1143
1246
  label: Maturity
1144
1247
  valueType: string
@@ -1214,7 +1317,8 @@ views:
1214
1317
  # derives nodes from the loaded entities + edges from the relations table.
1215
1318
  - name: Architecture Graph
1216
1319
  type: flow
1217
- scopeProfileSlugs: [devplane_workflow, devplane_system_component, devplane_capability]
1320
+ scopeProfileSlugs:
1321
+ [devplane_workflow, devplane_system_component, devplane_capability]
1218
1322
  config:
1219
1323
  render:
1220
1324
  variant: architecture
@@ -17,6 +17,7 @@
17
17
 
18
18
  meta:
19
19
  slug: business-developer
20
+ domain: revenue
20
21
  name: Business Developer
21
22
  description: "A lead-first sales motion for a CRM workspace: qualify leads, work commercial proposals, and hand won clients to the next system."
22
23
  icon: target
@@ -50,13 +51,29 @@ profiles:
50
51
  description: A person or company actively being qualified or contacted by this CRM workspace.
51
52
  scope: WORKSPACE
52
53
  profileKind: role
54
+ # Explicit concept identity: this is the WORKSPACE tier of the `lead`
55
+ # concept, not a second, unrelated one. Declaring it makes the two-tier
56
+ # design machine-readable (both tiers are reachable via getBySemanticSlug)
57
+ # and is what the cross-template concept guard requires alongside its
58
+ # DECLARED_DIVERGENCES entry — see src/concept-guard.ts.
59
+ semanticSlug: crm.lead
53
60
  applicableKinds: [person, company]
54
61
  properties:
55
62
  - slug: lifecycleStage
56
63
  label: Lifecycle stage
57
64
  valueType: string
58
65
  inputType: select
59
- enumValues: [new, researching, outreach, engaged, qualified, nurturing, disqualified, converted]
66
+ enumValues:
67
+ [
68
+ new,
69
+ researching,
70
+ outreach,
71
+ engaged,
72
+ qualified,
73
+ nurturing,
74
+ disqualified,
75
+ converted,
76
+ ]
60
77
  constraints: { defaultValue: new }
61
78
  - slug: assignedUserId
62
79
  label: Assigned user ID
@@ -67,7 +84,18 @@ profiles:
67
84
  label: Source
68
85
  valueType: string
69
86
  inputType: select
70
- enumValues: [manual, referral, linkedin, email, event, inbound, partner, import, other]
87
+ enumValues:
88
+ [
89
+ manual,
90
+ referral,
91
+ linkedin,
92
+ email,
93
+ event,
94
+ inbound,
95
+ partner,
96
+ import,
97
+ other,
98
+ ]
71
99
  - slug: icpScore
72
100
  label: ICP score
73
101
  valueType: number
@@ -112,6 +140,8 @@ profiles:
112
140
  description: A CRM lead converted by a won commercial proposal and ready for handoff.
113
141
  scope: WORKSPACE
114
142
  profileKind: role
143
+ # Workspace tier of the `client` concept — see the note on `crm-lead` above.
144
+ semanticSlug: crm.client
115
145
  applicableKinds: [person, company]
116
146
  properties:
117
147
  - slug: becameClientAt
@@ -333,8 +363,14 @@ automations:
333
363
  message: This action is only available for a commercial proposal.
334
364
  - path: steps.read-deal.output.entity.properties.commercialStage
335
365
  anyOf:
336
- - { path: steps.read-deal.output.entity.properties.commercialStage, equals: won }
337
- - { path: steps.read-deal.output.entity.properties.dealStage, equals: won }
366
+ - {
367
+ path: steps.read-deal.output.entity.properties.commercialStage,
368
+ equals: won,
369
+ }
370
+ - {
371
+ path: steps.read-deal.output.entity.properties.dealStage,
372
+ equals: won,
373
+ }
338
374
  message: Mark the proposal as won before handing it off.
339
375
  - path: steps.read-deal.output.entity.properties.handoffStatus
340
376
  notEquals: handed_off
@@ -586,21 +622,77 @@ automations:
586
622
  - { id: party-search-guard, source: find-party, target: guard-party }
587
623
  - { id: party-guard-read, source: guard-party, target: read-party }
588
624
  - { id: party-read-guard, source: read-party, target: guard-lead }
589
- - { id: lead-guard-commission-claim, source: guard-lead, target: claim-commission-entitlement }
590
- - { id: commission-claim-guard, source: claim-commission-entitlement, target: guard-commission-claim }
591
- - { id: commission-guard-first-claim, source: guard-commission-claim, target: claim-first-client-deal }
592
- - { id: first-claim-history, source: claim-first-client-deal, target: prior-won-deals }
593
- - { id: history-condition, source: prior-won-deals, target: has-no-prior-won-deals }
594
- - { id: first-confirm-bonus, source: is-first-client-deal, target: first-deal-bonus-bps }
595
- - { id: first-history-confirm, source: has-no-prior-won-deals, target: is-first-client-deal }
596
- - { id: first-confirm-sequence, source: is-first-client-deal, target: commission-sequence }
597
- - { id: bonus-rate, source: first-deal-bonus-bps, target: commission-rate-bps }
598
- - { id: rate-product, source: commission-rate-bps, target: commission-product }
599
- - { id: product-amount, source: commission-product, target: commission-amount }
625
+ - {
626
+ id: lead-guard-commission-claim,
627
+ source: guard-lead,
628
+ target: claim-commission-entitlement,
629
+ }
630
+ - {
631
+ id: commission-claim-guard,
632
+ source: claim-commission-entitlement,
633
+ target: guard-commission-claim,
634
+ }
635
+ - {
636
+ id: commission-guard-first-claim,
637
+ source: guard-commission-claim,
638
+ target: claim-first-client-deal,
639
+ }
640
+ - {
641
+ id: first-claim-history,
642
+ source: claim-first-client-deal,
643
+ target: prior-won-deals,
644
+ }
645
+ - {
646
+ id: history-condition,
647
+ source: prior-won-deals,
648
+ target: has-no-prior-won-deals,
649
+ }
650
+ - {
651
+ id: first-confirm-bonus,
652
+ source: is-first-client-deal,
653
+ target: first-deal-bonus-bps,
654
+ }
655
+ - {
656
+ id: first-history-confirm,
657
+ source: has-no-prior-won-deals,
658
+ target: is-first-client-deal,
659
+ }
660
+ - {
661
+ id: first-confirm-sequence,
662
+ source: is-first-client-deal,
663
+ target: commission-sequence,
664
+ }
665
+ - {
666
+ id: bonus-rate,
667
+ source: first-deal-bonus-bps,
668
+ target: commission-rate-bps,
669
+ }
670
+ - {
671
+ id: rate-product,
672
+ source: commission-rate-bps,
673
+ target: commission-product,
674
+ }
675
+ - {
676
+ id: product-amount,
677
+ source: commission-product,
678
+ target: commission-amount,
679
+ }
600
680
  - { id: amount-time, source: commission-amount, target: handoff-time }
601
- - { id: time-commission, source: handoff-time, target: create-commission }
602
- - { id: sequence-commission, source: commission-sequence, target: create-commission }
603
- - { id: commission-link, source: create-commission, target: link-commission }
681
+ - {
682
+ id: time-commission,
683
+ source: handoff-time,
684
+ target: create-commission,
685
+ }
686
+ - {
687
+ id: sequence-commission,
688
+ source: commission-sequence,
689
+ target: create-commission,
690
+ }
691
+ - {
692
+ id: commission-link,
693
+ source: create-commission,
694
+ target: link-commission,
695
+ }
604
696
  - { id: link-deal, source: link-commission, target: mark-deal-won }
605
697
  - { id: deal-client, source: mark-deal-won, target: attach-client }
606
698
  - { id: client-lead, source: attach-client, target: convert-lead }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Unit tests for the mergeCatalog door. Run: `npx tsx --test src/catalog.test.ts`
2
+ * Unit tests for the mergeCatalog door. Run: `npx vitest run packages/workspace-templates` (from the repo root).
3
3
  *
4
4
  * These test the SOURCE (`./catalog.js` → catalog.ts), never `dist/`, so no build
5
5
  * is required and there is no stale-generated-output trap.
6
6
  */
7
7
 
8
- import { test } from "node:test";
8
+ import { test } from "vitest";
9
9
  import assert from "node:assert/strict";
10
10
  import { mergeCatalog, computeTemplateUpdates, type RemoteEntry } from "./catalog.js";
11
11
  import type { WorkspaceYaml } from "./types.js";
package/src/catalog.ts CHANGED
@@ -58,6 +58,8 @@ export interface RemoteEntry {
58
58
  description?: string;
59
59
  /** CP package category / type (e.g. `"workspace"`, `"view"`). */
60
60
  category?: string;
61
+ /** Human-facing subject area for display grouping — authored on `meta.domain`. See `./domains.ts`. */
62
+ domain?: string;
61
63
  tags?: string[];
62
64
  icon?: string | null;
63
65
  /**
@@ -120,6 +122,8 @@ export interface CpPackageRow {
120
122
  description?: string | null;
121
123
  /** CP package category / type (e.g. `"workspace"`, `"view"`). */
122
124
  category?: string;
125
+ /** Human-facing subject area for display grouping — from the package's `meta.domain`. */
126
+ domain?: string | null;
123
127
  tags?: string[] | null;
124
128
  icon?: string | null;
125
129
  /** Present only on `/mine` rows. `false` ⇒ private. Browse rows omit it. */
@@ -146,6 +150,7 @@ export function rowToRemoteEntry(
146
150
  name: row.displayName,
147
151
  description: row.description ?? undefined,
148
152
  category: row.category,
153
+ domain: row.domain ?? undefined,
149
154
  tags: row.tags ?? [],
150
155
  icon: row.icon ?? null,
151
156
  isPrivate,
@@ -164,6 +169,8 @@ interface CatalogEntryBase {
164
169
  description?: string;
165
170
  /** Package category / type. Bundled templates are always `"workspace"`. */
166
171
  category?: string;
172
+ /** Human-facing subject area for display grouping — authored `meta.domain`. See ./domains. */
173
+ domain?: string;
167
174
  tags: string[];
168
175
  icon?: string | null;
169
176
  /** `true` ⇒ not publicly listed (internal bundled template, or private CP row). */
@@ -240,10 +247,12 @@ function bundledToEntry(tpl: WorkspaceYaml): BundledCatalogEntry {
240
247
  slug: tpl.meta.slug,
241
248
  name: tpl.meta.name,
242
249
  description: tpl.meta.description,
243
- // Every bundled template is a full workspace — the CP calls this category
244
- // "workspace" (PACKAGE_TYPES). Finer display grouping is derived from `tags`
245
- // by the consumer (browser `categoryFor`), not baked in here.
250
+ // `category` is the PACKAGE TYPE — every bundled template is a full
251
+ // workspace (PACKAGE_TYPES). Human display grouping is the SEPARATE
252
+ // authored `domain` field (see ./domains.ts), read straight from meta —
253
+ // not re-derived from tags by each consumer, which is what drifted before.
246
254
  category: "workspace",
255
+ domain: tpl.meta.domain,
247
256
  tags: tpl.meta.tags ?? [],
248
257
  icon: tpl.meta.icon,
249
258
  isPrivate: tpl.meta.isPublic === false,
@@ -258,6 +267,7 @@ function remoteToEntry(row: RemoteEntry): RemoteCatalogEntry {
258
267
  name: row.name,
259
268
  description: row.description,
260
269
  category: row.category,
270
+ domain: row.domain,
261
271
  tags: row.tags ?? [],
262
272
  icon: row.icon ?? null,
263
273
  isPrivate: row.isPrivate ?? false,