@comet/agent-features 9.0.0-beta.5

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 (35) hide show
  1. package/LICENSE +24 -0
  2. package/package.json +19 -0
  3. package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
  4. package/rules/coding-guidelines/cdn.instructions.md +24 -0
  5. package/rules/coding-guidelines/general.instructions.md +30 -0
  6. package/rules/coding-guidelines/git.instructions.md +37 -0
  7. package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
  8. package/rules/coding-guidelines/libraries.instructions.md +34 -0
  9. package/rules/coding-guidelines/naming.instructions.md +39 -0
  10. package/rules/coding-guidelines/postgresql.instructions.md +40 -0
  11. package/rules/coding-guidelines/react.instructions.md +102 -0
  12. package/rules/coding-guidelines/security.instructions.md +44 -0
  13. package/rules/coding-guidelines/styling.instructions.md +50 -0
  14. package/rules/coding-guidelines/typescript.instructions.md +50 -0
  15. package/skills/.gitkeep +0 -0
  16. package/skills/comet-block/SKILL.md +246 -0
  17. package/skills/comet-block/references/admin-patterns.md +192 -0
  18. package/skills/comet-block/references/api-patterns.md +183 -0
  19. package/skills/comet-block/references/block-loader.md +368 -0
  20. package/skills/comet-block/references/block-types.md +210 -0
  21. package/skills/comet-block/references/custom-block-field.md +266 -0
  22. package/skills/comet-block/references/fixtures.md +436 -0
  23. package/skills/comet-block/references/image.md +341 -0
  24. package/skills/comet-block/references/migration.md +597 -0
  25. package/skills/comet-block/references/registration.md +167 -0
  26. package/skills/comet-block/references/response-summary.md +102 -0
  27. package/skills/comet-block/references/rich-text.md +309 -0
  28. package/skills/comet-block/references/select.md +176 -0
  29. package/skills/comet-block/references/site-patterns.md +202 -0
  30. package/skills/comet-mail-react/SKILL.md +541 -0
  31. package/skills/comet-mail-react/references/components-and-theme.md +441 -0
  32. package/skills/comet-mail-react/references/layout-patterns.md +315 -0
  33. package/skills/comet-mail-react/references/styling-and-customization.md +306 -0
  34. package/skills/comet-minor-update/SKILL.md +191 -0
  35. package/skills/dev-pm/SKILL.md +100 -0
