@clipboard-health/ai-rules 1.6.43 → 1.7.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.
- package/backend/AGENTS.md +659 -258
- package/common/AGENTS.md +250 -46
- package/frontend/AGENTS.md +481 -791
- package/fullstack/AGENTS.md +838 -951
- package/package.json +1 -1
package/common/AGENTS.md
CHANGED
|
@@ -1,63 +1,267 @@
|
|
|
1
1
|
<!-- Generated by Ruler -->
|
|
2
2
|
|
|
3
|
-
<!-- Source: .ruler/common/
|
|
3
|
+
<!-- Source: .ruler/common/commitMessages.md -->
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# Commit Messages
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Use functional and declarative programming patterns.
|
|
9
|
-
- Prefer iteration and modularization over code duplication.
|
|
10
|
-
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
|
|
11
|
-
- Structure files: constants, types, exported functions, non-exported functions.
|
|
12
|
-
- Avoid magic strings and numbers; define constants.
|
|
13
|
-
- Use camelCase for files and directories (e.g., modules/shiftOffers.ts).
|
|
14
|
-
- When declaring functions, use the `function` keyword, not `const`.
|
|
15
|
-
- Files should read from top to bottom: `export`ed items live on top and the internal functions and methods they call go below them.
|
|
16
|
-
- Prefer data immutability.
|
|
7
|
+
Follow Conventional Commits 1.0 spec for commit messages and PR titles.
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
<!-- Source: .ruler/common/configuration.md -->
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
# Configuration
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
```text
|
|
14
|
+
Contains secrets?
|
|
15
|
+
└── Yes → SSM Parameter Store
|
|
16
|
+
└── No → Engineers-only, tolerate 1hr propagation?
|
|
17
|
+
└── Yes → Hardcode with @clipboard-health/config
|
|
18
|
+
└── No → 1:1 with DB entity OR needs custom UI?
|
|
19
|
+
└── Yes → Database
|
|
20
|
+
└── No → LaunchDarkly feature flag
|
|
21
|
+
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
<!-- Source: .ruler/common/featureFlags.md -->
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
# Feature Flags
|
|
26
|
+
|
|
27
|
+
**Naming:** `YYYY-MM-[kind]-[subject]` (e.g., `2024-03-release-new-booking-flow`)
|
|
28
|
+
|
|
29
|
+
| Kind | Purpose |
|
|
30
|
+
| ------------ | ------------------------ |
|
|
31
|
+
| `release` | Gradual rollout to 100% |
|
|
32
|
+
| `enable` | Kill switch |
|
|
33
|
+
| `experiment` | Trial for small audience |
|
|
34
|
+
| `configure` | Runtime config |
|
|
35
|
+
|
|
36
|
+
**Rules:**
|
|
37
|
+
|
|
38
|
+
- "Off" = default/safer value
|
|
39
|
+
- No permanent flags (except `configure`)
|
|
40
|
+
- Create archival ticket when creating flag
|
|
41
|
+
- Validate staging before production
|
|
42
|
+
- Always provide default values in code
|
|
43
|
+
- Clean up after full launch
|
|
44
|
+
|
|
45
|
+
<!-- Source: .ruler/common/loggingObservability.md -->
|
|
46
|
+
|
|
47
|
+
# Logging & Observability
|
|
48
|
+
|
|
49
|
+
## Log Levels
|
|
50
|
+
|
|
51
|
+
| Level | When |
|
|
52
|
+
| ----- | ------------------------------------------ |
|
|
53
|
+
| ERROR | Required functionality broken (2am pager?) |
|
|
54
|
+
| WARN | Optional broken OR recovered from failure |
|
|
55
|
+
| INFO | Informative, ignorable during normal ops |
|
|
56
|
+
| DEBUG | Local only, not production |
|
|
57
|
+
|
|
58
|
+
## Best Practices
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Bad
|
|
62
|
+
logger.error("Operation failed");
|
|
63
|
+
logger.error(`Operation failed for workplace ${workplaceId}`);
|
|
64
|
+
|
|
65
|
+
// Good—structured context
|
|
66
|
+
logger.error("Exporting urgent shifts to CSV failed", {
|
|
67
|
+
workplaceId,
|
|
68
|
+
startDate,
|
|
69
|
+
endDate,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Never log:** PII, PHI, tokens, secrets, SSN, account numbers, entire request/response/headers.
|
|
74
|
+
|
|
75
|
+
Use metrics for counting:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
datadogMetrics.increment("negotiation.errors", { state: "New York" });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
<!-- Source: .ruler/common/pullRequests.md -->
|
|
82
|
+
|
|
83
|
+
# Pull Requests
|
|
84
|
+
|
|
85
|
+
**Requirements:**
|
|
86
|
+
|
|
87
|
+
1. Clear title: change summary + ticket; Conventional Commits 1.0
|
|
88
|
+
2. Thorough description: why, not just what
|
|
89
|
+
3. Small & focused: single concept
|
|
90
|
+
4. Tested: service tests + validation proof
|
|
91
|
+
5. Passing CI
|
|
92
|
+
|
|
93
|
+
**Description:** Link ticket, context, reasoning, areas of concern.
|
|
94
|
+
|
|
95
|
+
<!-- Source: .ruler/common/security.md -->
|
|
96
|
+
|
|
97
|
+
# Security
|
|
98
|
+
|
|
99
|
+
**Secrets:**
|
|
100
|
+
|
|
101
|
+
- `.env` locally (gitignored)
|
|
102
|
+
- Production: AWS SSM Parameter Store
|
|
103
|
+
- Prefer short-lived tokens
|
|
104
|
+
|
|
105
|
+
**Naming:** `[ENV]_[VENDOR]_[TYPE]_usedBy_[CLIENT]_[SCOPE]_[CREATED_AT]_[OWNER]`
|
|
106
|
+
|
|
107
|
+
<!-- Source: .ruler/common/structuredConcurrency.md -->
|
|
108
|
+
|
|
109
|
+
# Structured Concurrency
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Cancellation propagation
|
|
113
|
+
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
116
|
+
try {
|
|
117
|
+
return await fetch(url, { signal: controller.signal });
|
|
118
|
+
} finally {
|
|
119
|
+
clearTimeout(timeoutId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// All results regardless of failures
|
|
124
|
+
const results = await Promise.allSettled(operations);
|
|
125
|
+
const succeeded = results.filter((r) => r.status === "fulfilled");
|
|
126
|
+
const failed = results.filter((r) => r.status === "rejected");
|
|
127
|
+
```
|
|
34
128
|
|
|
35
129
|
<!-- Source: .ruler/common/testing.md -->
|
|
36
130
|
|
|
37
131
|
# Testing
|
|
38
132
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
133
|
+
## Unit Tests
|
|
134
|
+
|
|
135
|
+
Use when: error handling hard to trigger black-box, concurrency scenarios, >5 variations, pure function logic.
|
|
136
|
+
|
|
137
|
+
## Conventions
|
|
138
|
+
|
|
139
|
+
- Use `it` not `test`; `describe` for grouping
|
|
140
|
+
- Arrange-Act-Assert with newlines between
|
|
141
|
+
- Variables: `mockX`, `input`, `expected`, `actual`
|
|
142
|
+
- Prefer `it.each` for multiple cases
|
|
143
|
+
- No conditional logic in tests
|
|
44
144
|
|
|
45
145
|
<!-- Source: .ruler/common/typeScript.md -->
|
|
46
146
|
|
|
47
|
-
# TypeScript
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
147
|
+
# TypeScript
|
|
148
|
+
|
|
149
|
+
## Naming Conventions
|
|
150
|
+
|
|
151
|
+
| Element | Convention | Example |
|
|
152
|
+
| --------------------- | --------------------- | ---------------------------- |
|
|
153
|
+
| File-scope constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
154
|
+
| Acronyms in camelCase | Lowercase after first | `httpRequest`, `gpsPosition` |
|
|
155
|
+
| Files | Singular, dotted | `user.service.ts` |
|
|
156
|
+
|
|
157
|
+
## Core Rules
|
|
158
|
+
|
|
159
|
+
- Strict-mode TypeScript; prefer interfaces over types
|
|
160
|
+
- Avoid enums—use const maps
|
|
161
|
+
- NEVER use `any`—use `unknown` or generics
|
|
162
|
+
- Avoid type assertions (`as`, `!`) unless absolutely necessary
|
|
163
|
+
- Use `function` keyword for declarations, not `const`
|
|
164
|
+
- Prefer `undefined` over `null`
|
|
165
|
+
- Explicit return types on functions
|
|
166
|
+
- Files read top-to-bottom: exports first, internal helpers below
|
|
167
|
+
- Boolean props: `is*`, `has*`, `should*`, `can*`
|
|
168
|
+
- Use const assertions for constants: `as const`
|
|
169
|
+
|
|
170
|
+
## Types
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Strong typing
|
|
174
|
+
function process(arg: unknown) {} // Better than any
|
|
175
|
+
function process<T>(arg: T) {} // Best
|
|
176
|
+
|
|
177
|
+
// Nullable checks
|
|
178
|
+
if (foo == null) {
|
|
179
|
+
} // Clear intent
|
|
180
|
+
if (isDefined(foo)) {
|
|
181
|
+
} // Better with utility
|
|
182
|
+
|
|
183
|
+
// Quantity values—always unambiguous
|
|
184
|
+
const money = { amountInMinorUnits: 500, currencyCode: "USD" };
|
|
185
|
+
const durationMinutes = 30;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Type Techniques:**
|
|
189
|
+
|
|
190
|
+
- Union, intersection, conditional types for complex definitions
|
|
191
|
+
- Mapped types: `Partial<T>`, `Pick<T>`, `Omit<T>`
|
|
192
|
+
- `keyof`, index access types, discriminated unions
|
|
193
|
+
- `as const`, `typeof`, `instanceof`, `satisfies`, type guards
|
|
194
|
+
- Exhaustiveness checking with `never`
|
|
195
|
+
- `readonly` for parameter immutability
|
|
196
|
+
|
|
197
|
+
## Functions
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Object arguments with interfaces
|
|
201
|
+
interface CreateUserRequest {
|
|
202
|
+
email: string;
|
|
203
|
+
name?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function createUser(request: CreateUserRequest): User {
|
|
207
|
+
const { email, name } = request; // Destructure inside
|
|
208
|
+
// ...
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Guard clauses for early returns
|
|
212
|
+
function processOrder(order: Order): Result {
|
|
213
|
+
if (!order.isValid) {
|
|
214
|
+
return { error: "Invalid order" };
|
|
215
|
+
}
|
|
216
|
+
// Main logic
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Objects & Arrays
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Spread over Object.assign
|
|
224
|
+
const updated = { ...original, name: "New Name" };
|
|
225
|
+
|
|
226
|
+
// Array methods over loops (unless breaking early)
|
|
227
|
+
const doubled = items.map((item) => item * 2);
|
|
228
|
+
const sorted = items.toSorted((a, b) => a - b); // Immutable
|
|
229
|
+
|
|
230
|
+
// For early exit
|
|
231
|
+
for (const item of items) {
|
|
232
|
+
if (condition) break;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Async
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// async/await over .then()
|
|
240
|
+
async function fetchData(): Promise<Data> {
|
|
241
|
+
const response = await api.get("/data");
|
|
242
|
+
return response.data;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Parallel
|
|
246
|
+
const results = await Promise.all(items.map(processItem));
|
|
247
|
+
|
|
248
|
+
// Sequential (when needed)
|
|
249
|
+
for (const item of items) {
|
|
250
|
+
// eslint-disable-next-line no-await-in-loop
|
|
251
|
+
await processItem(item);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Classes
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
class UserService {
|
|
259
|
+
public async findById(request: FindByIdRequest): Promise<User> {}
|
|
260
|
+
private validateUser(user: User): boolean {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Extract pure functions outside classes
|
|
264
|
+
function formatUserName(first: string, last: string): string {
|
|
265
|
+
return `${first} ${last}`;
|
|
266
|
+
}
|
|
267
|
+
```
|