@baldurpan/create-ai-workflow 0.2.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/agents-block.js +70 -0
- package/dist/bin.js +14 -0
- package/dist/check/markdown.js +47 -0
- package/dist/check/parse.js +113 -0
- package/dist/check/rules.js +245 -0
- package/dist/cli.js +109 -0
- package/dist/commands/check.js +41 -0
- package/dist/commands/install.js +76 -0
- package/dist/commands/standards-add.js +145 -0
- package/dist/commands/update.js +214 -0
- package/dist/layout.js +115 -0
- package/dist/log.js +20 -0
- package/dist/manifest.js +53 -0
- package/dist/paths.js +41 -0
- package/package.json +37 -0
- package/templates/blocks/agents-block.md +30 -0
- package/templates/claude/agents/planner.agent.md +60 -0
- package/templates/claude/agents/reviewer.agent.md +77 -0
- package/templates/context/README.md +81 -0
- package/templates/context/plan-template.md +69 -0
- package/templates/context/plan-template.notes.md +55 -0
- package/templates/context/roles/coder.md +76 -0
- package/templates/context/workflow.md +143 -0
- package/templates/skills/feature-close/SKILL.md +102 -0
- package/templates/skills/feature-implement/SKILL.md +142 -0
- package/templates/skills/feature-plan/SKILL.md +138 -0
- package/templates/skills/feature-status/SKILL.md +86 -0
- package/templates/skills/onboard/SKILL.md +122 -0
- package/templates/skills/orchestrate/SKILL.md +92 -0
- package/templates/skills/roadmap/SKILL.md +97 -0
- package/templates/standards/.source +8 -0
- package/templates/standards/README.md +104 -0
- package/templates/standards/architecture/api-design.md +212 -0
- package/templates/standards/architecture/dependency-boundaries.md +104 -0
- package/templates/standards/architecture/feature-driven.md +102 -0
- package/templates/standards/architecture/folder-structure.md +108 -0
- package/templates/standards/architecture/monorepos.md +107 -0
- package/templates/standards/architecture/refactoring.md +97 -0
- package/templates/standards/architecture/shared-code.md +81 -0
- package/templates/standards/docs/PHP-SPEC.md +1195 -0
- package/templates/standards/docs/SPEC.md +540 -0
- package/templates/standards/examples/bad/data-fetching.tsx +52 -0
- package/templates/standards/examples/bad/derived-state.tsx +60 -0
- package/templates/standards/examples/bad/feature-component.tsx +80 -0
- package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
- package/templates/standards/examples/good/data-fetching.tsx +53 -0
- package/templates/standards/examples/good/derived-state.tsx +52 -0
- package/templates/standards/examples/good/feature-component.tsx +141 -0
- package/templates/standards/examples/good/invoice.schema.ts +20 -0
- package/templates/standards/examples/good/prop-drilling.tsx +83 -0
- package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
- package/templates/standards/philosophy/core-principles.md +49 -0
- package/templates/standards/philosophy/incremental-abstraction.md +56 -0
- package/templates/standards/philosophy/maintainability.md +66 -0
- package/templates/standards/philosophy/readability.md +87 -0
- package/templates/standards/php/README.md +62 -0
- package/templates/standards/php/anti-patterns.md +259 -0
- package/templates/standards/php/architecture.md +241 -0
- package/templates/standards/php/frameworks.md +197 -0
- package/templates/standards/php/rules.md +205 -0
- package/templates/standards/php/security.md +190 -0
- package/templates/standards/php/testing.md +139 -0
- package/templates/standards/php/tooling.md +245 -0
- package/templates/standards/react/accessibility.md +130 -0
- package/templates/standards/react/anti-patterns.md +187 -0
- package/templates/standards/react/component-design.md +139 -0
- package/templates/standards/react/error-boundaries.md +195 -0
- package/templates/standards/react/forms.md +147 -0
- package/templates/standards/react/hooks.md +88 -0
- package/templates/standards/react/memoization.md +87 -0
- package/templates/standards/react/state-management.md +123 -0
- package/templates/standards/react/testing.md +118 -0
- package/templates/standards/react/use-effect.md +110 -0
- package/templates/standards/security/api-security.md +136 -0
- package/templates/standards/security/auth.md +112 -0
- package/templates/standards/security/secrets.md +105 -0
- package/templates/standards/security/validation.md +89 -0
- package/templates/standards/templates/.editorconfig +16 -0
- package/templates/standards/templates/.nvmrc +1 -0
- package/templates/standards/templates/AGENTS.md +46 -0
- package/templates/standards/templates/CLAUDE.md +3 -0
- package/templates/standards/templates/_dot_gitignore +50 -0
- package/templates/standards/templates/biome.json +53 -0
- package/templates/standards/templates/eslint.config.js +61 -0
- package/templates/standards/templates/project-readme.md +76 -0
- package/templates/standards/templates/pull-request-template.md +46 -0
- package/templates/standards/templates/tsconfig.json +35 -0
- package/templates/standards/tooling/biome.md +92 -0
- package/templates/standards/tooling/ci.md +128 -0
- package/templates/standards/tooling/dates.md +194 -0
- package/templates/standards/tooling/dependencies.md +90 -0
- package/templates/standards/tooling/nx.md +113 -0
- package/templates/standards/tooling/observability.md +183 -0
- package/templates/standards/tooling/prisma.md +152 -0
- package/templates/standards/tooling/shadcn.md +115 -0
- package/templates/standards/tooling/tailwind.md +135 -0
- package/templates/standards/tooling/tanstack-query.md +214 -0
- package/templates/standards/tooling/tanstack-router.md +365 -0
- package/templates/standards/tooling/tanstack.md +31 -0
- package/templates/standards/tooling/vite.md +135 -0
- package/templates/standards/typescript/anti-patterns.md +107 -0
- package/templates/standards/typescript/error-handling.md +216 -0
- package/templates/standards/typescript/naming.md +78 -0
- package/templates/standards/typescript/rules.md +159 -0
- package/templates/standards/typescript/tsconfig/base.json +32 -0
- package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
- package/templates/standards/typescript/tsconfig/react.json +9 -0
- package/templates/standards/typescript/validation.md +104 -0
- package/templates/stubs/executors.md +49 -0
- package/templates/stubs/findings.md +40 -0
- package/templates/stubs/history.md +13 -0
- package/templates/stubs/roadmap.md +29 -0
- package/templates/stubs/stack.md +35 -0
- package/templates/stubs/verify.md +48 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- Never swallow errors silently
|
|
6
|
+
- Never hide the original error context
|
|
7
|
+
- Never replace a specific error with a generic one
|
|
8
|
+
- Preserve HTTP status codes and original payloads
|
|
9
|
+
- Validate inputs before state transitions
|
|
10
|
+
|
|
11
|
+
## DO
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// Preserve original error context
|
|
15
|
+
try {
|
|
16
|
+
await db.user.create({ data });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new DatabaseError("Failed to create user", { cause: error });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Use custom error classes for categorized errors
|
|
22
|
+
class NotFoundError extends Error {
|
|
23
|
+
readonly statusCode = 404;
|
|
24
|
+
constructor(resource: string, id: string) {
|
|
25
|
+
super(`${resource} with id '${id}' not found`);
|
|
26
|
+
this.name = "NotFoundError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Log with context, not just a message
|
|
31
|
+
logger.error("Invoice creation failed", {
|
|
32
|
+
userId,
|
|
33
|
+
invoiceData,
|
|
34
|
+
error: err.message,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## DO NOT
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// BAD — swallows the error
|
|
42
|
+
try {
|
|
43
|
+
await riskyOperation();
|
|
44
|
+
} catch {
|
|
45
|
+
// silent
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// BAD — loses original context
|
|
49
|
+
try {
|
|
50
|
+
await riskyOperation();
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error("Something went wrong");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// BAD — catches too broadly
|
|
56
|
+
try {
|
|
57
|
+
const user = await fetchUser(id);
|
|
58
|
+
const profile = await fetchProfile(user.profileId);
|
|
59
|
+
const invoices = await fetchInvoices(user.id);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// which operation failed? unknown
|
|
62
|
+
console.error(error);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Async Rules
|
|
67
|
+
|
|
68
|
+
### Prefer async/await Over .then() Chains
|
|
69
|
+
|
|
70
|
+
For sequential async flow, `async/await` is far more readable than chained `.then().catch()`. Reserve `.then()` / `.catch()` for fire-and-forget side effects.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// GOOD — linear, easy to follow, errors propagate naturally
|
|
74
|
+
async function loadDashboard(userId: string) {
|
|
75
|
+
const user = await fetchUser(userId);
|
|
76
|
+
const profile = await fetchProfile(user.profileId);
|
|
77
|
+
const invoices = await fetchInvoices(user.id);
|
|
78
|
+
return { user, profile, invoices };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// BAD — nested .then chains, harder to read and reason about
|
|
82
|
+
function loadDashboard(userId: string) {
|
|
83
|
+
return fetchUser(userId)
|
|
84
|
+
.then((user) =>
|
|
85
|
+
fetchProfile(user.profileId).then((profile) =>
|
|
86
|
+
fetchInvoices(user.id).then((invoices) => ({ user, profile, invoices })),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
.catch((error) => {
|
|
90
|
+
// which step failed? unclear
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Always Await or Return
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// GOOD
|
|
99
|
+
async function loadUser(id: string) {
|
|
100
|
+
const user = await fetchUser(id);
|
|
101
|
+
return user;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// BAD — floating promise, errors lost
|
|
105
|
+
async function loadUser(id: string) {
|
|
106
|
+
fetchUser(id); // unawaited
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Fire-and-Forget Operations
|
|
111
|
+
|
|
112
|
+
`.catch()` is appropriate when you intentionally don't await:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
sendAnalyticsEvent(payload).catch((error) => {
|
|
116
|
+
logger.warn("Analytics event failed", { error });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// If you genuinely don't care about errors, mark intent with `void`
|
|
120
|
+
void sendAnalyticsEvent(payload);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Parallel Operations
|
|
124
|
+
|
|
125
|
+
Run independent async operations in parallel with `Promise.all`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// GOOD — parallel, faster
|
|
129
|
+
const [user, settings, invoices] = await Promise.all([
|
|
130
|
+
fetchUser(userId),
|
|
131
|
+
fetchSettings(userId),
|
|
132
|
+
fetchInvoices(userId),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// BAD — sequential when there's no dependency between calls
|
|
136
|
+
const user = await fetchUser(userId);
|
|
137
|
+
const settings = await fetchSettings(userId);
|
|
138
|
+
const invoices = await fetchInvoices(userId);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use `Promise.allSettled` when some failures are acceptable and you want all results regardless.
|
|
142
|
+
|
|
143
|
+
## Custom Error Classes
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
class AppError extends Error {
|
|
147
|
+
constructor(
|
|
148
|
+
message: string,
|
|
149
|
+
readonly statusCode: number,
|
|
150
|
+
options?: ErrorOptions,
|
|
151
|
+
) {
|
|
152
|
+
super(message, options);
|
|
153
|
+
this.name = this.constructor.name;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class ValidationError extends AppError {
|
|
158
|
+
constructor(message: string, readonly fields: Record<string, string[]>) {
|
|
159
|
+
super(message, 422);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class NotFoundError extends AppError {
|
|
164
|
+
constructor(resource: string, id: string) {
|
|
165
|
+
super(`${resource} '${id}' not found`, 404);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## User-Facing Errors
|
|
171
|
+
|
|
172
|
+
Every user-facing error must:
|
|
173
|
+
|
|
174
|
+
1. **Explain the failure** — what went wrong, specifically
|
|
175
|
+
2. **Explain the next step** — what the user should do
|
|
176
|
+
3. **Be actionable** — give the user something they can do
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// BAD
|
|
180
|
+
"Something went wrong. Please try again."
|
|
181
|
+
|
|
182
|
+
// GOOD
|
|
183
|
+
"We couldn't save your invoice. Check your internet connection and try again.
|
|
184
|
+
If the problem continues, contact support with reference #INV-1234."
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Result Pattern (Optional)
|
|
188
|
+
|
|
189
|
+
For operations where errors are expected and part of normal flow:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
type Result<T, E = Error> =
|
|
193
|
+
| { ok: true; value: T }
|
|
194
|
+
| { ok: false; error: E };
|
|
195
|
+
|
|
196
|
+
async function parseUser(raw: unknown): Promise<Result<User>> {
|
|
197
|
+
const result = userSchema.safeParse(raw);
|
|
198
|
+
if (!result.success) {
|
|
199
|
+
return { ok: false, error: new ValidationError(result.error.message) };
|
|
200
|
+
}
|
|
201
|
+
return { ok: true, value: result.data };
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## PRIORITY
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
Preserve context > Specific messages > Actionable user guidance
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## See Also
|
|
212
|
+
|
|
213
|
+
- [`../react/error-boundaries.md`](../react/error-boundaries.md) — catching render-time errors
|
|
214
|
+
- [`../tooling/observability.md`](../tooling/observability.md) — structured logging, error monitoring
|
|
215
|
+
- [`../security/api-security.md`](../security/api-security.md) — safe error responses
|
|
216
|
+
- [`validation.md`](validation.md) — preventing errors via input validation
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Naming Conventions
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Names should express intent clearly without requiring the reader to trace the code.
|
|
6
|
+
|
|
7
|
+
## General Rules
|
|
8
|
+
|
|
9
|
+
| Pattern | Convention | Example |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Variables | `camelCase` | `userProfile`, `isLoading` |
|
|
12
|
+
| Functions | `camelCase`, verb-first | `fetchUser`, `formatCurrency` |
|
|
13
|
+
| Types / Interfaces | `PascalCase` | `UserProfile`, `InvoiceLineItem` |
|
|
14
|
+
| Components | `PascalCase` | `InvoiceTable`, `UserAvatarGroup` |
|
|
15
|
+
| Hooks | `camelCase`, `use` prefix | `useScrollLock`, `useInvoiceSort` |
|
|
16
|
+
| Constants | `SCREAMING_SNAKE_CASE` for module-level literals | `MAX_RETRIES`, `API_BASE_URL` |
|
|
17
|
+
| Files | `kebab-case` for utilities, `PascalCase` for components | `format-currency.ts`, `InvoiceTable.tsx` |
|
|
18
|
+
| Enum-like objects | `SCREAMING_SNAKE_CASE` keys | `const STATUS = { Active: "ACTIVE" }` |
|
|
19
|
+
|
|
20
|
+
## DO
|
|
21
|
+
|
|
22
|
+
- Use full words — no abbreviations unless universally understood (`id`, `url`, `api`)
|
|
23
|
+
- Include the entity in the name: `userId` not `id`, `selectedInvoice` not `selected`
|
|
24
|
+
- Prefix booleans: `isLoading`, `hasError`, `canSubmit`, `shouldFetch`
|
|
25
|
+
- Name async functions clearly: `fetchUser`, `loadDashboard`, not `getData`
|
|
26
|
+
- Name event handlers with `handle` prefix: `handleSubmit`, `handleRowClick`
|
|
27
|
+
|
|
28
|
+
## DO NOT
|
|
29
|
+
|
|
30
|
+
- Abbreviate: `usr`, `mgr`, `btn`, `cfg`, `val`, `res`, `req`
|
|
31
|
+
- Use single letters except for loop indices (`i`, `j`) and type parameters (`T`, `K`)
|
|
32
|
+
- Name things by type: `userArray`, `stringValue`, `dataObject`
|
|
33
|
+
- Name things by position: `item1`, `item2`
|
|
34
|
+
- Use generic names: `handleStuff`, `doThing`, `myHelper`, `useData`, `processItem`
|
|
35
|
+
|
|
36
|
+
## Generics
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// BAD — single letter gives no context
|
|
40
|
+
function first<T>(arr: T[]): T | undefined
|
|
41
|
+
|
|
42
|
+
// GOOD — descriptive when it matters
|
|
43
|
+
function mapById<TItem extends { id: string }>(items: TItem[]): Map<string, TItem>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use single-letter generics (`T`, `K`, `V`) only when the generic is truly unconstrained and the function is simple enough that context is obvious.
|
|
47
|
+
|
|
48
|
+
## Component Props
|
|
49
|
+
|
|
50
|
+
Suffix prop type names with `Props`:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// components/InvoiceTable.tsx
|
|
54
|
+
interface InvoiceTableProps {
|
|
55
|
+
invoices: Invoice[];
|
|
56
|
+
onRowClick: (id: string) => void;
|
|
57
|
+
isLoading?: boolean;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Event Handlers
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Component props — callback names
|
|
65
|
+
interface ButtonProps {
|
|
66
|
+
onClick: (event: MouseEvent) => void; // external API: "on" prefix
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Local handlers — "handle" prefix
|
|
70
|
+
function handleSubmit(event: FormEvent) { ... }
|
|
71
|
+
function handleRowClick(id: string) { ... }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## See Also
|
|
75
|
+
|
|
76
|
+
- [`../philosophy/readability.md`](../philosophy/readability.md) — names express intent
|
|
77
|
+
- [`rules.md`](rules.md) — explicit, descriptive types
|
|
78
|
+
- [`../react/component-design.md`](../react/component-design.md) — `<ComponentName>Props` interfaces
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# TypeScript Rules
|
|
2
|
+
|
|
3
|
+
## DO
|
|
4
|
+
|
|
5
|
+
- Enable `"strict": true` in all projects — no exceptions
|
|
6
|
+
- Prefer union types over enums: `type Status = "active" | "inactive"` not `enum Status`
|
|
7
|
+
- Prefer generated API types (from OpenAPI, tRPC, Prisma) over hand-written DTOs
|
|
8
|
+
- Use schema-driven validation (Zod) for all external data
|
|
9
|
+
- Use explicit, descriptive type names
|
|
10
|
+
- Prefer `interface` over `type` for object shapes. Use `type` only when interfaces cannot express what you need (unions, intersections, mapped/conditional types, primitive aliases)
|
|
11
|
+
- Prefer `unknown` over `any` for untyped external input
|
|
12
|
+
- Prefer `async/await` over `.then().catch()` chains for sequential async flow
|
|
13
|
+
- Use `satisfies` operator to validate against a type without widening
|
|
14
|
+
- Use `const` assertions for literal types where appropriate
|
|
15
|
+
|
|
16
|
+
## DO NOT
|
|
17
|
+
|
|
18
|
+
- Use `any` — use `unknown` and narrow
|
|
19
|
+
- Use type assertions (`as SomeType`) unless interfacing with third-party types you cannot change
|
|
20
|
+
- Write duplicate DTOs for the same data shape (frontend + backend)
|
|
21
|
+
- Perform type gymnastics — if a type is hard to write, the underlying design may be wrong
|
|
22
|
+
- Use `// @ts-ignore` or `// @ts-expect-error` without a comment explaining why
|
|
23
|
+
- Use `namespace` or `module` declarations in new code
|
|
24
|
+
|
|
25
|
+
## Function Declarations Over Arrow Expressions
|
|
26
|
+
|
|
27
|
+
Use `function` declarations for named, module-level, and exported functions (including React components). Use arrow functions for anonymous callbacks and inline handlers.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// GOOD — named functions use declarations
|
|
31
|
+
function formatCurrency(value: number): string {
|
|
32
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function InvoiceTable({ invoices }: InvoiceTableProps) {
|
|
36
|
+
return <table>...</table>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// GOOD — anonymous arrows for callbacks and inline handlers
|
|
40
|
+
invoices.map((invoice) => invoice.amount);
|
|
41
|
+
invoices.filter((invoice) => invoice.status === "open");
|
|
42
|
+
<button onClick={() => setOpen(true)}>Open</button>
|
|
43
|
+
|
|
44
|
+
// BAD — arrow expression assigned to const for a named function
|
|
45
|
+
const formatCurrency = (value: number): string => { ... };
|
|
46
|
+
const InvoiceTable = ({ invoices }: InvoiceTableProps) => { ... };
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Reasoning:
|
|
50
|
+
- Function declarations are hoisted — usable before their definition in the file
|
|
51
|
+
- Named functions produce clearer stack traces
|
|
52
|
+
- `export function foo()` is easier to scan than `export const foo = () => ...`
|
|
53
|
+
- `this` binding is irrelevant for module-level functions
|
|
54
|
+
|
|
55
|
+
## Interfaces Over Type Aliases
|
|
56
|
+
|
|
57
|
+
Prefer `interface` for object shapes. Use `type` only when `interface` cannot express what you need.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// GOOD — interface for object shapes
|
|
61
|
+
interface User {
|
|
62
|
+
id: string;
|
|
63
|
+
name: string;
|
|
64
|
+
email: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface InvoiceTableProps {
|
|
68
|
+
invoices: Invoice[];
|
|
69
|
+
onRowClick: (id: string) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// GOOD — type for unions, intersections, mapped/conditional types, primitive aliases
|
|
73
|
+
type Status = "active" | "inactive" | "archived";
|
|
74
|
+
type UserId = string;
|
|
75
|
+
type WithTimestamps<T> = T & { createdAt: Date; updatedAt: Date };
|
|
76
|
+
type Nullable<T> = T | null;
|
|
77
|
+
|
|
78
|
+
// BAD — type for an object shape that interface would handle
|
|
79
|
+
type User = {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Reasoning:
|
|
86
|
+
- Interfaces produce clearer error messages
|
|
87
|
+
- Interfaces are open for declaration merging (useful for extending third-party types)
|
|
88
|
+
- `extends` is more readable than intersection types
|
|
89
|
+
- `interface` signals "this is an object shape" — `type` signals "this could be anything"
|
|
90
|
+
|
|
91
|
+
## Unions Over Enums
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// BAD
|
|
95
|
+
enum Direction {
|
|
96
|
+
North = "NORTH",
|
|
97
|
+
South = "SOUTH",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// GOOD
|
|
101
|
+
type Direction = "NORTH" | "SOUTH";
|
|
102
|
+
|
|
103
|
+
// GOOD — const object if you need runtime values
|
|
104
|
+
const DIRECTION = {
|
|
105
|
+
North: "NORTH",
|
|
106
|
+
South: "SOUTH",
|
|
107
|
+
} as const;
|
|
108
|
+
type Direction = (typeof DIRECTION)[keyof typeof DIRECTION];
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Narrowing Over Assertions
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// BAD
|
|
115
|
+
const value = data as User;
|
|
116
|
+
|
|
117
|
+
// GOOD
|
|
118
|
+
if (!isUser(data)) throw new Error("Expected User");
|
|
119
|
+
const value = data; // now narrowed to User
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Avoiding DTO Duplication
|
|
123
|
+
|
|
124
|
+
Prefer generating types from the API layer:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// tRPC — types come from the router
|
|
128
|
+
// Prisma — types come from the schema
|
|
129
|
+
// OpenAPI — types generated from the spec
|
|
130
|
+
// Zod — infer types from schemas
|
|
131
|
+
|
|
132
|
+
const userSchema = z.object({ id: z.string(), name: z.string() });
|
|
133
|
+
type User = z.infer<typeof userSchema>; // single source of truth
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## satisfies
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const theme = {
|
|
140
|
+
primary: "#0070f3",
|
|
141
|
+
secondary: "#1a1a1a",
|
|
142
|
+
} satisfies Record<string, string>;
|
|
143
|
+
|
|
144
|
+
theme.primary; // string — not widened to Record<string, string>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## PRIORITY
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Type safety > Brevity > Cleverness
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## See Also
|
|
154
|
+
|
|
155
|
+
- [anti-patterns.md](anti-patterns.md)
|
|
156
|
+
- [validation.md](validation.md)
|
|
157
|
+
- [naming.md](naming.md)
|
|
158
|
+
- [error-handling.md](error-handling.md)
|
|
159
|
+
- [tsconfig/base.json](tsconfig/base.json)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"noUnusedLocals": true,
|
|
6
|
+
"noUnusedParameters": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
9
|
+
"noFallthroughCasesInSwitch": true,
|
|
10
|
+
"allowUnreachableCode": false,
|
|
11
|
+
"allowUnusedLabels": false,
|
|
12
|
+
"noUncheckedSideEffectImports": true,
|
|
13
|
+
"erasableSyntaxOnly": true,
|
|
14
|
+
"exactOptionalPropertyTypes": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"moduleDetection": "force",
|
|
19
|
+
"target": "ES2022",
|
|
20
|
+
"lib": ["ES2022"],
|
|
21
|
+
"module": "ESNext",
|
|
22
|
+
"moduleResolution": "bundler",
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"declaration": true,
|
|
25
|
+
"declarationMap": true,
|
|
26
|
+
"sourceMap": true,
|
|
27
|
+
"paths": {
|
|
28
|
+
"~/*": ["./src/*"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"exclude": ["node_modules", "dist", "build"]
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./react.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"plugins": [{ "name": "next" }],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"incremental": true
|
|
9
|
+
},
|
|
10
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
11
|
+
"exclude": ["node_modules"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Validate all external data at system boundaries. Never trust raw input from users, APIs, or environment variables.
|
|
6
|
+
|
|
7
|
+
## Preferred Library
|
|
8
|
+
|
|
9
|
+
**Zod** is the default choice.
|
|
10
|
+
|
|
11
|
+
**Valibot** is worth evaluating for bundle-size-sensitive contexts (significantly smaller than Zod).
|
|
12
|
+
|
|
13
|
+
## Schema-First Approach
|
|
14
|
+
|
|
15
|
+
Define the schema once. Derive types from it. Share the schema across frontend and backend.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// shared/schemas/user.schema.ts
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
export const userSchema = z.object({
|
|
22
|
+
id: z.string().uuid(),
|
|
23
|
+
name: z.string().min(1).max(100),
|
|
24
|
+
email: z.string().email(),
|
|
25
|
+
role: z.enum(["admin", "member", "viewer"]),
|
|
26
|
+
createdAt: z.coerce.date(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type User = z.infer<typeof userSchema>;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Validate at Boundaries
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// API response — validate before using
|
|
36
|
+
const response = await fetch("/api/users/1");
|
|
37
|
+
const raw = await response.json();
|
|
38
|
+
const user = userSchema.parse(raw); // throws if invalid
|
|
39
|
+
|
|
40
|
+
// Form submission — validate before submitting
|
|
41
|
+
const formSchema = z.object({
|
|
42
|
+
name: z.string().min(1, "Name is required"),
|
|
43
|
+
email: z.string().email("Must be a valid email"),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Environment variables
|
|
47
|
+
const envSchema = z.object({
|
|
48
|
+
DATABASE_URL: z.string().url(),
|
|
49
|
+
PORT: z.coerce.number().default(3000),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const env = envSchema.parse(process.env);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Safe Parse for Non-Throwing Validation
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const result = userSchema.safeParse(raw);
|
|
59
|
+
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
// result.error.issues contains field-level errors
|
|
62
|
+
return { error: result.error.flatten() };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const user = result.data;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Shared FE/BE Schemas
|
|
69
|
+
|
|
70
|
+
Place shared schemas in a location accessible to both frontend and backend:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
shared/
|
|
74
|
+
schemas/
|
|
75
|
+
user.schema.ts
|
|
76
|
+
invoice.schema.ts
|
|
77
|
+
auth.schema.ts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In a monorepo, export from a shared package:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { userSchema } from "@acme/schemas";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## DO NOT
|
|
87
|
+
|
|
88
|
+
- Accept `any` from `JSON.parse` without parsing through a schema
|
|
89
|
+
- Duplicate validation logic in both frontend and backend
|
|
90
|
+
- Use manual `if` chains to validate complex shapes — use a schema library
|
|
91
|
+
- Rely on TypeScript types alone for runtime safety — types are erased at runtime
|
|
92
|
+
|
|
93
|
+
## PRIORITY
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Schema-first > Manual validation > No validation
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## See Also
|
|
100
|
+
|
|
101
|
+
- [`rules.md`](rules.md) — general TS rules including no `any`
|
|
102
|
+
- [`../security/validation.md`](../security/validation.md) — validating at API boundaries
|
|
103
|
+
- [`../architecture/api-design.md`](../architecture/api-design.md) — shared schemas across FE/BE
|
|
104
|
+
- [`../react/forms.md`](../react/forms.md) — Zod schemas with TanStack/RHF forms
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Executors
|
|
2
|
+
|
|
3
|
+
How this project dispatches a **coder** and a **reviewer**. Hand-written prose, read fresh at dispatch
|
|
4
|
+
time — the exact parallel to [`verify.md`](verify.md), and for the same reason: a skill that hardcodes an
|
|
5
|
+
invocation bakes one machine's setup into a tool that ships everywhere.
|
|
6
|
+
|
|
7
|
+
Run `/onboard` to fill this in.
|
|
8
|
+
|
|
9
|
+
## Coder
|
|
10
|
+
|
|
11
|
+
<!-- Empty means: implement in-host. That is a valid configuration, not a gap.
|
|
12
|
+
|
|
13
|
+
If an external coder CLI is configured, write its exact invocation here, including any directory or
|
|
14
|
+
permission scoping it needs on this machine. The system prompt is context/roles/coder.md.
|
|
15
|
+
|
|
16
|
+
Record alongside it whether that executor was actually observed reading this repository unaided, and
|
|
17
|
+
when. /onboard tests it; the standing rule below says why the answer changes how briefs are written. -->
|
|
18
|
+
|
|
19
|
+
**Not configured — implement in-host.**
|
|
20
|
+
|
|
21
|
+
## Reviewer
|
|
22
|
+
|
|
23
|
+
<!-- Empty means: the host reviews the diff itself against the plan's review checklist. Weaker than an
|
|
24
|
+
independent reviewer, but still a gate, and it must say which one it ran.
|
|
25
|
+
|
|
26
|
+
A host that offers review often offers more than one shape of it — a review subcommand, a review skill
|
|
27
|
+
it can be asked to run, or both — and they do not review alike. Whichever was chosen, write the exact
|
|
28
|
+
invocation. Nothing shipped with this tool names one, because the winner differs per host. -->
|
|
29
|
+
|
|
30
|
+
**Not configured — the host reviews the diff against the plan's review checklist, and says so.**
|
|
31
|
+
|
|
32
|
+
## The contract, whatever is configured
|
|
33
|
+
|
|
34
|
+
A review happens, it returns a verdict with a `P0`–`P3` severity on every blocking finding, and a `FAIL`
|
|
35
|
+
writes a finding to [`findings.md`](findings.md) **before** the loopback.
|
|
36
|
+
|
|
37
|
+
## Standing rules for any external executor
|
|
38
|
+
|
|
39
|
+
- **Exit code alone proves nothing.** A CLI can exit 0 after hitting a usage limit mid-run, having
|
|
40
|
+
completed most but not provably all of a brief. Grep the captured output for exhaustion and error
|
|
41
|
+
markers before trusting a summary, and on a hit check `git status` and each acceptance criterion
|
|
42
|
+
individually.
|
|
43
|
+
- **Take the model from the CLI's own config**, not from a flag written here. A hardcoded model flag is one
|
|
44
|
+
more place to update when models turn over, and a rejected model can still exit 0 having written nothing.
|
|
45
|
+
- **No blanket permission-bypass flag.** Scope permissions in the CLI's own config instead. A standing
|
|
46
|
+
bypass-everything instruction in a committed file is persistent privilege escalation.
|
|
47
|
+
- **Assume the executor can read this repository** unless you have tested otherwise. Briefs cite paths;
|
|
48
|
+
they do not paste file contents. If an executor genuinely has no filesystem access, say so here — that is
|
|
49
|
+
the one case where a brief has to carry content inline.
|