@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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/agents-block.js +70 -0
  4. package/dist/bin.js +14 -0
  5. package/dist/check/markdown.js +47 -0
  6. package/dist/check/parse.js +113 -0
  7. package/dist/check/rules.js +245 -0
  8. package/dist/cli.js +109 -0
  9. package/dist/commands/check.js +41 -0
  10. package/dist/commands/install.js +76 -0
  11. package/dist/commands/standards-add.js +145 -0
  12. package/dist/commands/update.js +214 -0
  13. package/dist/layout.js +115 -0
  14. package/dist/log.js +20 -0
  15. package/dist/manifest.js +53 -0
  16. package/dist/paths.js +41 -0
  17. package/package.json +37 -0
  18. package/templates/blocks/agents-block.md +30 -0
  19. package/templates/claude/agents/planner.agent.md +60 -0
  20. package/templates/claude/agents/reviewer.agent.md +77 -0
  21. package/templates/context/README.md +81 -0
  22. package/templates/context/plan-template.md +69 -0
  23. package/templates/context/plan-template.notes.md +55 -0
  24. package/templates/context/roles/coder.md +76 -0
  25. package/templates/context/workflow.md +143 -0
  26. package/templates/skills/feature-close/SKILL.md +102 -0
  27. package/templates/skills/feature-implement/SKILL.md +142 -0
  28. package/templates/skills/feature-plan/SKILL.md +138 -0
  29. package/templates/skills/feature-status/SKILL.md +86 -0
  30. package/templates/skills/onboard/SKILL.md +122 -0
  31. package/templates/skills/orchestrate/SKILL.md +92 -0
  32. package/templates/skills/roadmap/SKILL.md +97 -0
  33. package/templates/standards/.source +8 -0
  34. package/templates/standards/README.md +104 -0
  35. package/templates/standards/architecture/api-design.md +212 -0
  36. package/templates/standards/architecture/dependency-boundaries.md +104 -0
  37. package/templates/standards/architecture/feature-driven.md +102 -0
  38. package/templates/standards/architecture/folder-structure.md +108 -0
  39. package/templates/standards/architecture/monorepos.md +107 -0
  40. package/templates/standards/architecture/refactoring.md +97 -0
  41. package/templates/standards/architecture/shared-code.md +81 -0
  42. package/templates/standards/docs/PHP-SPEC.md +1195 -0
  43. package/templates/standards/docs/SPEC.md +540 -0
  44. package/templates/standards/examples/bad/data-fetching.tsx +52 -0
  45. package/templates/standards/examples/bad/derived-state.tsx +60 -0
  46. package/templates/standards/examples/bad/feature-component.tsx +80 -0
  47. package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
  48. package/templates/standards/examples/good/data-fetching.tsx +53 -0
  49. package/templates/standards/examples/good/derived-state.tsx +52 -0
  50. package/templates/standards/examples/good/feature-component.tsx +141 -0
  51. package/templates/standards/examples/good/invoice.schema.ts +20 -0
  52. package/templates/standards/examples/good/prop-drilling.tsx +83 -0
  53. package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
  54. package/templates/standards/philosophy/core-principles.md +49 -0
  55. package/templates/standards/philosophy/incremental-abstraction.md +56 -0
  56. package/templates/standards/philosophy/maintainability.md +66 -0
  57. package/templates/standards/philosophy/readability.md +87 -0
  58. package/templates/standards/php/README.md +62 -0
  59. package/templates/standards/php/anti-patterns.md +259 -0
  60. package/templates/standards/php/architecture.md +241 -0
  61. package/templates/standards/php/frameworks.md +197 -0
  62. package/templates/standards/php/rules.md +205 -0
  63. package/templates/standards/php/security.md +190 -0
  64. package/templates/standards/php/testing.md +139 -0
  65. package/templates/standards/php/tooling.md +245 -0
  66. package/templates/standards/react/accessibility.md +130 -0
  67. package/templates/standards/react/anti-patterns.md +187 -0
  68. package/templates/standards/react/component-design.md +139 -0
  69. package/templates/standards/react/error-boundaries.md +195 -0
  70. package/templates/standards/react/forms.md +147 -0
  71. package/templates/standards/react/hooks.md +88 -0
  72. package/templates/standards/react/memoization.md +87 -0
  73. package/templates/standards/react/state-management.md +123 -0
  74. package/templates/standards/react/testing.md +118 -0
  75. package/templates/standards/react/use-effect.md +110 -0
  76. package/templates/standards/security/api-security.md +136 -0
  77. package/templates/standards/security/auth.md +112 -0
  78. package/templates/standards/security/secrets.md +105 -0
  79. package/templates/standards/security/validation.md +89 -0
  80. package/templates/standards/templates/.editorconfig +16 -0
  81. package/templates/standards/templates/.nvmrc +1 -0
  82. package/templates/standards/templates/AGENTS.md +46 -0
  83. package/templates/standards/templates/CLAUDE.md +3 -0
  84. package/templates/standards/templates/_dot_gitignore +50 -0
  85. package/templates/standards/templates/biome.json +53 -0
  86. package/templates/standards/templates/eslint.config.js +61 -0
  87. package/templates/standards/templates/project-readme.md +76 -0
  88. package/templates/standards/templates/pull-request-template.md +46 -0
  89. package/templates/standards/templates/tsconfig.json +35 -0
  90. package/templates/standards/tooling/biome.md +92 -0
  91. package/templates/standards/tooling/ci.md +128 -0
  92. package/templates/standards/tooling/dates.md +194 -0
  93. package/templates/standards/tooling/dependencies.md +90 -0
  94. package/templates/standards/tooling/nx.md +113 -0
  95. package/templates/standards/tooling/observability.md +183 -0
  96. package/templates/standards/tooling/prisma.md +152 -0
  97. package/templates/standards/tooling/shadcn.md +115 -0
  98. package/templates/standards/tooling/tailwind.md +135 -0
  99. package/templates/standards/tooling/tanstack-query.md +214 -0
  100. package/templates/standards/tooling/tanstack-router.md +365 -0
  101. package/templates/standards/tooling/tanstack.md +31 -0
  102. package/templates/standards/tooling/vite.md +135 -0
  103. package/templates/standards/typescript/anti-patterns.md +107 -0
  104. package/templates/standards/typescript/error-handling.md +216 -0
  105. package/templates/standards/typescript/naming.md +78 -0
  106. package/templates/standards/typescript/rules.md +159 -0
  107. package/templates/standards/typescript/tsconfig/base.json +32 -0
  108. package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
  109. package/templates/standards/typescript/tsconfig/react.json +9 -0
  110. package/templates/standards/typescript/validation.md +104 -0
  111. package/templates/stubs/executors.md +49 -0
  112. package/templates/stubs/findings.md +40 -0
  113. package/templates/stubs/history.md +13 -0
  114. package/templates/stubs/roadmap.md +29 -0
  115. package/templates/stubs/stack.md +35 -0
  116. package/templates/stubs/verify.md +48 -0
