@clipboard-health/ai-rules 1.2.0 → 1.2.2
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.
- package/backend/CLAUDE.md +1 -219
- package/common/CLAUDE.md +1 -61
- package/frontend/CLAUDE.md +1 -1037
- package/fullstack/CLAUDE.md +1 -1195
- package/package.json +1 -1
- package/scripts/constants.js +5 -1
- package/scripts/sync.js +1 -1
package/backend/CLAUDE.md
CHANGED
|
@@ -1,219 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# NestJS APIs
|
|
4
|
-
|
|
5
|
-
- Use a three-tier architecture:
|
|
6
|
-
- Controllers in the entrypoints tier translate from data transfer objects (DTOs) to domain objects (DOs) and call the logic tier.
|
|
7
|
-
- Logic tier services call other services in the logic tier and repos and gateways at the data tier. The logic tier operates only on DOs.
|
|
8
|
-
- Data tier repos translate from DOs to data access objects (DAOs), call the database using either Prisma for Postgres or Mongoose for MongoDB, and then translate from DAOs to DOs before returning to the logic tier.
|
|
9
|
-
- Use ts-rest to define contracts using Zod schemas, one contract per resource.
|
|
10
|
-
- A controller implements each ts-rest contract.
|
|
11
|
-
- Requests and responses follow the JSON:API specification, including pagination for listings.
|
|
12
|
-
- Use TypeDoc to document public functions, classes, methods, and complex code blocks.
|
|
13
|
-
|
|
14
|
-
<!-- Source: .ruler/backend/notifications.md -->
|
|
15
|
-
|
|
16
|
-
# Notifications
|
|
17
|
-
|
|
18
|
-
Send notifications through [Knock](https://docs.knock.app) using the `@clipboard-health/notifications` NPM library.
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
<embedex source="packages/notifications/examples/usage.md">
|
|
23
|
-
|
|
24
|
-
1. Search your service for a `NotificationJobEnqueuer` instance. If there isn't one, create and export it:
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
|
|
28
|
-
|
|
29
|
-
import { BackgroundJobsService } from "./setup";
|
|
30
|
-
|
|
31
|
-
// Create and export one instance of this in your microservice.
|
|
32
|
-
export const notificationJobEnqueuer = new NotificationJobEnqueuer({
|
|
33
|
-
// Use your instance of `@clipboard-health/mongo-jobs` or `@clipboard-health/background-jobs-postgres` here.
|
|
34
|
-
adapter: new BackgroundJobsService(),
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
1. Implement a minimal job, calling off to a NestJS service for any business logic and to send the notification.
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
|
|
42
|
-
import { type NotificationData } from "@clipboard-health/notifications";
|
|
43
|
-
import { isFailure, toError } from "@clipboard-health/util-ts";
|
|
44
|
-
|
|
45
|
-
import { type ExampleNotificationService } from "./exampleNotification.service";
|
|
46
|
-
|
|
47
|
-
export type ExampleNotificationData = NotificationData<{
|
|
48
|
-
workplaceId: string;
|
|
49
|
-
}>;
|
|
50
|
-
|
|
51
|
-
export const EXAMPLE_NOTIFICATION_JOB_NAME = "ExampleNotificationJob";
|
|
52
|
-
|
|
53
|
-
// For mongo-jobs, you'll implement HandlerInterface<ExampleNotificationData["Job"]>
|
|
54
|
-
// For background-jobs-postgres, you'll implement Handler<ExampleNotificationData["Job"]>
|
|
55
|
-
export class ExampleNotificationJob implements BaseHandler<ExampleNotificationData["Job"]> {
|
|
56
|
-
public name = EXAMPLE_NOTIFICATION_JOB_NAME;
|
|
57
|
-
|
|
58
|
-
constructor(private readonly service: ExampleNotificationService) {}
|
|
59
|
-
|
|
60
|
-
async perform(data: ExampleNotificationData["Job"], job: { attemptsCount: number }) {
|
|
61
|
-
const result = await this.service.sendNotification({
|
|
62
|
-
...data,
|
|
63
|
-
// Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
|
|
64
|
-
attempt: job.attemptsCount + 1,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (isFailure(result)) {
|
|
68
|
-
throw toError(result.error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
1. Search your service for a constant that stores workflow keys. If there isn't one, create and export it:
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
export const WORKFLOW_KEYS = {
|
|
78
|
-
eventStartingReminder: "event-starting-reminder",
|
|
79
|
-
} as const;
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
1. Enqueue your job:
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
import {
|
|
86
|
-
EXAMPLE_NOTIFICATION_JOB_NAME,
|
|
87
|
-
type ExampleNotificationData,
|
|
88
|
-
} from "./exampleNotification.job";
|
|
89
|
-
import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
|
|
90
|
-
import { WORKFLOW_KEYS } from "./workflowKeys";
|
|
91
|
-
|
|
92
|
-
async function enqueueNotificationJob() {
|
|
93
|
-
await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationData["Enqueue"]>(
|
|
94
|
-
EXAMPLE_NOTIFICATION_JOB_NAME,
|
|
95
|
-
// Important: Read the TypeDoc documentation for additional context.
|
|
96
|
-
{
|
|
97
|
-
/**
|
|
98
|
-
* Set expiresAt at enqueue-time so it remains stable across job retries. Use date-fns in your
|
|
99
|
-
* service instead of this manual calculation.
|
|
100
|
-
*/
|
|
101
|
-
expiresAt: new Date(Date.now() + 60 * 60_000).toISOString(),
|
|
102
|
-
// Set idempotencyKey at enqueue-time so it remains stable across job retries.
|
|
103
|
-
idempotencyKey: {
|
|
104
|
-
resourceId: "event-123",
|
|
105
|
-
},
|
|
106
|
-
// Set recipients at enqueue-time so they respect our notification provider's limits.
|
|
107
|
-
recipients: ["userId-1"],
|
|
108
|
-
|
|
109
|
-
workflowKey: WORKFLOW_KEYS.eventStartingReminder,
|
|
110
|
-
|
|
111
|
-
// Any additional enqueue-time data passed to the job:
|
|
112
|
-
workplaceId: "workplaceId-123",
|
|
113
|
-
},
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
118
|
-
void enqueueNotificationJob();
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
1. Trigger the job in your NestJS service:
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
import { type NotificationClient } from "@clipboard-health/notifications";
|
|
125
|
-
|
|
126
|
-
import { type ExampleNotificationData } from "./exampleNotification.job";
|
|
127
|
-
|
|
128
|
-
type ExampleNotificationDo = ExampleNotificationData["Job"] & { attempt: number };
|
|
129
|
-
|
|
130
|
-
export class ExampleNotificationService {
|
|
131
|
-
constructor(private readonly client: NotificationClient) {}
|
|
132
|
-
|
|
133
|
-
async sendNotification(params: ExampleNotificationDo) {
|
|
134
|
-
const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
|
|
135
|
-
|
|
136
|
-
// Assume this comes from a database and are used as template variables...
|
|
137
|
-
// Use @clipboard-health/date-time's formatShortDateTime in your service for consistency.
|
|
138
|
-
const data = { favoriteColor: "blue", favoriteAt: new Date().toISOString(), secret: "2" };
|
|
139
|
-
|
|
140
|
-
// Important: Read the TypeDoc documentation for additional context.
|
|
141
|
-
return await this.client.trigger({
|
|
142
|
-
attempt,
|
|
143
|
-
body: {
|
|
144
|
-
data,
|
|
145
|
-
recipients,
|
|
146
|
-
workplaceId,
|
|
147
|
-
},
|
|
148
|
-
expiresAt: new Date(expiresAt),
|
|
149
|
-
idempotencyKey,
|
|
150
|
-
keysToRedact: ["secret"],
|
|
151
|
-
workflowKey,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
</embedex>
|
|
158
|
-
|
|
159
|
-
<!-- Source: .ruler/common/codeStyleAndStructure.md -->
|
|
160
|
-
|
|
161
|
-
# Code style and structure
|
|
162
|
-
|
|
163
|
-
- Write concise, technical TypeScript code with accurate examples.
|
|
164
|
-
- Use functional and declarative programming patterns.
|
|
165
|
-
- Prefer iteration and modularization over code duplication.
|
|
166
|
-
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
|
|
167
|
-
- Structure files: constants, types, exported functions, non-exported functions.
|
|
168
|
-
- Avoid magic strings and numbers; define constants.
|
|
169
|
-
- Use camelCase for files and directories (e.g., modules/shiftOffers.ts).
|
|
170
|
-
- When declaring functions, use the `function` keyword, not `const`.
|
|
171
|
-
- Files should read from top to bottom: `export`ed items live on top and the internal functions and methods they call go below them.
|
|
172
|
-
- Prefer data immutability.
|
|
173
|
-
|
|
174
|
-
# Commit messages
|
|
175
|
-
|
|
176
|
-
- Follow the Conventional Commits 1.0 spec for commit messages and in pull request titles.
|
|
177
|
-
|
|
178
|
-
<!-- Source: .ruler/common/errorHandlingAndValidation.md -->
|
|
179
|
-
|
|
180
|
-
# Error handling and validation
|
|
181
|
-
|
|
182
|
-
- Sanitize user input.
|
|
183
|
-
- Handle errors and edge cases at the beginning of functions.
|
|
184
|
-
- Use early returns for error conditions to avoid deeply nested if statements.
|
|
185
|
-
- Place the happy path last in the function for improved readability.
|
|
186
|
-
- Avoid unnecessary else statements; use the if-return pattern instead.
|
|
187
|
-
- Use guard clauses to handle preconditions and invalid states early.
|
|
188
|
-
- Implement proper error logging and user-friendly error messages.
|
|
189
|
-
- Favor `@clipboard-health/util-ts`'s `Either` type for expected errors instead of `try`/`catch`.
|
|
190
|
-
|
|
191
|
-
<!-- Source: .ruler/common/testing.md -->
|
|
192
|
-
|
|
193
|
-
# Testing
|
|
194
|
-
|
|
195
|
-
- Follow the Arrange-Act-Assert convention for tests with newlines between each section.
|
|
196
|
-
- Name test variables using the `mockX`, `input`, `expected`, `actual` convention.
|
|
197
|
-
- Aim for high test coverage, writing both positive and negative test cases.
|
|
198
|
-
- Prefer `it.each` for multiple test cases.
|
|
199
|
-
- Avoid conditional logic in tests.
|
|
200
|
-
|
|
201
|
-
<!-- Source: .ruler/common/typeScript.md -->
|
|
202
|
-
|
|
203
|
-
# TypeScript usage
|
|
204
|
-
|
|
205
|
-
- Use strict-mode TypeScript for all code; prefer interfaces over types.
|
|
206
|
-
- Avoid enums; use const maps instead.
|
|
207
|
-
- Strive for precise types. Look for type definitions in the codebase and create your own if none exist.
|
|
208
|
-
- Avoid using type assertions like `as` or `!` unless absolutely necessary.
|
|
209
|
-
- Use the `unknown` type instead of `any` when the type is truly unknown.
|
|
210
|
-
- Use an object to pass multiple function params and to return results.
|
|
211
|
-
- Leverage union types, intersection types, and conditional types for complex type definitions.
|
|
212
|
-
- Use mapped types and utility types (e.g., `Partial<T>`, `Pick<T>`, `Omit<T>`) to transform existing types.
|
|
213
|
-
- Implement generic types to create reusable, flexible type definitions.
|
|
214
|
-
- Utilize the `keyof` operator and index access types for dynamic property access.
|
|
215
|
-
- Implement discriminated unions for type-safe handling of different object shapes where appropriate.
|
|
216
|
-
- Use the `infer` keyword in conditional types for type inference.
|
|
217
|
-
- Leverage `readonly` properties for function parameter immutability.
|
|
218
|
-
- Prefer narrow types whenever possible with `as const` assertions, `typeof`, `instanceof`, `satisfies`, and custom type guards.
|
|
219
|
-
- Implement exhaustiveness checking using `never`.
|
|
1
|
+
@AGENTS.md
|
package/common/CLAUDE.md
CHANGED
|
@@ -1,61 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# Code style and structure
|
|
4
|
-
|
|
5
|
-
- Write concise, technical TypeScript code with accurate examples.
|
|
6
|
-
- Use functional and declarative programming patterns.
|
|
7
|
-
- Prefer iteration and modularization over code duplication.
|
|
8
|
-
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
|
|
9
|
-
- Structure files: constants, types, exported functions, non-exported functions.
|
|
10
|
-
- Avoid magic strings and numbers; define constants.
|
|
11
|
-
- Use camelCase for files and directories (e.g., modules/shiftOffers.ts).
|
|
12
|
-
- When declaring functions, use the `function` keyword, not `const`.
|
|
13
|
-
- Files should read from top to bottom: `export`ed items live on top and the internal functions and methods they call go below them.
|
|
14
|
-
- Prefer data immutability.
|
|
15
|
-
|
|
16
|
-
# Commit messages
|
|
17
|
-
|
|
18
|
-
- Follow the Conventional Commits 1.0 spec for commit messages and in pull request titles.
|
|
19
|
-
|
|
20
|
-
<!-- Source: .ruler/common/errorHandlingAndValidation.md -->
|
|
21
|
-
|
|
22
|
-
# Error handling and validation
|
|
23
|
-
|
|
24
|
-
- Sanitize user input.
|
|
25
|
-
- Handle errors and edge cases at the beginning of functions.
|
|
26
|
-
- Use early returns for error conditions to avoid deeply nested if statements.
|
|
27
|
-
- Place the happy path last in the function for improved readability.
|
|
28
|
-
- Avoid unnecessary else statements; use the if-return pattern instead.
|
|
29
|
-
- Use guard clauses to handle preconditions and invalid states early.
|
|
30
|
-
- Implement proper error logging and user-friendly error messages.
|
|
31
|
-
- Favor `@clipboard-health/util-ts`'s `Either` type for expected errors instead of `try`/`catch`.
|
|
32
|
-
|
|
33
|
-
<!-- Source: .ruler/common/testing.md -->
|
|
34
|
-
|
|
35
|
-
# Testing
|
|
36
|
-
|
|
37
|
-
- Follow the Arrange-Act-Assert convention for tests with newlines between each section.
|
|
38
|
-
- Name test variables using the `mockX`, `input`, `expected`, `actual` convention.
|
|
39
|
-
- Aim for high test coverage, writing both positive and negative test cases.
|
|
40
|
-
- Prefer `it.each` for multiple test cases.
|
|
41
|
-
- Avoid conditional logic in tests.
|
|
42
|
-
|
|
43
|
-
<!-- Source: .ruler/common/typeScript.md -->
|
|
44
|
-
|
|
45
|
-
# TypeScript usage
|
|
46
|
-
|
|
47
|
-
- Use strict-mode TypeScript for all code; prefer interfaces over types.
|
|
48
|
-
- Avoid enums; use const maps instead.
|
|
49
|
-
- Strive for precise types. Look for type definitions in the codebase and create your own if none exist.
|
|
50
|
-
- Avoid using type assertions like `as` or `!` unless absolutely necessary.
|
|
51
|
-
- Use the `unknown` type instead of `any` when the type is truly unknown.
|
|
52
|
-
- Use an object to pass multiple function params and to return results.
|
|
53
|
-
- Leverage union types, intersection types, and conditional types for complex type definitions.
|
|
54
|
-
- Use mapped types and utility types (e.g., `Partial<T>`, `Pick<T>`, `Omit<T>`) to transform existing types.
|
|
55
|
-
- Implement generic types to create reusable, flexible type definitions.
|
|
56
|
-
- Utilize the `keyof` operator and index access types for dynamic property access.
|
|
57
|
-
- Implement discriminated unions for type-safe handling of different object shapes where appropriate.
|
|
58
|
-
- Use the `infer` keyword in conditional types for type inference.
|
|
59
|
-
- Leverage `readonly` properties for function parameter immutability.
|
|
60
|
-
- Prefer narrow types whenever possible with `as const` assertions, `typeof`, `instanceof`, `satisfies`, and custom type guards.
|
|
61
|
-
- Implement exhaustiveness checking using `never`.
|
|
1
|
+
@AGENTS.md
|