@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,128 @@
|
|
|
1
|
+
# CI and Pull Requests
|
|
2
|
+
|
|
3
|
+
## Required CI Gates
|
|
4
|
+
|
|
5
|
+
Every PR must pass these gates before merge:
|
|
6
|
+
|
|
7
|
+
- **Lint** — `pnpm lint` (Biome or ESLint)
|
|
8
|
+
- **Format** — `pnpm format --check` (Biome or Prettier)
|
|
9
|
+
- **Typecheck** — `pnpm typecheck` (`tsc --noEmit`)
|
|
10
|
+
- **Test** — `pnpm test` (Vitest)
|
|
11
|
+
|
|
12
|
+
E2E tests (Playwright) are recommended on critical-path branches but may be slower / less frequent.
|
|
13
|
+
|
|
14
|
+
## Example GitHub Actions Workflow
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
# .github/workflows/ci.yml
|
|
18
|
+
name: CI
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
pull_request:
|
|
22
|
+
push:
|
|
23
|
+
branches: [main]
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
check:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: pnpm/action-setup@v4
|
|
31
|
+
- uses: actions/setup-node@v4
|
|
32
|
+
with:
|
|
33
|
+
node-version: 22
|
|
34
|
+
cache: pnpm
|
|
35
|
+
- run: pnpm install --frozen-lockfile
|
|
36
|
+
- run: pnpm lint
|
|
37
|
+
- run: pnpm format --check
|
|
38
|
+
- run: pnpm typecheck
|
|
39
|
+
- run: pnpm test
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For monorepos with Nx, use `nx affected` to skip unchanged projects:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
- run: pnpm nx affected --target=lint --base=origin/main
|
|
46
|
+
- run: pnpm nx affected --target=typecheck --base=origin/main
|
|
47
|
+
- run: pnpm nx affected --target=test --base=origin/main
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Pull Request Sizing
|
|
51
|
+
|
|
52
|
+
### DO
|
|
53
|
+
|
|
54
|
+
- Keep PRs focused on one concern — easier to review, faster to land
|
|
55
|
+
- Split a refactor and a feature change into separate PRs
|
|
56
|
+
- Open a draft PR early to make work visible
|
|
57
|
+
|
|
58
|
+
### DO NOT
|
|
59
|
+
|
|
60
|
+
- Bundle unrelated changes into one PR
|
|
61
|
+
- Mix renames and behavior changes in the same commit
|
|
62
|
+
- Wait until everything is "done" to open a PR
|
|
63
|
+
|
|
64
|
+
### When Large PRs Are Acceptable
|
|
65
|
+
|
|
66
|
+
- Mechanical changes (codemod, formatter run) — easy to review despite size
|
|
67
|
+
- Migrations that cannot be split safely
|
|
68
|
+
- Initial scaffolds
|
|
69
|
+
|
|
70
|
+
## PR Titles and Descriptions
|
|
71
|
+
|
|
72
|
+
### Title
|
|
73
|
+
|
|
74
|
+
- Imperative mood: "Add invoice export", not "Added" or "Adds"
|
|
75
|
+
- Under ~70 characters
|
|
76
|
+
- Scoped if the project uses scopes: `invoices: add CSV export`
|
|
77
|
+
|
|
78
|
+
### Description
|
|
79
|
+
|
|
80
|
+
Always include:
|
|
81
|
+
- **Summary** — what changed and why, 1–3 bullets
|
|
82
|
+
- **Test plan** — how it was verified
|
|
83
|
+
- **Screenshots** — for UI changes
|
|
84
|
+
|
|
85
|
+
Use [`templates/pull-request-template.md`](../templates/pull-request-template.md) as a starting point.
|
|
86
|
+
|
|
87
|
+
## Merge Strategy
|
|
88
|
+
|
|
89
|
+
**Prefer squash merges.** This keeps `main`'s history linear and readable. Each PR becomes one commit on `main`.
|
|
90
|
+
|
|
91
|
+
- The PR title becomes the commit message — write it well
|
|
92
|
+
- The PR description becomes the commit body
|
|
93
|
+
- Branch commits are preserved in the PR itself for archeology
|
|
94
|
+
|
|
95
|
+
Avoid:
|
|
96
|
+
- Merge commits on `main` (history becomes a tangle)
|
|
97
|
+
- Rebase-and-merge without squashing (every WIP commit ends up on `main`)
|
|
98
|
+
|
|
99
|
+
## Branch Hygiene
|
|
100
|
+
|
|
101
|
+
- Branch from `main` (or `develop` if your project uses it)
|
|
102
|
+
- Use descriptive branch names: `feature/invoice-csv-export`, `fix/login-redirect-loop`
|
|
103
|
+
- Delete branches after merge
|
|
104
|
+
- Avoid long-lived feature branches — merge early behind a flag if needed
|
|
105
|
+
|
|
106
|
+
## Worktree-Based Workflows
|
|
107
|
+
|
|
108
|
+
For AI-assisted PR workflows, use [`@burglekitt/worktree`](https://github.com/burglekitt/worktree) — a CLI utility for managing git worktree branches. Multiple agents (or a human plus an agent) can work on parallel branches in isolated worktrees without stomping on each other.
|
|
109
|
+
|
|
110
|
+
## CI for Dependency Changes
|
|
111
|
+
|
|
112
|
+
When dependencies change, CI should:
|
|
113
|
+
- Run a full install (no partial cache)
|
|
114
|
+
- Run security audit (`pnpm audit --audit-level=high`)
|
|
115
|
+
- Re-run all gates
|
|
116
|
+
|
|
117
|
+
## PRIORITY
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Reviewability > Speed > Volume
|
|
121
|
+
Small PRs > Large PRs (unless mechanical)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## See Also
|
|
125
|
+
|
|
126
|
+
- [`templates/pull-request-template.md`](../templates/pull-request-template.md)
|
|
127
|
+
- [`architecture/refactoring.md`](../architecture/refactoring.md) — incremental refactor strategies
|
|
128
|
+
- [`tooling/biome.md`](biome.md) — lint and format gate
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Dates, Times, and Timezones
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- Use `Temporal` types for all date/time work — never the legacy `Date` object
|
|
6
|
+
- Store timestamps as UTC (`Temporal.Instant` serialized to ISO 8601)
|
|
7
|
+
- Convert to the user's timezone only at display time (`Temporal.ZonedDateTime`)
|
|
8
|
+
- Use `Temporal.PlainDate` for calendar dates (birthdays, holidays) — not timestamps
|
|
9
|
+
- Never assume the server and client share a timezone
|
|
10
|
+
|
|
11
|
+
## Preferred Library
|
|
12
|
+
|
|
13
|
+
Use [`@burglekitt/gmt`](https://github.com/burglekitt/gmt) — a thin layer over the modern [Temporal API](https://tc39.es/proposal-temporal/docs/) for handling date/time, timezones, and arithmetic. Includes a polyfill for environments without native Temporal yet.
|
|
14
|
+
|
|
15
|
+
Temporal replaces the legacy `Date` object and provides:
|
|
16
|
+
- Immutable types with explicit timezone handling
|
|
17
|
+
- Calendar-aware arithmetic that doesn't break on DST or leap seconds
|
|
18
|
+
- Clear separation between instants, zoned datetimes, plain dates, and durations
|
|
19
|
+
|
|
20
|
+
## Choosing the Right Temporal Type
|
|
21
|
+
|
|
22
|
+
| Concept | Type | Use for |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| A specific moment globally | `Temporal.Instant` | Server timestamps, event times, audit logs |
|
|
25
|
+
| A moment in a specific timezone | `Temporal.ZonedDateTime` | Scheduled local meetings, recurring events |
|
|
26
|
+
| A calendar date with no time | `Temporal.PlainDate` | Birthdays, holidays, due dates |
|
|
27
|
+
| Wall-clock time with no date | `Temporal.PlainTime` | Daily schedules, opening hours |
|
|
28
|
+
| Date + time without timezone | `Temporal.PlainDateTime` | Floating events ("3pm on the 19th, wherever") |
|
|
29
|
+
| A time span | `Temporal.Duration` | TTLs, intervals, elapsed time |
|
|
30
|
+
|
|
31
|
+
Choosing the right type prevents whole categories of bugs:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { Temporal } from "@burglekitt/gmt";
|
|
35
|
+
|
|
36
|
+
// Birthday — calendar date, no timezone (avoids "birthday shows wrong day in Pacific time")
|
|
37
|
+
const birthday = Temporal.PlainDate.from("1990-04-12");
|
|
38
|
+
|
|
39
|
+
// Event — specific instant in time
|
|
40
|
+
const eventAt = Temporal.Instant.from("2026-05-19T15:00:00Z");
|
|
41
|
+
|
|
42
|
+
// Recurring meeting — wall-clock time bound to a zone
|
|
43
|
+
const meeting = Temporal.ZonedDateTime.from("2026-05-19T10:00-04:00[America/New_York]");
|
|
44
|
+
|
|
45
|
+
// Duration — for arithmetic
|
|
46
|
+
const ttl = Temporal.Duration.from({ hours: 24 });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Storage
|
|
50
|
+
|
|
51
|
+
Store in UTC. The database column should be:
|
|
52
|
+
- PostgreSQL: `TIMESTAMP WITH TIME ZONE` (`timestamptz`)
|
|
53
|
+
- MySQL: `DATETIME` stored in UTC by convention
|
|
54
|
+
- Prisma: `DateTime` (maps to `timestamptz` on Postgres)
|
|
55
|
+
|
|
56
|
+
Convert between Prisma's `Date` and `Temporal.Instant` at the ORM boundary. Treat Temporal types as canonical inside your application.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// Reading from Prisma
|
|
60
|
+
const invoice = await db.invoice.findUnique({ where: { id } });
|
|
61
|
+
const dueAt = Temporal.Instant.fromEpochMilliseconds(invoice.dueDate.getTime());
|
|
62
|
+
|
|
63
|
+
// Writing to Prisma
|
|
64
|
+
await db.invoice.update({
|
|
65
|
+
where: { id },
|
|
66
|
+
data: { dueDate: new Date(dueAt.epochMilliseconds) },
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For calendar dates with no time component, use a `DATE` column and `Temporal.PlainDate`:
|
|
71
|
+
|
|
72
|
+
```prisma
|
|
73
|
+
model User {
|
|
74
|
+
birthday DateTime @db.Date // stored as a date, not a timestamp
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API Contracts
|
|
79
|
+
|
|
80
|
+
Serialize as ISO 8601 strings. Parse and validate with Zod, then convert to Temporal types:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { Temporal } from "@burglekitt/gmt";
|
|
84
|
+
import { z } from "zod";
|
|
85
|
+
|
|
86
|
+
const invoiceSchema = z.object({
|
|
87
|
+
id: z.string(),
|
|
88
|
+
dueDate: z.string().transform((s) => Temporal.Instant.from(s)),
|
|
89
|
+
createdAt: z.string().transform((s) => Temporal.Instant.from(s)),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
type Invoice = z.infer<typeof invoiceSchema>;
|
|
93
|
+
// Invoice["dueDate"] is now Temporal.Instant
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For calendar dates, use `PlainDate`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const userSchema = z.object({
|
|
100
|
+
birthday: z.string().transform((s) => Temporal.PlainDate.from(s)),
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Detecting User Timezone
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
108
|
+
// "America/New_York", "Europe/London", etc.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Store the user's preferred timezone on their profile if they may travel; fall back to browser-detected timezone otherwise.
|
|
112
|
+
|
|
113
|
+
## Display
|
|
114
|
+
|
|
115
|
+
Convert to the user's timezone at the edge — in the component, not in storage or transport:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { Temporal } from "@burglekitt/gmt";
|
|
119
|
+
|
|
120
|
+
interface InvoiceDateProps {
|
|
121
|
+
instant: Temporal.Instant;
|
|
122
|
+
timezone: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function InvoiceDate({ instant, timezone }: InvoiceDateProps) {
|
|
126
|
+
const zoned = instant.toZonedDateTimeISO(timezone);
|
|
127
|
+
const formatted = zoned.toLocaleString("en-US", {
|
|
128
|
+
dateStyle: "medium",
|
|
129
|
+
timeStyle: "short",
|
|
130
|
+
});
|
|
131
|
+
return (
|
|
132
|
+
<time dateTime={instant.toString()}>
|
|
133
|
+
{formatted}
|
|
134
|
+
</time>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Arithmetic
|
|
140
|
+
|
|
141
|
+
Temporal types are immutable — every operation returns a new value:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { Temporal } from "@burglekitt/gmt";
|
|
145
|
+
|
|
146
|
+
const created = Temporal.Instant.from("2026-05-19T15:00:00Z");
|
|
147
|
+
const dueIn30 = created.add({ days: 30 });
|
|
148
|
+
|
|
149
|
+
const today = Temporal.Now.plainDateISO();
|
|
150
|
+
const due = Temporal.PlainDate.from("2026-06-18");
|
|
151
|
+
const daysUntilDue = today.until(due, { largestUnit: "days" }).days;
|
|
152
|
+
const isOverdue = Temporal.PlainDate.compare(today, due) > 0;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
DST and timezone offsets are handled correctly because Temporal knows about calendars and timezones — manual millisecond math does not.
|
|
156
|
+
|
|
157
|
+
## Relative Time
|
|
158
|
+
|
|
159
|
+
For "2 hours ago" style display:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Temporal } from "@burglekitt/gmt";
|
|
163
|
+
|
|
164
|
+
const elapsed = Temporal.Now.instant().since(invoice.createdAt);
|
|
165
|
+
// elapsed is a Temporal.Duration — format with Intl.RelativeTimeFormat
|
|
166
|
+
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
167
|
+
rtf.format(-elapsed.total({ unit: "hour" }), "hour");
|
|
168
|
+
// "2 hours ago"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## DO NOT
|
|
172
|
+
|
|
173
|
+
- Use the legacy `Date` object for new code — Temporal is strictly better
|
|
174
|
+
- Use `Date.parse` or `new Date("string")` — parse with `Temporal.Instant.from` etc.
|
|
175
|
+
- Compare dates with `<` / `>` operators — use `Temporal.Instant.compare` / `Temporal.PlainDate.compare`
|
|
176
|
+
- Do millisecond arithmetic — use `.add()` / `.subtract()` / `.until()` / `.since()`
|
|
177
|
+
- Mix `Date` and `Temporal` types — convert at the boundary, stay Temporal internally
|
|
178
|
+
- Use a timestamp where a calendar date belongs (causes timezone display bugs)
|
|
179
|
+
- Trust client-supplied timestamps for security-critical logic — derive on the server
|
|
180
|
+
|
|
181
|
+
## PRIORITY
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Temporal types > Date object
|
|
185
|
+
UTC storage > Local storage
|
|
186
|
+
Display at the edge > Display everywhere
|
|
187
|
+
Right type for the concept > Generic timestamp
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## See Also
|
|
191
|
+
|
|
192
|
+
- [`../typescript/validation.md`](../typescript/validation.md) — Zod schemas with Temporal parsing
|
|
193
|
+
- [`prisma.md`](prisma.md) — Prisma date columns
|
|
194
|
+
- [`dependencies.md`](dependencies.md) — when to prefer native APIs
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Every dependency is a long-term liability. Add one only when the value clearly exceeds the cost.
|
|
6
|
+
|
|
7
|
+
## DO
|
|
8
|
+
|
|
9
|
+
- Prefer browser-native or runtime-native APIs when they exist
|
|
10
|
+
- Prefer mature, well-maintained libraries with active commits and a healthy issue tracker
|
|
11
|
+
- Check bundle impact before adding any client-side dependency (`bundlephobia.com`)
|
|
12
|
+
- Commit a lockfile (`pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`)
|
|
13
|
+
- Pin exact versions for tooling that breaks easily across minor updates (linters, formatters, build tools)
|
|
14
|
+
- Audit dependencies periodically (`pnpm audit`, Dependabot, Renovate)
|
|
15
|
+
|
|
16
|
+
## DO NOT
|
|
17
|
+
|
|
18
|
+
- Add a dependency to avoid writing 5–20 lines of straightforward code (`left-pad`, `is-array`, `is-odd`)
|
|
19
|
+
- Add a dependency for a feature already provided by the platform (`lodash.clone` → `structuredClone`, `axios` → `fetch`)
|
|
20
|
+
- Add unmaintained packages (no commits in 2+ years, open issues piling up, last release stale)
|
|
21
|
+
- Add packages with license incompatibilities (GPL into proprietary code, etc.)
|
|
22
|
+
- Add packages that pull in massive transitive dependency trees for trivial features
|
|
23
|
+
|
|
24
|
+
## Native Over Library
|
|
25
|
+
|
|
26
|
+
Prefer the platform when it does the job:
|
|
27
|
+
|
|
28
|
+
| Instead of… | Use… |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `lodash.clone`, `lodash.cloneDeep` | `structuredClone` |
|
|
31
|
+
| `axios` | `fetch` |
|
|
32
|
+
| `moment`, `date-fns` (for simple cases) | `Intl.DateTimeFormat`, `Intl.RelativeTimeFormat` |
|
|
33
|
+
| `uuid` (for non-cryptographic IDs) | `crypto.randomUUID()` |
|
|
34
|
+
| `query-string` | `URLSearchParams` |
|
|
35
|
+
| `classnames` | `clsx` (smaller) or template strings |
|
|
36
|
+
| `lodash.debounce`, `lodash.throttle` | tiny custom util or `@uidotdev/usehooks` |
|
|
37
|
+
|
|
38
|
+
## Evaluating a Library
|
|
39
|
+
|
|
40
|
+
Before adding, check:
|
|
41
|
+
|
|
42
|
+
1. **Maintenance** — last release date, commit frequency, issue response time
|
|
43
|
+
2. **Adoption** — weekly downloads on npm, GitHub stars relative to age
|
|
44
|
+
3. **Bundle size** — `bundlephobia.com/package/<name>` for minified + gzipped size
|
|
45
|
+
4. **Transitive deps** — what does it pull in?
|
|
46
|
+
5. **Alternatives** — is there a smaller / more focused option?
|
|
47
|
+
6. **License** — compatible with your project
|
|
48
|
+
|
|
49
|
+
## Bundle Impact Tools
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Inspect the size of a published package
|
|
53
|
+
npx bundlephobia <package-name>
|
|
54
|
+
|
|
55
|
+
# Visualize what's in your bundle
|
|
56
|
+
pnpm dlx source-map-explorer dist/assets/*.js
|
|
57
|
+
|
|
58
|
+
# Vite plugin for bundle analysis
|
|
59
|
+
pnpm add -D rollup-plugin-visualizer
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## When to Vendor or Fork
|
|
63
|
+
|
|
64
|
+
Vendor (copy into your repo) when:
|
|
65
|
+
- The library is unmaintained but you need a small piece of it
|
|
66
|
+
- You need a small modification the maintainer won't accept
|
|
67
|
+
- The package is so small (< 50 lines) that the dependency overhead exceeds the code
|
|
68
|
+
|
|
69
|
+
Fork when:
|
|
70
|
+
- You need a sustained, significant modification
|
|
71
|
+
- You can commit to maintaining the fork
|
|
72
|
+
|
|
73
|
+
## Lockfile Discipline
|
|
74
|
+
|
|
75
|
+
- Always commit the lockfile
|
|
76
|
+
- Use the same package manager across the team (pnpm preferred — strict, fast, disk-efficient)
|
|
77
|
+
- Use `pnpm install --frozen-lockfile` in CI
|
|
78
|
+
|
|
79
|
+
## PRIORITY
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Native API > Tiny focused library > Mature comprehensive library > Custom code
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## See Also
|
|
86
|
+
|
|
87
|
+
- [`biome.md`](biome.md) — replacing ESLint + Prettier with one tool
|
|
88
|
+
- [`vite.md`](vite.md) — bundle analysis
|
|
89
|
+
- [`ci.md`](ci.md) — security audits in CI
|
|
90
|
+
- [`dates.md`](dates.md) — date library recommendation
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Nx
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Nx is the build system and task orchestrator for monorepos. It provides:
|
|
6
|
+
|
|
7
|
+
- Affected command computation (only build/test what changed)
|
|
8
|
+
- Local and remote caching
|
|
9
|
+
- Generators for scaffolding
|
|
10
|
+
- Dependency graph visualization
|
|
11
|
+
|
|
12
|
+
## Key Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Run a target for a specific project
|
|
16
|
+
nx build web
|
|
17
|
+
nx test api
|
|
18
|
+
nx lint ui
|
|
19
|
+
|
|
20
|
+
# Run a target for all projects
|
|
21
|
+
nx run-many --target=build
|
|
22
|
+
nx run-many --target=test --parallel=3
|
|
23
|
+
|
|
24
|
+
# Only run for affected projects (CI-optimized)
|
|
25
|
+
nx affected --target=build --base=main
|
|
26
|
+
nx affected --target=test --base=main
|
|
27
|
+
|
|
28
|
+
# Visualize the dependency graph
|
|
29
|
+
nx graph
|
|
30
|
+
|
|
31
|
+
# Generate code
|
|
32
|
+
nx generate @nx/react:component InvoiceTable --project=web
|
|
33
|
+
nx generate @nx/node:library schemas --directory=packages/schemas
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Project Configuration
|
|
37
|
+
|
|
38
|
+
Each project has a `project.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"name": "web",
|
|
43
|
+
"projectType": "application",
|
|
44
|
+
"targets": {
|
|
45
|
+
"build": {
|
|
46
|
+
"executor": "@nx/vite:build",
|
|
47
|
+
"options": { "outputPath": "dist/apps/web" }
|
|
48
|
+
},
|
|
49
|
+
"dev": {
|
|
50
|
+
"executor": "@nx/vite:dev-server",
|
|
51
|
+
"options": { "buildTarget": "web:build" }
|
|
52
|
+
},
|
|
53
|
+
"test": {
|
|
54
|
+
"executor": "@nx/vite:test"
|
|
55
|
+
},
|
|
56
|
+
"lint": {
|
|
57
|
+
"executor": "@nx/eslint:lint"
|
|
58
|
+
},
|
|
59
|
+
"typecheck": {
|
|
60
|
+
"executor": "nx:run-commands",
|
|
61
|
+
"options": { "command": "tsc --noEmit" }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Caching
|
|
68
|
+
|
|
69
|
+
Nx caches task outputs by default. CI should use Nx Cloud or remote cache:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# nx.json
|
|
73
|
+
{
|
|
74
|
+
"tasksRunnerOptions": {
|
|
75
|
+
"default": {
|
|
76
|
+
"runner": "nx-cloud",
|
|
77
|
+
"options": {
|
|
78
|
+
"cacheableOperations": ["build", "test", "lint", "typecheck"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Enforcing Boundaries
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
// nx.json
|
|
89
|
+
{
|
|
90
|
+
"targetDefaults": {},
|
|
91
|
+
"namedInputs": {},
|
|
92
|
+
"generators": {
|
|
93
|
+
"@nx/react": {
|
|
94
|
+
"library": { "bundler": "vite" }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `@nx/eslint-plugin` with `enforce-module-boundaries` to prevent cross-boundary imports.
|
|
101
|
+
|
|
102
|
+
## DO NOT
|
|
103
|
+
|
|
104
|
+
- Skip `nx affected` in CI — it is the primary value proposition of Nx
|
|
105
|
+
- Manually run all tests when only one project changed
|
|
106
|
+
- Create circular dependencies between projects
|
|
107
|
+
- Publish packages unless explicitly required for external consumption
|
|
108
|
+
|
|
109
|
+
## See Also
|
|
110
|
+
|
|
111
|
+
- [`../architecture/monorepos.md`](../architecture/monorepos.md) — when and how to use a monorepo
|
|
112
|
+
- [`../architecture/dependency-boundaries.md`](../architecture/dependency-boundaries.md) — enforcing imports
|
|
113
|
+
- [`ci.md`](ci.md) — `nx affected` in CI
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Observability — Logging, Metrics, Errors
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- Log with structured context — never raw strings without metadata
|
|
6
|
+
- Never log PII, secrets, or sensitive payloads
|
|
7
|
+
- Send errors to an error monitoring service (Sentry, Datadog, etc.)
|
|
8
|
+
- Use levels intentionally — `error` is not the same as `warn`
|
|
9
|
+
- Correlate logs with a request / trace ID
|
|
10
|
+
|
|
11
|
+
## Logging Levels
|
|
12
|
+
|
|
13
|
+
| Level | Use for |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `error` | An operation failed and the system could not complete the user's intent |
|
|
16
|
+
| `warn` | Something unexpected, but the operation continued (degraded mode, fallback) |
|
|
17
|
+
| `info` | Normal operational events — startup, shutdown, important state changes |
|
|
18
|
+
| `debug` | Detail useful during investigation; off in production |
|
|
19
|
+
|
|
20
|
+
`error` should page someone. `warn` should not. Use them accordingly.
|
|
21
|
+
|
|
22
|
+
## Structured Logging
|
|
23
|
+
|
|
24
|
+
Log objects, not strings. Structured logs are searchable and indexable in any modern log aggregator:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// BAD — string concatenation, ungrep-able
|
|
28
|
+
logger.info(`User ${userId} created invoice ${invoiceId} for $${amount}`);
|
|
29
|
+
|
|
30
|
+
// GOOD — structured fields
|
|
31
|
+
logger.info("invoice.created", {
|
|
32
|
+
userId,
|
|
33
|
+
invoiceId,
|
|
34
|
+
amount,
|
|
35
|
+
currency: "USD",
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Prefer libraries that emit JSON in production:
|
|
40
|
+
|
|
41
|
+
- **pino** — fast, JSON output, Node.js
|
|
42
|
+
- **winston** — flexible, multi-transport
|
|
43
|
+
- **slog** — built into Go (mentioned for backend parity)
|
|
44
|
+
|
|
45
|
+
## Context Propagation
|
|
46
|
+
|
|
47
|
+
Attach request / trace / user context once at the boundary, then log everywhere:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// Express / Hono middleware — set request context once
|
|
51
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
52
|
+
|
|
53
|
+
const logContext = new AsyncLocalStorage<{ requestId: string; userId?: string }>();
|
|
54
|
+
|
|
55
|
+
app.use((req, res, next) => {
|
|
56
|
+
const requestId = req.headers["x-request-id"] ?? crypto.randomUUID();
|
|
57
|
+
logContext.run({ requestId, userId: req.user?.id }, () => next());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Anywhere in the request — context is automatically included
|
|
61
|
+
function log(message: string, fields: Record<string, unknown> = {}) {
|
|
62
|
+
const ctx = logContext.getStore();
|
|
63
|
+
logger.info(message, { ...ctx, ...fields });
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What Not to Log
|
|
68
|
+
|
|
69
|
+
Never log:
|
|
70
|
+
|
|
71
|
+
- Passwords (even hashed) — exposure risk if logs leak
|
|
72
|
+
- Tokens, API keys, session IDs
|
|
73
|
+
- Personal data — email, phone, SSN, addresses — unless legally required and access-controlled
|
|
74
|
+
- Full request/response bodies that might contain PII
|
|
75
|
+
- Stripe / payment data — PCI compliance forbids this
|
|
76
|
+
|
|
77
|
+
Redact at the source:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
function redactUser(user: User) {
|
|
81
|
+
return { id: user.id, role: user.role }; // omit email, name, etc.
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logger.info("user.action", { user: redactUser(user), action: "login" });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Error Monitoring
|
|
88
|
+
|
|
89
|
+
Use a hosted service (Sentry, Datadog, Honeybadger, Bugsnag). Setup once at app entry:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// app/providers.tsx (client) or server entry
|
|
93
|
+
import * as Sentry from "@sentry/react";
|
|
94
|
+
|
|
95
|
+
Sentry.init({
|
|
96
|
+
dsn: env.SENTRY_DSN,
|
|
97
|
+
environment: env.NODE_ENV,
|
|
98
|
+
tracesSampleRate: 0.1,
|
|
99
|
+
replaysSessionSampleRate: 0.01,
|
|
100
|
+
beforeSend(event) {
|
|
101
|
+
// strip PII before sending
|
|
102
|
+
if (event.user) delete event.user.email;
|
|
103
|
+
return event;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Send errors from:
|
|
109
|
+
|
|
110
|
+
- React error boundaries (`onError` callback) — see [`react/error-boundaries.md`](../react/error-boundaries.md)
|
|
111
|
+
- Server-side error middleware
|
|
112
|
+
- TanStack Query `onError` callbacks
|
|
113
|
+
- Unhandled promise rejections / uncaught exceptions
|
|
114
|
+
|
|
115
|
+
## Correlating Frontend and Backend
|
|
116
|
+
|
|
117
|
+
Attach a request ID header on every API call. The backend echoes it in responses and logs it. Sentry / Datadog can then stitch frontend + backend traces:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// Frontend
|
|
121
|
+
const requestId = crypto.randomUUID();
|
|
122
|
+
const response = await fetch("/api/invoices", {
|
|
123
|
+
headers: { "X-Request-ID": requestId },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// On error — include requestId in the error report
|
|
127
|
+
Sentry.captureException(error, { tags: { requestId } });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Metrics
|
|
131
|
+
|
|
132
|
+
For latency, throughput, and business metrics, prefer:
|
|
133
|
+
|
|
134
|
+
- **Vercel Analytics / Cloudflare Analytics** — out-of-the-box for frontend
|
|
135
|
+
- **OpenTelemetry** — vendor-neutral, integrates with most backends
|
|
136
|
+
- **Prometheus / Grafana** — self-hosted
|
|
137
|
+
|
|
138
|
+
Track:
|
|
139
|
+
- Request latency (p50, p95, p99)
|
|
140
|
+
- Error rate by route
|
|
141
|
+
- Critical business events (signups, conversions, purchases)
|
|
142
|
+
|
|
143
|
+
## Web Vitals
|
|
144
|
+
|
|
145
|
+
For frontend perceived performance, monitor Core Web Vitals:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { onCLS, onFID, onLCP, onINP, onTTFB } from "web-vitals";
|
|
149
|
+
|
|
150
|
+
function send(metric: { name: string; value: number; id: string }) {
|
|
151
|
+
fetch("/api/metrics", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
body: JSON.stringify(metric),
|
|
154
|
+
headers: { "Content-Type": "application/json" },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onCLS(send);
|
|
159
|
+
onLCP(send);
|
|
160
|
+
onINP(send);
|
|
161
|
+
onTTFB(send);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## DO NOT
|
|
165
|
+
|
|
166
|
+
- Log to `console.log` in production code — use a logger
|
|
167
|
+
- Catch errors and log without re-throwing (silently swallows failures)
|
|
168
|
+
- Log inside hot loops without rate limiting
|
|
169
|
+
- Send 100% of traffic to a paid APM — sample appropriately
|
|
170
|
+
|
|
171
|
+
## PRIORITY
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Structured logs > String logs
|
|
175
|
+
Errors reported > Errors only logged
|
|
176
|
+
Correlated traces > Isolated logs
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## See Also
|
|
180
|
+
|
|
181
|
+
- [`typescript/error-handling.md`](../typescript/error-handling.md) — error patterns
|
|
182
|
+
- [`react/error-boundaries.md`](../react/error-boundaries.md) — React error reporting
|
|
183
|
+
- [`security/secrets.md`](../security/secrets.md) — what not to log
|