@@ -0,0 +1,123 @@
1
+ # State Management
2
+
3
+ ## Hierarchy
4
+
5
+ Choose the simplest state mechanism that works. Escalate only when necessary.
6
+
7
+ ```
8
+ 1. URL state — shareable, bookmarkable, free
9
+ 2. Server state — TanStack Query (cached, synchronized, background-refreshed)
10
+ 3. Local state — useState / useReducer
11
+ 4. Context — for shared UI state (theme, current user, modal stack)
12
+ 5. Zustand — only when Context is insufficient
13
+ 6. Redux — never
14
+ ```
15
+
16
+ ## URL State
17
+
18
+ Prefer URL state for anything that should be shareable, bookmarkable, or survive a refresh:
19
+
20
+ - Filters, search queries, pagination, selected tab
21
+ - Use TanStack Router's typed search params (validated with Zod)
22
+
23
+ ```ts
24
+ // TanStack Router — typed search params
25
+ const search = invoicesRoute.useSearch();
26
+ const navigate = invoicesRoute.useNavigate();
27
+
28
+ function setStatus(status: InvoiceStatus) {
29
+ navigate({ search: (prev) => ({ ...prev, status, page: 1 }) });
30
+ }
31
+ ```
32
+
33
+ See [`../tooling/tanstack-router.md`](../tooling/tanstack-router.md) for the full pattern (route definition, validation, loaders).
34
+
35
+ ## Server State — TanStack Query
36
+
37
+ For data that lives on the server, TanStack Query is the default:
38
+
39
+ ```ts
40
+ const { data: invoices, isLoading } = useQuery({
41
+ queryKey: ["invoices", filters],
42
+ queryFn: () => fetchInvoices(filters),
43
+ staleTime: 60_000,
44
+ });
45
+
46
+ const { mutate: createInvoice } = useMutation({
47
+ mutationFn: createInvoiceFn,
48
+ onSuccess: () => {
49
+ queryClient.invalidateQueries({ queryKey: ["invoices"] });
50
+ },
51
+ });
52
+ ```
53
+
54
+ Do not duplicate server state into local state. Do not use `useEffect` + `useState` for data fetching.
55
+
56
+ ## Local State
57
+
58
+ For UI-only state that does not need to be shared:
59
+
60
+ ```ts
61
+ const [isOpen, setIsOpen] = useState(false);
62
+ const [activeTab, setActiveTab] = useState<"overview" | "details">("overview");
63
+ ```
64
+
65
+ Use `useReducer` when state transitions are complex or interrelated:
66
+
67
+ ```ts
68
+ type Action =
69
+ | { type: "SET_STEP"; step: number }
70
+ | { type: "SUBMIT" }
71
+ | { type: "RESET" };
72
+
73
+ function reducer(state: WizardState, action: Action): WizardState { ... }
74
+
75
+ const [state, dispatch] = useReducer(reducer, initialState);
76
+ ```
77
+
78
+ ## Context
79
+
80
+ Use Context for state that many components need but that does not belong in the server:
81
+
82
+ - Current user / session
83
+ - Theme / color scheme
84
+ - Open modals / toasts
85
+ - Feature flags
86
+
87
+ Keep Context providers small. Do not put everything in a single `AppContext`.
88
+
89
+ ## Zustand
90
+
91
+ Use Zustand when:
92
+ - State is complex and cross-cutting enough that Context causes re-render issues
93
+ - Multiple independent slices of UI state need to be coordinated
94
+
95
+ ```ts
96
+ const useInvoiceStore = create<InvoiceStore>((set) => ({
97
+ selectedIds: [],
98
+ toggleSelected: (id) =>
99
+ set((state) => ({
100
+ selectedIds: state.selectedIds.includes(id)
101
+ ? state.selectedIds.filter((x) => x !== id)
102
+ : [...state.selectedIds, id],
103
+ })),
104
+ }));
105
+ ```
106
+
107
+ ## Never Redux
108
+
109
+ Redux adds ceremony, boilerplate, and indirection that is not justified for modern React applications. TanStack Query + Zustand + URL state covers all real use cases with far less overhead.
110
+
111
+ ## PRIORITY
112
+
113
+ ```
114
+ URL state > Server state > Local state > Context > Zustand
115
+ ```
116
+
117
+ ## See Also
118
+
119
+ - [`../tooling/tanstack-router.md`](../tooling/tanstack-router.md) — typed URL state, loaders
120
+ - [`../tooling/tanstack-query.md`](../tooling/tanstack-query.md) — server state cache
121
+ - [`use-effect.md`](use-effect.md) — what useEffect is and isn't for
122
+ - [`forms.md`](forms.md) — form state management
123
+ - [`hooks.md`](hooks.md) — composing hooks for stateful logic
@@ -0,0 +1,118 @@
1
+ # Testing
2
+
3
+ ## Philosophy
4
+
5
+ Test behavior, not implementation. A test that breaks when you rename a variable is a bad test. A test that breaks when user-visible behavior changes is a good test.
6
+
7
+ Reference: [Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
8
+
9
+ ## Tooling
10
+
11
+ - **Vitest** — test runner (fast, native ESM, compatible with Jest API)
12
+ - **Testing Library** — component testing (`@testing-library/react`, `@testing-library/user-event`)
13
+ - **Playwright** — E2E tests for critical user flows only
14
+
15
+ ## DO
16
+
17
+ - Test from the user's perspective — what does the user see and do?
18
+ - Query by accessible role, label, or text — not by `data-testid` or CSS class
19
+ - Use `userEvent` over `fireEvent` for realistic interaction simulation
20
+ - Test the happy path and the critical error paths
21
+ - Write unit tests for pure utility functions and complex business logic
22
+ - Write integration tests for feature flows (form submission, data fetching, state transitions)
23
+
24
+ ## DO NOT
25
+
26
+ - Test implementation details (internal state, private methods, component structure)
27
+ - Use snapshot tests — they break constantly and encode nothing meaningful
28
+ - Mock things you do not own (browser APIs, global state) without good reason
29
+ - Assert on CSS class names or DOM structure
30
+ - Write tests that are tightly coupled to component internals
31
+
32
+ ## Query Priority
33
+
34
+ Follow Testing Library's recommended query priority:
35
+
36
+ ```
37
+ 1. getByRole — best: matches what a screen reader sees
38
+ 2. getByLabelText — for form inputs
39
+ 3. getByPlaceholderText — last resort for inputs
40
+ 4. getByText — for non-interactive elements
41
+ 5. getByDisplayValue — for form elements with a value
42
+ 6. getByAltText — for images
43
+ 7. getByTitle — for title attributes
44
+ 8. getByTestId — last resort only (requires data-testid attribute)
45
+ ```
46
+
47
+ ## Example — Integration Test
48
+
49
+ ```tsx
50
+ import { render, screen } from "@testing-library/react";
51
+ import userEvent from "@testing-library/user-event";
52
+ import { CreateInvoiceForm } from "./CreateInvoiceForm";
53
+
54
+ test("submits the form with valid data", async () => {
55
+ const user = userEvent.setup();
56
+ const onSubmit = vi.fn();
57
+
58
+ render(<CreateInvoiceForm onSubmit={onSubmit} />);
59
+
60
+ await user.type(screen.getByLabelText("Title"), "Q4 Report");
61
+ await user.type(screen.getByLabelText("Amount"), "500");
62
+ await user.click(screen.getByRole("button", { name: "Create" }));
63
+
64
+ expect(onSubmit).toHaveBeenCalledWith({ title: "Q4 Report", amount: 500 });
65
+ });
66
+
67
+ test("shows validation errors for empty fields", async () => {
68
+ const user = userEvent.setup();
69
+
70
+ render(<CreateInvoiceForm onSubmit={vi.fn()} />);
71
+
72
+ await user.click(screen.getByRole("button", { name: "Create" }));
73
+
74
+ expect(screen.getByRole("alert")).toHaveTextContent("Title is required");
75
+ });
76
+ ```
77
+
78
+ ## Playwright — E2E (Critical Flows Only)
79
+
80
+ Reserve Playwright for flows that are:
81
+ - Business-critical (checkout, authentication, data export)
82
+ - Too complex to integration-test meaningfully
83
+ - Dependent on real browser behavior (file upload, clipboard, print)
84
+
85
+ ```ts
86
+ test("user can log in and view their dashboard", async ({ page }) => {
87
+ await page.goto("/login");
88
+ await page.getByLabel("Email").fill("user@example.com");
89
+ await page.getByLabel("Password").fill("password");
90
+ await page.getByRole("button", { name: "Sign in" }).click();
91
+ await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
92
+ });
93
+ ```
94
+
95
+ ## File Colocation
96
+
97
+ ```
98
+ features/
99
+ invoices/
100
+ InvoiceTable.tsx
101
+ InvoiceTable.test.tsx ← colocated with the component
102
+ useInvoiceSort.ts
103
+ useInvoiceSort.test.ts ← colocated with the hook
104
+ ```
105
+
106
+ ## PRIORITY
107
+
108
+ ```
109
+ Integration tests > Unit tests > E2E tests
110
+ Behavior > Implementation
111
+ ```
112
+
113
+ ## See Also
114
+
115
+ - [`component-design.md`](component-design.md) — testable component structure
116
+ - [`hooks.md`](hooks.md) — testing custom hooks via the components that use them
117
+ - [`accessibility.md`](accessibility.md) — query by role, label, text
118
+ - [`../architecture/refactoring.md`](../architecture/refactoring.md) — write tests before refactoring untested code
@@ -0,0 +1,110 @@
1
+ # useEffect
2
+
3
+ ## Core Rule
4
+
5
+ `useEffect` is an escape hatch for synchronizing with external systems. It is not a general-purpose lifecycle hook.
6
+
7
+ ## VALID uses
8
+
9
+ - Synchronizing with external systems (WebSocket, EventEmitter, third-party widget)
10
+ - DOM APIs that require imperative access (focus management, scroll position, canvas)
11
+ - Subscriptions (event listeners, pub/sub, real-time)
12
+ - Timers and intervals
13
+ - Imperative bridges to non-React code
14
+
15
+ ## INVALID uses
16
+
17
+ - Deriving state from props or other state
18
+ - Syncing two pieces of React state together
19
+ - Fetching data (use TanStack Query instead)
20
+ - Responding to user events (handle directly in event handlers)
21
+ - Updating state on prop change (compute during render instead)
22
+
23
+ ## Examples
24
+
25
+ ### Correct — external system sync
26
+
27
+ ```ts
28
+ useEffect(() => {
29
+ const socket = new WebSocket(url);
30
+ socket.addEventListener("message", handleMessage);
31
+ return () => {
32
+ socket.removeEventListener("message", handleMessage);
33
+ socket.close();
34
+ };
35
+ }, [url, handleMessage]);
36
+ ```
37
+
38
+ ### Correct — DOM API
39
+
40
+ ```ts
41
+ useEffect(() => {
42
+ if (isOpen) {
43
+ inputRef.current?.focus();
44
+ }
45
+ }, [isOpen]);
46
+ ```
47
+
48
+ ### Incorrect — derived state
49
+
50
+ ```ts
51
+ // BAD — deriving state with useEffect
52
+ const [fullName, setFullName] = useState("");
53
+ useEffect(() => {
54
+ setFullName(`${firstName} ${lastName}`);
55
+ }, [firstName, lastName]);
56
+
57
+ // GOOD — compute during render
58
+ const fullName = `${firstName} ${lastName}`;
59
+ ```
60
+
61
+ ### Incorrect — state synchronization
62
+
63
+ ```ts
64
+ // BAD — syncing two pieces of state
65
+ useEffect(() => {
66
+ setDraftValue(externalValue);
67
+ }, [externalValue]);
68
+
69
+ // GOOD — use a key to reset controlled state
70
+ <Input key={externalValue} defaultValue={externalValue} />
71
+ ```
72
+
73
+ ### Incorrect — data fetching
74
+
75
+ ```ts
76
+ // BAD — manual fetch in useEffect
77
+ useEffect(() => {
78
+ let cancelled = false;
79
+ fetchUser(id).then((data) => {
80
+ if (!cancelled) setUser(data);
81
+ });
82
+ return () => { cancelled = true; };
83
+ }, [id]);
84
+
85
+ // GOOD — use TanStack Query
86
+ const { data: user } = useQuery({
87
+ queryKey: ["user", id],
88
+ queryFn: () => fetchUser(id),
89
+ });
90
+ ```
91
+
92
+ ## Dependency Array Rules
93
+
94
+ - Always specify the dependency array — never omit it
95
+ - Include all reactive values used inside the effect
96
+ - If a dependency changes too often, consider `useRef` or restructuring
97
+ - Do not use `// eslint-disable-line react-hooks/exhaustive-deps` to suppress warnings — fix the underlying issue
98
+
99
+ ## PRIORITY
100
+
101
+ ```
102
+ No effect > Computed value > Event handler > useEffect
103
+ ```
104
+
105
+ ## See Also
106
+
107
+ - [`state-management.md`](state-management.md) — where state should actually live
108
+ - [`hooks.md`](hooks.md) — composing hooks without useEffect abuse
109
+ - [`../tooling/tanstack-query.md`](../tooling/tanstack-query.md) — data fetching without useEffect
110
+ - [`anti-patterns.md`](anti-patterns.md) — common useEffect mistakes
@@ -0,0 +1,136 @@
1
+ # API Security
2
+
3
+ ## Core Rules
4
+
5
+ - Validate all input (see [validation.md](validation.md))
6
+ - Authenticate all protected routes
7
+ - Authorize every resource access
8
+ - Return minimal, appropriate HTTP status codes
9
+ - Rate limit public and sensitive endpoints
10
+ - Never expose internal error details to clients
11
+
12
+ ## HTTP Status Codes
13
+
14
+ Use precise status codes:
15
+
16
+ | Code | When |
17
+ |---|---|
18
+ | `200` | Successful GET, PATCH, PUT |
19
+ | `201` | Successful POST (resource created) |
20
+ | `204` | Successful DELETE (no content) |
21
+ | `400` | Malformed request (bad JSON, missing required field) |
22
+ | `401` | Not authenticated |
23
+ | `403` | Authenticated but not authorized |
24
+ | `404` | Resource not found |
25
+ | `409` | Conflict (duplicate, optimistic lock failure) |
26
+ | `422` | Validation error (well-formed but invalid data) |
27
+ | `429` | Rate limit exceeded |
28
+ | `500` | Internal server error |
29
+
30
+ ## Error Responses
31
+
32
+ Return structured errors. Never expose stack traces or internal details:
33
+
34
+ ```ts
35
+ // BAD — exposes internals
36
+ res.status(500).json({ error: error.stack });
37
+
38
+ // GOOD — structured, safe
39
+ res.status(422).json({
40
+ error: "Validation failed",
41
+ fields: result.error.flatten().fieldErrors,
42
+ });
43
+ ```
44
+
45
+ ## Rate Limiting
46
+
47
+ Apply rate limiting to all public APIs:
48
+
49
+ ```ts
50
+ const ratelimit = new Ratelimit({
51
+ redis: Redis.fromEnv(),
52
+ limiter: Ratelimit.slidingWindow(100, "1 m"), // 100 req/min
53
+ });
54
+
55
+ export async function middleware(req: NextRequest) {
56
+ const ip = req.ip ?? "unknown";
57
+ const { success, limit, remaining } = await ratelimit.limit(ip);
58
+
59
+ if (!success) {
60
+ return NextResponse.json({ error: "Too many requests" }, {
61
+ status: 429,
62
+ headers: {
63
+ "X-RateLimit-Limit": limit.toString(),
64
+ "X-RateLimit-Remaining": remaining.toString(),
65
+ },
66
+ });
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## CORS
72
+
73
+ Configure CORS explicitly. Do not allow all origins in production:
74
+
75
+ ```ts
76
+ const corsOptions = {
77
+ origin: process.env.NODE_ENV === "production"
78
+ ? ["https://app.example.com"]
79
+ : ["http://localhost:3000"],
80
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
81
+ credentials: true,
82
+ };
83
+ ```
84
+
85
+ ## SQL Injection Prevention
86
+
87
+ Use parameterized queries. Prisma handles this automatically:
88
+
89
+ ```ts
90
+ // SAFE — Prisma parameterizes automatically
91
+ const user = await db.user.findUnique({ where: { email } });
92
+
93
+ // DANGEROUS — never concatenate user input into raw SQL
94
+ const user = await db.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);
95
+
96
+ // SAFE — use $queryRaw with tagged template (automatically parameterized)
97
+ const user = await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
98
+ ```
99
+
100
+ ## Content Security Policy
101
+
102
+ Set a Content Security Policy header to mitigate XSS:
103
+
104
+ ```ts
105
+ // Next.js — next.config.ts
106
+ const securityHeaders = [
107
+ { key: "X-Content-Type-Options", value: "nosniff" },
108
+ { key: "X-Frame-Options", value: "DENY" },
109
+ { key: "X-XSS-Protection", value: "1; mode=block" },
110
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
111
+ {
112
+ key: "Content-Security-Policy",
113
+ value: [
114
+ "default-src 'self'",
115
+ "script-src 'self' 'unsafe-inline'", // tighten after audit
116
+ "style-src 'self' 'unsafe-inline'",
117
+ "img-src 'self' data: https:",
118
+ ].join("; "),
119
+ },
120
+ ];
121
+ ```
122
+
123
+ ## DO NOT
124
+
125
+ - Expose database errors or stack traces to the client
126
+ - Trust user-supplied IDs for authorization
127
+ - Use `*` for CORS `Access-Control-Allow-Origin` in production
128
+ - Accept file uploads without size and MIME type validation
129
+ - Log request bodies that may contain passwords or payment data
130
+
131
+ ## See Also
132
+
133
+ - [`validation.md`](validation.md) — input validation patterns
134
+ - [`auth.md`](auth.md) — authentication and authorization
135
+ - [`../typescript/error-handling.md`](../typescript/error-handling.md) — structured error responses
136
+ - [`../architecture/api-design.md`](../architecture/api-design.md) — error shape conventions
@@ -0,0 +1,112 @@
1
+ # Authentication and Authorization
2
+
3
+ ## Core Rules
4
+
5
+ - Authentication confirms identity. Authorization confirms permission. Both are required.
6
+ - Never trust client-supplied user IDs or roles without server-side verification
7
+ - Always verify authorization on the server — never rely solely on client-side checks
8
+ - Use an established auth library — do not build custom auth from scratch
9
+
10
+ ## Preferred Libraries
11
+
12
+ - **Auth.js** (Next.js) — sessions, OAuth, credentials, magic links
13
+ - **Clerk** — managed auth with pre-built UI components
14
+ - **Lucia** — lightweight, flexible, self-hosted
15
+
16
+ ## Session Handling
17
+
18
+ ```ts
19
+ // Server-side — always verify the session server-side
20
+ import { auth } from "~/lib/auth";
21
+
22
+ export async function GET(req: Request) {
23
+ const session = await auth();
24
+ if (!session?.user) {
25
+ return new Response("Unauthorized", { status: 401 });
26
+ }
27
+ // use session.user.id — never use a userId from the request body
28
+ const invoices = await getInvoicesForUser(session.user.id);
29
+ return Response.json(invoices);
30
+ }
31
+ ```
32
+
33
+ ## Authorization Checks
34
+
35
+ Always check permissions on the server, for every request:
36
+
37
+ ```ts
38
+ async function getInvoice(invoiceId: string, requestingUserId: string) {
39
+ const invoice = await db.invoice.findUnique({ where: { id: invoiceId } });
40
+ if (!invoice) throw new NotFoundError("Invoice", invoiceId);
41
+
42
+ // Authorization — verify the user owns this resource
43
+ if (invoice.userId !== requestingUserId) {
44
+ throw new ForbiddenError("You do not have access to this invoice");
45
+ }
46
+
47
+ return invoice;
48
+ }
49
+ ```
50
+
51
+ ## Token Storage
52
+
53
+ | Token Type | Recommended Storage | Avoid |
54
+ |---|---|---|
55
+ | Session cookies | `HttpOnly`, `Secure`, `SameSite=Lax` cookies | `localStorage` |
56
+ | JWT (short-lived) | `HttpOnly` cookie | `localStorage` |
57
+ | Refresh tokens | `HttpOnly` cookie | `localStorage` or `sessionStorage` |
58
+
59
+ `localStorage` is accessible to JavaScript and vulnerable to XSS. Always use `HttpOnly` cookies for auth tokens.
60
+
61
+ ## Password Handling
62
+
63
+ ```ts
64
+ import bcrypt from "bcryptjs";
65
+
66
+ // Hashing
67
+ const hash = await bcrypt.hash(plaintext, 12); // min 12 rounds
68
+
69
+ // Verification — use timing-safe comparison
70
+ const isValid = await bcrypt.compare(plaintext, hash);
71
+ ```
72
+
73
+ - Never store plaintext passwords
74
+ - Use bcrypt, argon2, or scrypt — not MD5 or SHA-1
75
+ - Minimum 12 rounds for bcrypt
76
+
77
+ ## CSRF Protection
78
+
79
+ - Use `SameSite=Lax` or `SameSite=Strict` on session cookies
80
+ - Use CSRF tokens for state-changing requests if cookies are `SameSite=None`
81
+ - Auth.js and Clerk handle CSRF automatically
82
+
83
+ ## Rate Limiting Auth Endpoints
84
+
85
+ Apply rate limiting to:
86
+ - Login endpoints
87
+ - Password reset endpoints
88
+ - OTP/magic link endpoints
89
+
90
+ ```ts
91
+ import { Ratelimit } from "@upstash/ratelimit";
92
+
93
+ const ratelimit = new Ratelimit({
94
+ limiter: Ratelimit.slidingWindow(5, "15 m"), // 5 attempts per 15 minutes
95
+ });
96
+
97
+ const { success } = await ratelimit.limit(ip);
98
+ if (!success) return new Response("Too many attempts", { status: 429 });
99
+ ```
100
+
101
+ ## DO NOT
102
+
103
+ - Use the user's ID from request body for authorization decisions — always use the server session
104
+ - Return different HTTP status codes for "user not found" vs. "wrong password" (user enumeration)
105
+ - Log passwords or tokens
106
+ - Implement custom crypto or JWT libraries
107
+
108
+ ## See Also
109
+
110
+ - [`secrets.md`](secrets.md) — secret storage and rotation
111
+ - [`api-security.md`](api-security.md) — rate limiting and CORS
112
+ - [`validation.md`](validation.md) — validating auth requests
@@ -0,0 +1,105 @@
1
+ # Secrets Management
2
+
3
+ ## Core Rules
4
+
5
+ - Never commit secrets to version control
6
+ - Never log secrets
7
+ - Never expose backend secrets to the client
8
+ - Rotate secrets that have been compromised or accidentally exposed
9
+
10
+ ## Environment Variables
11
+
12
+ Store secrets in environment variables. Never hardcode them.
13
+
14
+ ```
15
+ # .env.local (gitignored — for local development only)
16
+ DATABASE_URL=postgres://user:password@localhost:5432/mydb
17
+ JWT_SECRET=your-secret-key
18
+ STRIPE_SECRET_KEY=sk_test_...
19
+ ```
20
+
21
+ ```
22
+ # .env.example (committed — shows required vars without values)
23
+ DATABASE_URL=
24
+ JWT_SECRET=
25
+ STRIPE_SECRET_KEY=
26
+ SENDGRID_API_KEY=
27
+ ```
28
+
29
+ ## `.gitignore`
30
+
31
+ Always include:
32
+
33
+ ```gitignore
34
+ .env
35
+ .env.local
36
+ .env.*.local
37
+ *.pem
38
+ *.key
39
+ ```
40
+
41
+ ## Validate at Startup
42
+
43
+ ```ts
44
+ // lib/env.ts (server-only)
45
+ import { z } from "zod";
46
+
47
+ const envSchema = z.object({
48
+ DATABASE_URL: z.string().url(),
49
+ JWT_SECRET: z.string().min(32),
50
+ STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
51
+ NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
52
+ });
53
+
54
+ export const env = envSchema.parse(process.env);
55
+ ```
56
+
57
+ Fail fast on startup if required secrets are missing or malformed.
58
+
59
+ ## Client vs. Server Secrets
60
+
61
+ | Type | Where | Prefix (Vite/Next.js) | Example |
62
+ |---|---|---|---|
63
+ | Server-only | Server environment | No prefix | `DATABASE_URL`, `JWT_SECRET` |
64
+ | Client-safe | Client environment | `VITE_` / `NEXT_PUBLIC_` | `VITE_API_BASE_URL` |
65
+
66
+ Never put a secret key in a `VITE_` or `NEXT_PUBLIC_` variable — it will be embedded in the client bundle.
67
+
68
+ ## CI/CD Secrets
69
+
70
+ - Use the CI provider's secret store (GitHub Actions secrets, Vercel env vars, etc.)
71
+ - Use separate secrets for each environment (dev, staging, production)
72
+ - Limit secret access to only the jobs that need them
73
+
74
+ ## Secret Scanning
75
+
76
+ - Enable GitHub's secret scanning on all repositories
77
+ - Add `gitleaks` or `truffleHog` to pre-commit hooks for local detection
78
+
79
+ ```bash
80
+ # Install gitleaks pre-commit hook
81
+ brew install gitleaks
82
+ gitleaks protect --staged
83
+ ```
84
+
85
+ ## If a Secret Is Exposed
86
+
87
+ 1. Rotate it immediately — assume it has been compromised
88
+ 2. Revoke the old secret in the provider's dashboard
89
+ 3. Audit access logs for unauthorized use
90
+ 4. Update all environments with the new secret
91
+ 5. Review git history and remove the commit from public branches if possible
92
+
93
+ ## DO NOT
94
+
95
+ - Hardcode API keys, passwords, or tokens in source code
96
+ - Log secrets (even in debug mode)
97
+ - Pass secrets as command-line arguments (visible in process list)
98
+ - Store secrets in `localStorage` or cookies without encryption
99
+ - Use the same secret across environments
100
+
101
+ ## See Also
102
+
103
+ - [`auth.md`](auth.md) — token storage and rotation
104
+ - [`api-security.md`](api-security.md) — error responses that don't leak details
105
+ - [`../tooling/observability.md`](../tooling/observability.md) — redacting before logging