@@ -0,0 +1,436 @@
1
+ # Block Fixture Rules
2
+
3
+ Detailed rules for creating and maintaining block fixture services that generate realistic seed data for Comet blocks. Load this file when creating a new block, editing an existing block, or managing fixture services.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ Block fixtures are NestJS `@Injectable()` services that generate realistic seed data for development databases using `@faker-js/faker`. Each block fixture service maps 1:1 to an API block and implements a `generateBlockInput()` method that returns a populated block input shape.
10
+
11
+ ---
12
+
13
+ ## When to Create Fixtures
14
+
15
+ - Create a fixture service whenever a new block is created.
16
+ - Fixtures populate development databases with realistic test data so developers and editors can see how content looks without manually entering data.
17
+ - Each block fixture service corresponds to exactly one API block.
18
+
19
+ ---
20
+
21
+ ## File Structure and Naming
22
+
23
+ - **File:** `{block-name}-block-fixture.service.ts` (kebab-case)
24
+ - **Class:** `{BlockName}BlockFixtureService`
25
+ - **Location:** Discover the existing fixtures directory structure in the project. Fixtures typically live in `api/src/db/fixtures/generators/blocks/{category}/`.
26
+ - **Categories** typically mirror block types (e.g., `layout`, `media`, `navigation`, `teaser`, `text-and-content`). Place the new fixture in the category that best matches the block's purpose.
27
+
28
+ ---
29
+
30
+ ## Core Type and Interface
31
+
32
+ Projects define a shared `BlockFixture` type that every fixture service must satisfy:
33
+
34
+ ```ts
35
+ import { type Block, type ExtractBlockInputFactoryProps } from "@comet/cms-api";
36
+
37
+ export type BlockFixture = {
38
+ generateBlockInput: () => Promise<ExtractBlockInputFactoryProps<Block>>;
39
+ };
40
+ ```
41
+
42
+ Every fixture service must implement `generateBlockInput()` returning `Promise<ExtractBlockInputFactoryProps<typeof TheBlock>>`. The service is decorated with `@Injectable()` and composes child fixture services via constructor injection.
43
+
44
+ ---
45
+
46
+ ## Fixture Patterns by Block Type
47
+
48
+ ### 1. Composite Block Fixture
49
+
50
+ For blocks created with `createBlock()`. Inject child fixture services via the constructor, generate each field with faker, and delegate child blocks to their fixture services.
51
+
52
+ ```ts
53
+ import { faker } from "@faker-js/faker";
54
+ import { Injectable } from "@nestjs/common";
55
+ import { DamImageBlock, type ExtractBlockInputFactoryProps } from "@comet/cms-api";
56
+ import { MyItemBlock } from "@src/documents/pages/blocks/my-item.block";
57
+ import { type BlockFixture } from "../block-fixture.type";
58
+ import { DamImageBlockFixtureService } from "../media/dam-image-block-fixture.service";
59
+ import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
60
+
61
+ // Import the enum directly from the block file
62
+ import { Variant } from "@src/documents/pages/blocks/my-item.block";
63
+
64
+ @Injectable()
65
+ export class MyItemBlockFixtureService implements BlockFixture {
66
+ constructor(
67
+ private readonly damImageBlockFixtureService: DamImageBlockFixtureService,
68
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
69
+ ) {}
70
+
71
+ async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof MyItemBlock>> {
72
+ return {
73
+ title: faker.lorem.words({ min: 3, max: 9 }),
74
+ variant: faker.helpers.arrayElement(Object.values(Variant)),
75
+ image: await this.damImageBlockFixtureService.generateBlockInput(),
76
+ description: await this.richTextBlockFixtureService.generateBlockInput(),
77
+ };
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### 2. List Block Fixture
83
+
84
+ For blocks created with `createListBlock()`. Accept optional `min`/`max` parameters to control the number of generated items. Each item has a `key`, `visible` flag, and `props` delegated to the item fixture service.
85
+
86
+ ```ts
87
+ import { faker } from "@faker-js/faker";
88
+ import { Injectable } from "@nestjs/common";
89
+ import { type ExtractBlockInputFactoryProps } from "@comet/cms-api";
90
+ import { MyListBlock } from "@src/documents/pages/blocks/my-list.block";
91
+ import { type BlockFixture } from "../block-fixture.type";
92
+ import { MyItemBlockFixtureService } from "./my-item-block-fixture.service";
93
+
94
+ @Injectable()
95
+ export class MyListBlockFixtureService implements BlockFixture {
96
+ constructor(private readonly myItemBlockFixtureService: MyItemBlockFixtureService) {}
97
+
98
+ async generateBlockInput(settings: { min?: number; max?: number } = {}): Promise<ExtractBlockInputFactoryProps<typeof MyListBlock>> {
99
+ const { min = 2, max = 6 } = settings;
100
+ const itemCount = faker.number.int({ min, max });
101
+
102
+ const blocks = [];
103
+ for (let i = 0; i < itemCount; i++) {
104
+ blocks.push({
105
+ key: faker.string.uuid(),
106
+ visible: true,
107
+ props: await this.myItemBlockFixtureService.generateBlockInput(),
108
+ });
109
+ }
110
+
111
+ return { blocks };
112
+ }
113
+ }
114
+ ```
115
+
116
+ ### 3. Blocks Block Fixture
117
+
118
+ For blocks created with `createBlocksBlock()`. Define a `Record<string, BlockFixture>` mapping each supported block type key to its fixture service. Iterate over all types and generate entries.
119
+
120
+ ```ts
121
+ import { faker } from "@faker-js/faker";
122
+ import { Injectable } from "@nestjs/common";
123
+ import { type ExtractBlockInputFactoryProps } from "@comet/cms-api";
124
+ import { MyContentBlock } from "@src/documents/pages/blocks/my-content.block";
125
+ import { type BlockFixture } from "../block-fixture.type";
126
+ import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service";
127
+ import { HeadingBlockFixtureService } from "../text-and-content/heading-block-fixture.service";
128
+ import { SpaceBlockFixtureService } from "../layout/space-block-fixture.service";
129
+
130
+ @Injectable()
131
+ export class MyContentBlockFixtureService implements BlockFixture {
132
+ private readonly blockFixtures: Record<string, BlockFixture>;
133
+
134
+ constructor(
135
+ private readonly richTextBlockFixtureService: RichTextBlockFixtureService,
136
+ private readonly headingBlockFixtureService: HeadingBlockFixtureService,
137
+ private readonly spaceBlockFixtureService: SpaceBlockFixtureService,
138
+ ) {
139
+ this.blockFixtures = {
140
+ richText: this.richTextBlockFixtureService,
141
+ heading: this.headingBlockFixtureService,
142
+ space: this.spaceBlockFixtureService,
143
+ };
144
+ }
145
+
146
+ async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof MyContentBlock>> {
147
+ const blocks = [];
148
+
149
+ for (const [type, fixtureService] of Object.entries(this.blockFixtures)) {
150
+ blocks.push({
151
+ key: faker.string.uuid(),
152
+ visible: true,
153
+ type,
154
+ props: await fixtureService.generateBlockInput(),
155
+ });
156
+ }
157
+
158
+ return { blocks };
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### 4. One-Of / Union Block Fixture
164
+
165
+ For blocks created with `createOneOfBlock()` or `createLinkBlock()`. Generate all possible types in the `attachedBlocks` array and randomly select one as `activeType`.
166
+
167
+ ```ts
168
+ import { faker } from "@faker-js/faker";
169
+ import { Injectable } from "@nestjs/common";
170
+ import { type ExtractBlockInputFactoryProps } from "@comet/cms-api";
171
+ import { MyOneOfBlock } from "@src/documents/pages/blocks/my-one-of.block";
172
+ import { type BlockFixture } from "../block-fixture.type";
173
+ import { DamImageBlockFixtureService } from "../media/dam-image-block-fixture.service";
174
+ import { DamVideoBlockFixtureService } from "../media/dam-video-block-fixture.service";
175
+
176
+ @Injectable()
177
+ export class MyOneOfBlockFixtureService implements BlockFixture {
178
+ private readonly blockFixtures: Record<string, BlockFixture>;
179
+
180
+ constructor(
181
+ private readonly damImageBlockFixtureService: DamImageBlockFixtureService,
182
+ private readonly damVideoBlockFixtureService: DamVideoBlockFixtureService,
183
+ ) {
184
+ this.blockFixtures = {
185
+ image: this.damImageBlockFixtureService,
186
+ damVideo: this.damVideoBlockFixtureService,
187
+ };
188
+ }
189
+
190
+ async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof MyOneOfBlock>> {
191
+ const attachedBlocks = [];
192
+
193
+ for (const [type, fixtureService] of Object.entries(this.blockFixtures)) {
194
+ attachedBlocks.push({
195
+ type,
196
+ props: await fixtureService.generateBlockInput(),
197
+ });
198
+ }
199
+
200
+ const activeType = faker.helpers.arrayElement(Object.keys(this.blockFixtures));
201
+
202
+ return { attachedBlocks, activeType };
203
+ }
204
+ }
205
+ ```
206
+
207
+ ### 5. Simple / Empty Block Fixture
208
+
209
+ Blocks with no data fields return an empty object.
210
+
211
+ ```ts
212
+ import { Injectable } from "@nestjs/common";
213
+ import { type ExtractBlockInputFactoryProps } from "@comet/cms-api";
214
+ import { MySpacerBlock } from "@src/documents/pages/blocks/my-spacer.block";
215
+ import { type BlockFixture } from "../block-fixture.type";
216
+
217
+ @Injectable()
218
+ export class MySpacerBlockFixtureService implements BlockFixture {
219
+ async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof MySpacerBlock>> {
220
+ return {};
221
+ }
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Faker.js Guidelines for Realistic Values
228
+
229
+ Match the faker method to the field's semantic purpose. A headline should be a few words, not a paragraph. A link label should be 1–3 words. List item counts should be small.
230
+
231
+ | Field Semantic | Faker Method | Notes |
232
+ | --------------------------- | --------------------------------------------------- | --------------------------------------------------------------- |
233
+ | Title / headline | `faker.lorem.words({ min: 3, max: 9 })` | Short phrase, not a sentence |
234
+ | Eyebrow / label | `faker.lorem.words({ min: 2, max: 5 })` | Compact label text |
235
+ | Link text / button text | `faker.lorem.words({ min: 1, max: 3 })` | Very short |
236
+ | Sentence (fact, caption) | `faker.lorem.sentence()` | Single sentence |
237
+ | Paragraph / body text | `faker.lorem.paragraph()` | Inside RichText Draft content blocks |
238
+ | Anchor name | `faker.word.words(3)` | Short identifier |
239
+ | Enum | `faker.helpers.arrayElement(Object.values(MyEnum))` | Import the enum from the block file |
240
+ | Boolean | `faker.datatype.boolean()` | |
241
+ | Number (percentage/overlay) | `faker.number.int({ min: 50, max: 90 })` | Constrain to a realistic range |
242
+ | Number (list item count) | `faker.number.int({ min: 2, max: 6 })` | Small, realistic set |
243
+ | Block key (UUID) | `faker.string.uuid()` | Used for `key` in list/blocks entries |
244
+ | URL | `faker.internet.url()` | Or use `faker.helpers.arrayElement([...])` with predefined URLs |
245
+ | SEO title | `faker.word.words(2)` | |
246
+ | SEO meta description | `faker.word.words(20)` | |
247
+
248
+ ---
249
+
250
+ ## RichText (Draft.js) Fixture Pattern
251
+
252
+ RichText blocks use Draft.js structure. Generate the content inline using `draftContent` with `blocks` and `entityMap`.
253
+
254
+ > **Important:** The `type` value in each Draft.js block must match a block type actually defined in the project's `RichTextBlock`. **Always check the project's Admin-side `RichTextBlock` configuration** (its `blocktypeMap` and `standardBlockType`) before writing fixture data. The examples below use `"paragraph-standard"` and `"header-one"` as illustrations — these types may not exist in the current project. Even `"unstyled"` (the Draft.js default) may be absent if the project overrides `standardBlockType` with a custom type. Use whichever type the project actually defines.
255
+
256
+ ### Paragraph Content
257
+
258
+ ```ts
259
+ // "paragraph-standard" is an example — use the actual paragraph block type from the project's RichTextBlock definition
260
+ const generateParagraphRichText = () => ({
261
+ draftContent: {
262
+ blocks: [
263
+ {
264
+ key: faker.string.uuid(),
265
+ text: faker.lorem.paragraph(),
266
+ type: "paragraph-standard",
267
+ depth: 0,
268
+ inlineStyleRanges: [],
269
+ entityRanges: [],
270
+ data: {},
271
+ },
272
+ ],
273
+ entityMap: {},
274
+ },
275
+ });
276
+ ```
277
+
278
+ ### Heading Content
279
+
280
+ ```ts
281
+ // "header-one" is an example — verify that heading block types exist in the project's RichTextBlock definition
282
+ const generateHeadingRichText = () => ({
283
+ draftContent: {
284
+ blocks: [
285
+ {
286
+ key: faker.string.uuid(),
287
+ text: faker.lorem.words({ min: 3, max: 9 }),
288
+ type: "header-one",
289
+ depth: 0,
290
+ inlineStyleRanges: [],
291
+ entityRanges: [],
292
+ data: {},
293
+ },
294
+ ],
295
+ entityMap: {},
296
+ },
297
+ });
298
+ ```
299
+
300
+ ### Notes
301
+
302
+ - **Always check the project's `RichTextBlock` configuration** to determine which block types are valid. The `type` field must exactly match a type that the block defines — either through `blocktypeMap` or as a standard Draft.js type that is explicitly supported.
303
+ - `"paragraph-standard"` and `"paragraph-small"` are project-defined custom types and will not exist in every project.
304
+ - `"header-one"` through `"header-six"` are standard Draft.js types but only valid if the project's RichTextBlock actually includes them in `supports`.
305
+ - `"unstyled"` is the Draft.js default but may be suppressed if the project sets a custom `standardBlockType` — in that case it is not a valid choice for fixture data either.
306
+ - Always include `key`, `depth: 0`, `inlineStyleRanges: []`, `entityRanges: []`, and `data: {}`.
307
+ - Wrap the content in `{ draftContent: { blocks: [...], entityMap: {} } }`.
308
+
309
+ ---
310
+
311
+ ## Registration and Parent Fixture Wiring
312
+
313
+ After creating a fixture service, perform these steps:
314
+
315
+ ### 1. Register in the fixtures module
316
+
317
+ Add the new fixture service as a provider in the project's `fixtures.module.ts`:
318
+
319
+ ```ts
320
+ // api/src/db/fixtures/fixtures.module.ts
321
+ import { MyItemBlockFixtureService } from "./generators/blocks/teaser/my-item-block-fixture.service";
322
+
323
+ @Module({
324
+ providers: [
325
+ // ... existing providers
326
+ MyItemBlockFixtureService,
327
+ ],
328
+ })
329
+ export class FixturesModule {}
330
+ ```
331
+
332
+ ### 2. Wire into the parent block's fixture
333
+
334
+ The new block is always registered in a parent block (typically a blocks-block like a page content block). Find the parent block's fixture service and add the new fixture there:
335
+
336
+ 1. Inject the new fixture service into the parent fixture's constructor.
337
+ 2. Add a new entry in the parent fixture's `blockFixtures` record, mapping the block's type key (the same camelCase key used in `supportedBlocks`) to the new fixture service.
338
+
339
+ ```ts
340
+ // In the parent blocks-block fixture service
341
+ constructor(
342
+ // ... existing injections
343
+ private readonly myItemBlockFixtureService: MyItemBlockFixtureService,
344
+ ) {
345
+ this.blockFixtures = {
346
+ // ... existing entries
347
+ myItem: this.myItemBlockFixtureService,
348
+ };
349
+ }
350
+ ```
351
+
352
+ ### 3. If the parent has no fixture
353
+
354
+ Check whether the parent blocks-block already has a fixture service. If it does not, ask the user whether a fixture service should be created for the parent block as well, or whether fixtures should be skipped entirely for this block.
355
+
356
+ ### 4. Composite block parent
357
+
358
+ If the new block is a child of another composite block (not a blocks-block), inject the new fixture into that parent fixture's constructor and call its `generateBlockInput()` in the parent's own `generateBlockInput()` method.
359
+
360
+ ---
361
+
362
+ ## Editing Existing Block Fixtures
363
+
364
+ Whenever an existing block is modified, its fixture must be updated to match.
365
+
366
+ | Change Type | Fixture Action |
367
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
368
+ | **Field added** | Add the new field to `generateBlockInput()` with an appropriate faker call. |
369
+ | **Field removed** | Remove the field from `generateBlockInput()`. |
370
+ | **Field type changed** | Update the faker call or child fixture delegation to match the new type. |
371
+ | **Enum values changed** | Update the enum import. No other fixture change is usually needed since `faker.helpers.arrayElement(Object.values(...))` picks from all values automatically. |
372
+ | **Block renamed** | Rename the fixture service class, file, and all references (imports, constructor injections, module registration, parent fixture wiring). |
373
+
374
+ ---
375
+
376
+ ## Deleting a Block's Fixtures
377
+
378
+ When a block is deleted:
379
+
380
+ 1. Delete the fixture service file.
381
+ 2. Remove it from `fixtures.module.ts` providers.
382
+ 3. Remove it from any parent fixture service (constructor injection, `blockFixtures` record entry, and import).
383
+ 4. If the deleted block had child fixture services that are no longer used by any other fixture, delete those too.
384
+
385
+ ---
386
+
387
+ ## Conditional Logic Patterns
388
+
389
+ Some blocks have fields that depend on other field values. Reflect this in fixtures.
390
+
391
+ **Boolean-dependent fields:**
392
+
393
+ ```ts
394
+ const autoplay = faker.datatype.boolean();
395
+
396
+ return {
397
+ autoplay,
398
+ showControls: !autoplay,
399
+ // ...
400
+ };
401
+ ```
402
+
403
+ **Enum-dependent optional fields:**
404
+
405
+ ```ts
406
+ const variant = faker.helpers.arrayElement(Object.values(Variant));
407
+
408
+ return {
409
+ variant,
410
+ subtitle: variant === "expanded" ? faker.lorem.words({ min: 3, max: 9 }) : undefined,
411
+ // ...
412
+ };
413
+ ```
414
+
415
+ **Optional settings parameter for controlling generation behavior:**
416
+
417
+ ```ts
418
+ async generateBlockInput(
419
+ settings: { min?: number; max?: number; includeOptional?: boolean } = {},
420
+ ): Promise<ExtractBlockInputFactoryProps<typeof MyBlock>> {
421
+ const { min = 2, max = 6, includeOptional = true } = settings;
422
+ // ...
423
+ }
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Cross-references
429
+
430
+ | Topic | File |
431
+ | ---------------------------------------------------- | ---------------------------------- |
432
+ | Block creation workflow and registration | [SKILL.md](../SKILL.md) |
433
+ | API block patterns, decorators, field rules | [api-patterns.md](api-patterns.md) |
434
+ | Enum/select patterns and enum imports | [select.md](select.md) |
435
+ | RichText block structure (relevant to Draft.js data) | [rich-text.md](rich-text.md) |
436
+ | Image block types and selection | [image.md](image.md) |