@skyf0xx/hedgehog 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -124,6 +124,7 @@ Hedgehog is a package of agents and skills. An opinionated stack is used so the
124
124
  | Backend | NestJS | Modules naturally mirror Hedgehog's build progression. |
125
125
  | ORM | Drizzle + drizzle-zod | Database schema is the single source of truth. |
126
126
  | Database | PostgreSQL | Simple, relational, predictable. |
127
+ | Local infra | Docker Compose | Postgres/Redis run identically on every machine. |
127
128
  | Platform | Railway | Infrastructure is available from the first commit. |
128
129
  | API contract | ts-rest | Contracts are code, not documentation. |
129
130
  | Validation | Zod | One schema for runtime and compile time. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyf0xx/hedgehog",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Install the Hedgehog build discipline (agents + skills) into a repo.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -13,6 +13,9 @@
13
13
  "bin": {
14
14
  "hedgehog": "bin/cli.mjs"
15
15
  },
16
+ "scripts": {
17
+ "release": "npm version patch -m \"chore: bump version to %s\""
18
+ },
16
19
  "files": [
17
20
  "bin",
18
21
  "src/agents",
@@ -29,6 +29,7 @@ One opinionated stack, applied the same way on every project:
29
29
  | Backend framework | NestJS |
30
30
  | ORM | Drizzle (+ `drizzle-zod`) |
31
31
  | Database | PostgreSQL |
32
+ | Local infra | Docker Compose (Postgres + Redis) |
32
33
  | Platform | Railway |
33
34
  | API contract | ts-rest |
34
35
  | Validation | Zod |
@@ -106,6 +107,13 @@ project misconfiguration or a corrupted pnpm store — check **Known issue:
106
107
  esbuild postinstall version mismatch** below first; this is a known,
107
108
  deterministic collision, not something to misdiagnose from scratch.
108
109
 
110
+ Confirm Docker is available (`docker --version`) before step 1. Local
111
+ Postgres/Redis run through Docker Compose on every host OS — see **Local
112
+ infra: Docker, always** below. No Docker installed: stop and point the
113
+ user to installing Docker Desktop (macOS/Windows) or Docker
114
+ Engine (Linux) rather than falling back to a natively-installed
115
+ Postgres/Redis.
116
+
109
117
  ## Steps (run in sequence, one commit per step)
110
118
 
111
119
  ### 1. Nx workspace + `packages/config`
@@ -134,6 +142,18 @@ root; if present, delete it and run `pnpm install` to regenerate
134
142
  `pnpm-lock.yaml` before continuing to step 2. Don't assume `nx init`
135
143
  respects the locked package manager — verify.
136
144
 
145
+ `nx init` also does not create `pnpm-workspace.yaml`. Without it, pnpm
146
+ doesn't recognize `packages/*` or `apps/*` as workspace members —
147
+ `pnpm add -w` fails with `ERR_PNPM_ADDING_TO_ROOT`-adjacent errors, and
148
+ cross-package `pnpm add` for a lib silently installs to the wrong place.
149
+ Create it manually right after `nx init`, before the first `pnpm add`:
150
+
151
+ ```yaml
152
+ packages:
153
+ - 'packages/*'
154
+ - 'apps/*'
155
+ ```
156
+
137
157
  Then generate the first lib. The **first** `@nx/js:lib` call materializes
138
158
  the whole workspace shape (`tsconfig.base.json`, root `eslint.config.mjs`,
139
159
  `.prettierrc`, `vitest.workspace.ts`, the `packages/` layout, and the
@@ -150,7 +170,16 @@ files:
150
170
  - `packages/config/eslint-base.js` — flat config, extended by every
151
171
  app/lib. Include `@nx/enforce-module-boundaries` and `depConstraints`
152
172
  from Enforcement wiring below, verbatim.
153
- - `packages/config/prettier.js` — includes `prettier-plugin-tailwindcss`.
173
+ - `packages/config/prettier.js` — **don't** add `prettier-plugin-tailwindcss`
174
+ here. The plugin parses every file prettier touches (not just files with
175
+ Tailwind classes) and throws (`TypeError: e.charAt is not a function` /
176
+ `a.startsWith is not a function`) on any file when no Tailwind config is
177
+ resolvable yet — which is every project from step 1 through step 5, since
178
+ Tailwind doesn't arrive until `apps/web` in step 6. Loading it globally
179
+ here breaks `nx format:write` and any bare `prettier --check` for the
180
+ first five steps of every project. Add the plugin to `apps/web`'s own
181
+ prettier setup (extending this base config) once Tailwind exists, not to
182
+ the shared base.
154
183
  - `packages/config/env.schema.ts` — the Zod env schema from Enforcement
155
184
  wiring below (`DATABASE_URL`, `BETTER_AUTH_SECRET`, `REDIS_URL`,
156
185
  `NODE_ENV`; extend per project as new infra is added later).
@@ -158,6 +187,53 @@ files:
158
187
  declares the project tags table below (`scope:*`, `type:*`) as comments
159
188
  or a lookup, so every later generator step tags its project correctly.
160
189
 
190
+ Root `eslint.config.mjs` is itself ESM (`.mjs`), which surfaces a Node
191
+ warning ("Module type of file... is not specified") unless the root
192
+ `package.json` declares `"type": "module"`. Setting that is correct and
193
+ worth doing — but it changes how Node resolves *every* plain `.js` file
194
+ in the repo from CommonJS to ESM by default. Several files later steps
195
+ generate are CommonJS (`require`/`module.exports`) and will break with
196
+ `ReferenceError: require is not defined` or `module is not defined` the
197
+ moment `"type": "module"` is set: `apps/*/webpack.config.js`,
198
+ `commitlint.config.js`, and any hand-written CommonJS tool script (e.g.
199
+ `tools/phase-gate.js`). Rename each to `.cjs` as it's created (or convert
200
+ its content to ESM, as `next.config.js` supports natively via
201
+ `export default`) rather than discovering the break later when `nx
202
+ graph` or a generator that depends on the project graph fails
203
+ opaquely — Nx surfaces this as "Failed to process project graph" pointing
204
+ at the offending file, not as a module-system explanation.
205
+
206
+ Add a root `docker-compose.yml` provisioning Postgres and Redis for local
207
+ dev, regardless of host OS (macOS, Windows, Linux) — see **Local infra:
208
+ Docker, always** below for why this isn't optional. `DATABASE_URL` and
209
+ `REDIS_URL` in `.env` point at the compose services from the first
210
+ commit, matching the env schema below.
211
+
212
+ ```yaml
213
+ services:
214
+ postgres:
215
+ image: postgres:16-alpine
216
+ environment:
217
+ POSTGRES_USER: postgres
218
+ POSTGRES_PASSWORD: postgres
219
+ POSTGRES_DB: app
220
+ ports:
221
+ - '5432:5432'
222
+ volumes:
223
+ - postgres-data:/var/lib/postgresql/data
224
+
225
+ redis:
226
+ image: redis:7-alpine
227
+ ports:
228
+ - '6379:6379'
229
+ volumes:
230
+ - redis-data:/data
231
+
232
+ volumes:
233
+ postgres-data:
234
+ redis-data:
235
+ ```
236
+
161
237
  Add an `esbuild` override to root `package.json` in this step, before step
162
238
  2 installs `drizzle-kit` — see **Known issue: esbuild postinstall version
163
239
  mismatch** below for why.
@@ -209,6 +285,20 @@ structured logging. Call `loadEnv()` at the top of `apps/api/src/main.ts`.
209
285
  Tag: `scope:api`. No controllers beyond a health check — domain
210
286
  controllers arrive per module in Phase A.
211
287
 
288
+ `@nx/nest:app` also scaffolds a companion `apps/api-e2e` project wired to
289
+ Jest (`jest.config.cts`), not Vitest — inconsistent with the locked stack.
290
+ Convert it: delete the Jest config, add a `vitest.config.mts` (mirroring
291
+ `packages/db`'s), and give `apps/api-e2e`'s `tsconfig.spec.json` the same
292
+ `composite`/`declaration` treatment as above. Rename its target from the
293
+ plugin-inferred `test` to an explicit `e2e` — an HTTP e2e suite needs a
294
+ live server (`dependsOn: ["api:build", "api:serve"]`), and if it keeps the
295
+ default `test` name, `nx affected -t test` (what lefthook's pre-commit
296
+ hook runs on every commit, per the Commit gate below) will try to boot a
297
+ server on every commit. Exclude `apps/api-e2e` from the `@nx/vitest`
298
+ plugin's auto-inference in `nx.json` (`"exclude": ["apps/api-e2e/**"]` on
299
+ that plugin entry) so it stops registering a `test` target for this
300
+ project at all, keeping only the manually-defined `e2e` target.
301
+
212
302
  Commit: `feat(api): nest shell + global guard + pino`
213
303
 
214
304
  ### 5. `apps/worker` — BullMQ seam (Redis, no consumers yet)
@@ -230,17 +320,73 @@ Commit: `feat(worker): bullmq seam, no consumers`
230
320
  ```bash
231
321
  npx nx g @nx/next:app apps/web
232
322
  pnpm add @tanstack/react-query
233
- pnpm dlx shadcn@latest init
234
323
  ```
235
324
 
236
- Wire the TanStack Query provider at the root layout. `shadcn init` writes
237
- `apps/web`'s base theme (CSS variables for color, radius, light/dark
238
- mode) — set the actual palette here, once, rather than leaving ShadCN's
239
- placeholder values for `ui-builder` to inherit unnoticed on the first
240
- screen. Light/dark mode toggle wiring belongs here too, not as a
241
- per-screen decision later. No screens or hooks yet — Phase B doesn't
325
+ **Do not run `pnpm dlx shadcn@latest init` (or `shadcn add`) against
326
+ `apps/web` directly.** The shadcn CLI hard-requires a `package.json` in
327
+ its target directory to detect the project; an Nx-generated app under
328
+ this stack has no per-app `package.json` (dependencies live at the
329
+ workspace root). Finding none, the CLI's default behavior is to offer —
330
+ and on `--yes`, silently proceed — to scaffold a *brand-new*
331
+ `create-next-app` project **inside** `apps/web`, complete with its own
332
+ nested `.git`, its own lockfile, and its own `package.json`. This doesn't
333
+ error; it succeeds and leaves a corrupted nested project (e.g.
334
+ `apps/web/web/`) that has to be manually detected and deleted. There is
335
+ currently no supported non-interactive flag that makes shadcn's CLI
336
+ target an existing package-json-less directory in place.
337
+
338
+ Build the base theme by hand instead: write `apps/web/components.json`
339
+ directly (style, aliases, `tailwind.css` path — the `css` field), add
340
+ `class-variance-authority`, `clsx`, `tailwind-merge`, `lucide-react`, and
341
+ `@radix-ui/react-slot` as dependencies, write `apps/web/src/lib/utils.ts`
342
+ (the standard `cn()` helper), and hand-write the CSS variable theme block
343
+ (light/dark, using shadcn's published default token values) into
344
+ `apps/web`'s global stylesheet. Individual components (e.g. `button.tsx`)
345
+ can then be hand-written from shadcn's published source for that
346
+ component — small, stable, well-known files — rather than fetched via the
347
+ CLI. Set the actual palette here, once, rather than leaving placeholder
348
+ values for `ui-builder` to inherit unnoticed on the first screen. Light/
349
+ dark mode toggle wiring belongs here too, not as a per-screen decision
350
+ later.
351
+
352
+ Wire the TanStack Query provider at the root layout (App Router: a
353
+ `'use client'` `Providers` wrapper component, since the root layout
354
+ itself is a server component). No screens or hooks yet — Phase B doesn't
242
355
  start until Phase A closes for at least one module. Tag: `scope:web`.
243
356
 
357
+ **Set `NODE_ENV` explicitly on the `build` target, or the production
358
+ build silently runs in dev mode under Nx.** Nx's task runner sets
359
+ `NODE_ENV=development` by default for every task unless a target
360
+ overrides it — including a plain `nx:run-commands` target running `next
361
+ build`. Next.js's production build pipeline assumes `NODE_ENV=production`;
362
+ running it under `development` produces a build that compiles and
363
+ appears to succeed on individual pages but crashes prerendering the
364
+ auto-generated `/_global-error` route with `TypeError: Cannot read
365
+ properties of null (reading 'useContext')` — a React-internals mismatch
366
+ from the dev/prod build split, not an app bug. This only reproduces
367
+ through `nx run web:build`; the identical `next build --webpack` run
368
+ directly from `apps/web` succeeds, because a bare shell has no `NODE_ENV`
369
+ set and Next defaults it correctly itself — which makes the symptom look
370
+ Nx-specific and environment-related rather than what it is (a task-runner
371
+ default silently overriding a value the build assumes). Set it on the
372
+ `build` target only, in `apps/web/project.json`:
373
+
374
+ ```json
375
+ {
376
+ "targets": {
377
+ "build": {
378
+ "options": {
379
+ "command": "next build --webpack",
380
+ "env": { "NODE_ENV": "production" }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ ```
386
+
387
+ Leave `dev` alone — it correctly wants `NODE_ENV=development`, which is
388
+ also what Nx already defaults it to.
389
+
244
390
  Commit: `feat(web): next shell + query provider + base theme`
245
391
 
246
392
  ### 7. `apps/mobile` — Expo shell (only if mobile is in scope)
@@ -315,6 +461,51 @@ e.g. `libs/orders/port`, `libs/orders/repository`, `libs/orders/service`.
315
461
  Building out of order (a controller before a service exists, a hook
316
462
  reaching into `apps/api` directly) fails `nx lint`.
317
463
 
464
+ The rule list above is illustrative, not exhaustive — `@nx/enforce-
465
+ module-boundaries` denies by default for any project whose tag isn't
466
+ named as a `sourceTag` in some `depConstraints` entry ("A project without
467
+ tags matching at least one constraint cannot depend on any libraries").
468
+ `packages/db` (`scope:db`, `type:adapter`) and `apps/api` (`scope:api`)
469
+ both hit this the moment they're generated and tagged in steps 2 and 4,
470
+ because neither `type:adapter` nor `scope:api` has an entry above. Add
471
+ constraints for every tag combination as it's introduced, not only the
472
+ ones in the illustrative list — at minimum `type:adapter` (needs
473
+ `type:util`, for `packages/config`) and `scope:api` (needs `type:port`,
474
+ `type:service`, `type:util` — never `scope:db` directly; `apps/api`
475
+ reaches storage through a module's repository/service, not by importing
476
+ `packages/db` itself).
477
+
478
+ ### Typecheck target (required before the commit gate can work)
479
+
480
+ `lefthook.yml`'s pre-commit below runs `nx affected -t typecheck`, but
481
+ `@nx/js:lib` and `@nx/nest:app` don't register a `typecheck` target on
482
+ their own — it only exists once the `@nx/js/typescript` plugin is
483
+ registered in `nx.json`, added in step 1 alongside the eslint and
484
+ vitest plugins:
485
+
486
+ ```json
487
+ {
488
+ "plugin": "@nx/js/typescript",
489
+ "options": { "typecheck": { "targetName": "typecheck" } }
490
+ }
491
+ ```
492
+
493
+ Registering the plugin isn't sufficient on its own, either: `tsc
494
+ --build`'s composite-project mode (what the inferred `typecheck` target
495
+ actually runs) requires every `tsconfig.lib.json` and `tsconfig.spec.json`
496
+ it touches to set `"composite": true` and `"declaration": true`. The
497
+ generators don't set these — every `@nx/js:lib` and `@nx/nest:app`
498
+ project needs both added by hand to `tsconfig.lib.json` /
499
+ `tsconfig.app.json` and `tsconfig.spec.json` right after generation, or
500
+ `nx run <proj>:typecheck` fails immediately with `TS5069: Option
501
+ 'emitDeclarationOnly' cannot be specified without specifying option
502
+ 'declaration' or option 'composite'`. If a project's spec file imports
503
+ from its own lib source (the common case), `tsconfig.spec.json` also
504
+ needs an explicit `references` entry pointing at `tsconfig.lib.json`, or
505
+ `tsc` reports `TS6307: File '...' is not listed within the file list of
506
+ project`. Do this for `packages/config` in step 1 so the pattern is
507
+ established before every later step repeats it.
508
+
318
509
  ### Commit gate (lefthook + commitlint)
319
510
 
320
511
  Enforces one coherent change, tested, conventionally committed. Runs on
@@ -441,10 +632,30 @@ One shared config, extended everywhere:
441
632
 
442
633
  - `packages/config/eslint-base.js` — flat config, extended by every
443
634
  app/lib.
444
- - `packages/config/prettier.js` — includes `prettier-plugin-tailwindcss`.
635
+ - `packages/config/prettier.js` — the shared base, *without*
636
+ `prettier-plugin-tailwindcss` (see step 1 — it belongs in `apps/web`'s
637
+ own config once Tailwind exists, not the shared base).
445
638
 
446
639
  A per-app override request signals to fix the base config at the source.
447
640
 
641
+ ## Local infra: Docker, always
642
+
643
+ Postgres and Redis run through the `docker-compose.yml` from step 1 on
644
+ every project, on every host OS — macOS, Windows, Linux alike. This isn't
645
+ a convenience default; it's what makes "clone the repo, run the stack"
646
+ mechanically true regardless of who's building. A natively-installed
647
+ Postgres or Redis (Homebrew, an existing Windows service, a system
648
+ package) is a per-machine setup step that isn't in the commit history and
649
+ isn't reproducible on the next machine — exactly the kind of
650
+ tribal-knowledge dependency Hedgehog's enforcement exists to remove.
651
+
652
+ Don't offer a "native install" path as an alternative, even if a
653
+ contributor already has Postgres running locally for another project. One
654
+ mechanism, every machine: `docker compose up -d` before `pnpm install`,
655
+ every time. If Docker genuinely can't run on a target machine, that's a
656
+ platform-support gap to raise, not a reason to quietly fall back to a
657
+ native install for that one contributor.
658
+
448
659
  ## Known issue: esbuild postinstall version mismatch
449
660
 
450
661
  `@nx/vite` (step 1) declares `esbuild` as an *optional* peer dependency
@@ -494,3 +705,7 @@ gated by lefthook, each its own commit.
494
705
  - Each of the 7 steps is its own commit, in order — same unit-of-work
495
706
  discipline as every other step in the discipline, even though this is
496
707
  infra rather than a domain module.
708
+ - Local Postgres/Redis always run through the `docker-compose.yml` from
709
+ step 1, on every host OS. Never substitute a natively-installed
710
+ Postgres/Redis, even to match a contributor's existing local setup —
711
+ see **Local infra: Docker, always**.
@@ -81,11 +81,13 @@ steps from memory:
81
81
  ### Stack (locked)
82
82
 
83
83
  Nx monorepo · pnpm · **NestJS** (all domain logic + DB access) · **Drizzle**
84
- (+ `drizzle-zod`) · **PostgreSQL** · Railway · **ts-rest** contracts · **Zod**
85
- validation · **Better Auth** · **TanStack Query** hooks · **Next.js** + ShadCN
86
- + Tailwind (web, UI only) · Expo + React Native Reusables + NativeWind
87
- (mobile, optional) · **BullMQ + Redis** (queues) · Pino logging · Vitest +
88
- Playwright (tests) · Conventional Commits + commitlint + lefthook · Sentry.
84
+ (+ `drizzle-zod`) · **PostgreSQL** · **Docker Compose** (local Postgres +
85
+ Redis, every host OS) · Railway · **ts-rest** contracts · **Zod**
86
+ validation · **Better Auth** · **TanStack Query** hooks · **Next.js** +
87
+ ShadCN + Tailwind (web, UI only) · Expo + React Native Reusables +
88
+ NativeWind (mobile, optional) · **BullMQ + Redis** (queues) · Pino logging ·
89
+ Vitest + Playwright (tests) · Conventional Commits + commitlint + lefthook ·
90
+ Sentry.
89
91
 
90
92
  Don't substitute libraries. If a package or generator name changed
91
93
  upstream, verify against current docs before running — don't swap in a
@@ -94,6 +96,7 @@ different library.
94
96
  ### Layout
95
97
 
96
98
  ```
99
+ docker-compose.yml local Postgres + Redis — every host OS, no native install
97
100
  apps/
98
101
  web Next.js — UI only
99
102
  mobile Expo — optional
@@ -130,6 +133,9 @@ docs/
130
133
  (lefthook gate).
131
134
  - **Fix wrong steps at the source** via the Correction Protocol — never a
132
135
  downstream workaround.
136
+ - **Local Postgres/Redis always run through `docker-compose.yml`**, on
137
+ every host OS. Never a natively-installed Postgres/Redis, even to match
138
+ a contributor's existing local setup.
133
139
  - **`packages/config` is the single source** for shared config. A per-app
134
140
  override request means fix the base config, not add an override.
135
141
 
@@ -10,7 +10,7 @@ has one, written by planner at Intake. -->
10
10
 
11
11
  ## Bootstrap
12
12
 
13
- - [ ] Nx workspace + `packages/config`
13
+ - [ ] Nx workspace + `packages/config` (incl. `docker-compose.yml` for local Postgres/Redis)
14
14
  - [ ] `packages/db` — Drizzle client
15
15
  - [ ] `packages/auth` — Better Auth config
16
16
  - [ ] `apps/api` — Nest shell, global guard, Pino