@skyf0xx/hedgehog-core-full-stack-app 1.0.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/CLAUDE.core.md +147 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/agents/backend-eng.md +189 -0
- package/agents/front-end-eng.md +176 -0
- package/agents/ux-planner.md +174 -0
- package/hedgehog-core.yaml +20 -0
- package/package.json +41 -0
- package/skills/hedgehog-bootstrap/SKILL.md +324 -0
- package/skills/hedgehog-bootstrap-full-stack-app-core/SKILL.md +411 -0
- package/skills/hedgehog-loop/SKILL.md +643 -0
- package/skills/link-workspace-packages/SKILL.md +41 -0
- package/skills/nx-generate/SKILL.md +165 -0
- package/skills/nx-run-tasks/SKILL.md +52 -0
- package/skills/nx-workspace/SKILL.md +261 -0
- package/skills/nx-workspace/references/AFFECTED.md +27 -0
- package/vendor-skills/GSAP/ATTRIBUTION.md +30 -0
- package/vendor-skills/GSAP/LICENSE +21 -0
- package/vendor-skills/GSAP/gsap-core/SKILL.md +254 -0
- package/vendor-skills/GSAP/gsap-frameworks/SKILL.md +266 -0
- package/vendor-skills/GSAP/gsap-performance/SKILL.md +79 -0
- package/vendor-skills/GSAP/gsap-plugins/SKILL.md +433 -0
- package/vendor-skills/GSAP/gsap-react/SKILL.md +136 -0
- package/vendor-skills/GSAP/gsap-scrolltrigger/SKILL.md +296 -0
- package/vendor-skills/GSAP/gsap-timeline/SKILL.md +107 -0
- package/vendor-skills/GSAP/gsap-utils/SKILL.md +284 -0
- package/vendor-skills/GSAP/llms.txt +39 -0
- package/workspace/.env.example +5 -0
- package/workspace/.github/workflows/phase-gate.yml +17 -0
- package/workspace/.prettierignore +5 -0
- package/workspace/.prettierrc +3 -0
- package/workspace/.vscode/extensions.json +3 -0
- package/workspace/apps/api/eslint.config.mjs +3 -0
- package/workspace/apps/api/package.json +117 -0
- package/workspace/apps/api/src/app/app.module.spec.ts +15 -0
- package/workspace/apps/api/src/app/app.module.ts +15 -0
- package/workspace/apps/api/src/app/feature-modules.ts +8 -0
- package/workspace/apps/api/src/app/health.controller.ts +9 -0
- package/workspace/apps/api/src/assets/.gitkeep +0 -0
- package/workspace/apps/api/src/main.ts +32 -0
- package/workspace/apps/api/tsconfig.app.json +22 -0
- package/workspace/apps/api/tsconfig.json +13 -0
- package/workspace/apps/api/tsconfig.spec.json +36 -0
- package/workspace/apps/api/vitest.config.mts +18 -0
- package/workspace/apps/api/webpack.config.cjs +25 -0
- package/workspace/apps/api-e2e/eslint.config.mjs +3 -0
- package/workspace/apps/api-e2e/package.json +28 -0
- package/workspace/apps/api-e2e/src/api/api.spec.ts +11 -0
- package/workspace/apps/api-e2e/src/support/global-setup.ts +17 -0
- package/workspace/apps/api-e2e/src/support/test-setup.ts +6 -0
- package/workspace/apps/api-e2e/tsconfig.json +10 -0
- package/workspace/apps/api-e2e/tsconfig.spec.json +28 -0
- package/workspace/apps/api-e2e/vitest.config.mts +20 -0
- package/workspace/apps/web/.env.example +12 -0
- package/workspace/apps/web/.prettierrc.js +10 -0
- package/workspace/apps/web/.swcrc +30 -0
- package/workspace/apps/web/components.json +21 -0
- package/workspace/apps/web/eslint.config.mjs +12 -0
- package/workspace/apps/web/index.d.ts +7 -0
- package/workspace/apps/web/next-env.d.ts +7 -0
- package/workspace/apps/web/next.config.js +9 -0
- package/workspace/apps/web/package.json +38 -0
- package/workspace/apps/web/postcss.config.mjs +7 -0
- package/workspace/apps/web/public/.gitkeep +0 -0
- package/workspace/apps/web/public/favicon.ico +0 -0
- package/workspace/apps/web/src/app/global.css +83 -0
- package/workspace/apps/web/src/app/layout.tsx +34 -0
- package/workspace/apps/web/src/app/module-routes.ts +13 -0
- package/workspace/apps/web/src/app/page.tsx +34 -0
- package/workspace/apps/web/src/app/providers.tsx +12 -0
- package/workspace/apps/web/src/components/theme-toggle.spec.tsx +20 -0
- package/workspace/apps/web/src/components/theme-toggle.tsx +32 -0
- package/workspace/apps/web/src/components/ui/button.tsx +60 -0
- package/workspace/apps/web/src/lib/utils.ts +6 -0
- package/workspace/apps/web/src/test-setup.ts +1 -0
- package/workspace/apps/web/tsconfig.json +62 -0
- package/workspace/apps/web/tsconfig.spec.json +37 -0
- package/workspace/apps/web/vitest.config.mts +27 -0
- package/workspace/apps/web-e2e/eslint.config.mjs +12 -0
- package/workspace/apps/web-e2e/package.json +10 -0
- package/workspace/apps/web-e2e/playwright.config.mts +75 -0
- package/workspace/apps/web-e2e/src/example.spec.ts +7 -0
- package/workspace/apps/web-e2e/tsconfig.json +26 -0
- package/workspace/commitlint.config.cjs +3 -0
- package/workspace/core.yaml +68 -0
- package/workspace/docker-compose.yml +14 -0
- package/workspace/eslint.config.mjs +15 -0
- package/workspace/gitignore.template +24 -0
- package/workspace/lefthook.yml +52 -0
- package/workspace/nx.json +90 -0
- package/workspace/package.json +99 -0
- package/workspace/packages/config/eslint-base.js +205 -0
- package/workspace/packages/config/package.json +28 -0
- package/workspace/packages/config/prettier.js +13 -0
- package/workspace/packages/config/src/env.schema.spec.ts +38 -0
- package/workspace/packages/config/src/env.schema.ts +18 -0
- package/workspace/packages/config/src/index.ts +1 -0
- package/workspace/packages/config/tsconfig.json +13 -0
- package/workspace/packages/config/tsconfig.lib.json +27 -0
- package/workspace/packages/config/tsconfig.spec.json +34 -0
- package/workspace/packages/config/vitest.config.mts +18 -0
- package/workspace/packages/db/package.json +27 -0
- package/workspace/packages/db/src/index.ts +2 -0
- package/workspace/packages/db/src/lib/db.spec.ts +48 -0
- package/workspace/packages/db/src/lib/db.ts +54 -0
- package/workspace/packages/db/src/schema/index.ts +4 -0
- package/workspace/packages/db/tsconfig.json +13 -0
- package/workspace/packages/db/tsconfig.lib.json +27 -0
- package/workspace/packages/db/tsconfig.spec.json +34 -0
- package/workspace/packages/db/vitest.config.mts +18 -0
- package/workspace/pnpm-lock.yaml +23719 -0
- package/workspace/pnpm-workspace.yaml +7 -0
- package/workspace/tools/generate-feature-modules.cjs +104 -0
- package/workspace/tools/generate-module-routes.cjs +89 -0
- package/workspace/tools/generators/contract/generator.ts +336 -0
- package/workspace/tools/generators/contract/schema.json +24 -0
- package/workspace/tools/generators/controller/generator.ts +390 -0
- package/workspace/tools/generators/controller/schema.json +24 -0
- package/workspace/tools/generators/fields.ts +160 -0
- package/workspace/tools/generators/generators.json +42 -0
- package/workspace/tools/generators/hook/generator.ts +291 -0
- package/workspace/tools/generators/hook/schema.json +19 -0
- package/workspace/tools/generators/lib-shell.ts +124 -0
- package/workspace/tools/generators/naming.ts +84 -0
- package/workspace/tools/generators/package.json +6 -0
- package/workspace/tools/generators/repository/generator.ts +298 -0
- package/workspace/tools/generators/repository/schema.json +15 -0
- package/workspace/tools/generators/schema/generator.ts +169 -0
- package/workspace/tools/generators/schema/schema.json +20 -0
- package/workspace/tools/generators/screen/generator.ts +218 -0
- package/workspace/tools/generators/screen/schema.json +15 -0
- package/workspace/tools/generators/service/generator.ts +270 -0
- package/workspace/tools/generators/service/schema.json +19 -0
- package/workspace/tools/phase-gate.cjs +77 -0
- package/workspace/tsconfig.base.json +21 -0
- package/workspace/tsconfig.json +25 -0
- package/workspace/vitest.workspace.ts +4 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hedgehog-loop
|
|
3
|
+
description: Use for every unit of work once a Hedgehog project is bootstrapped — building one layer (schema, contract, repository, service, controller, hook, screen) per module, gated by `hedgehog verify` and committed one layer at a time. Triggers on "next step", "build this module", "what's next", or the start of any work session on a bootstrapped project. Also covers the Correction Protocol for fixing a wrong upstream step.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hedgehog Loop
|
|
7
|
+
|
|
8
|
+
The operating loop for a bootstrapped Hedgehog project: `hedgehog claim`
|
|
9
|
+
reserves the packet(s) for ready layers, build them, `hedgehog verify`
|
|
10
|
+
gates and commits each. The build graph (`.hedgehog/hedgehog.db`) is the
|
|
11
|
+
live list — query it via `hedgehog status`/`hedgehog ready`, never
|
|
12
|
+
re-derive state from prose. The step tables below mirror this core's
|
|
13
|
+
`workspace/core.yaml`, the design source of truth
|
|
14
|
+
for layer order, scope, and verify command per layer — read the tables
|
|
15
|
+
for the human-readable shape, and the YAML when they seem to disagree.
|
|
16
|
+
|
|
17
|
+
The packet, though, is what actually runs. `hedgehog plan` copies each
|
|
18
|
+
layer's scope globs, verify command and commit message onto every task
|
|
19
|
+
row at compile time; from then on the row — not `core.yaml` — is what
|
|
20
|
+
`hedgehog claim` hands out and `hedgehog verify` gates against, and
|
|
21
|
+
editing `core.yaml` afterwards does not reach tasks already compiled (a
|
|
22
|
+
plain `hedgehog plan` re-run won't apply it either — it only reads
|
|
23
|
+
intents still pending). `hedgehog status` prints a **DRIFT** section
|
|
24
|
+
whenever the two have diverged, and `hedgehog plan --recompile` rewrites
|
|
25
|
+
the layer-derived fields on not-yet-started tasks from the current
|
|
26
|
+
`core.yaml`, refusing — and naming — every task already building,
|
|
27
|
+
verifying, complete, or blocked. Never patch a task row in SQLite by
|
|
28
|
+
hand: the DB is derived and gitignored, so `hedgehog db rebuild` drops
|
|
29
|
+
the patch.
|
|
30
|
+
|
|
31
|
+
## Determine phase
|
|
32
|
+
|
|
33
|
+
Before touching code, know which phase applies to the module in scope:
|
|
34
|
+
|
|
35
|
+
- **Phase A** — building/extending the backend. Every module in scope
|
|
36
|
+
needs schema → contract → repository → service → controller before
|
|
37
|
+
Phase B starts for any of them.
|
|
38
|
+
- **Phase B** — Phase A is closed for the module. Build hooks and screens.
|
|
39
|
+
|
|
40
|
+
Check `hedgehog status` (or `hedgehog why <path>` for a specific file),
|
|
41
|
+
or the commit log for `feat(<module>): api` commits. No such commit (and
|
|
42
|
+
no `controller` task `complete` for that module) means the module is in
|
|
43
|
+
Phase A.
|
|
44
|
+
|
|
45
|
+
## The Domain Module Pattern
|
|
46
|
+
|
|
47
|
+
A **domain module = one table.** `users`, `orders`, `order_items` are each
|
|
48
|
+
their own module, carrying the full step sequence below. The schema is the
|
|
49
|
+
source of truth for module boundaries.
|
|
50
|
+
|
|
51
|
+
**Cross-module references are FK-by-ID only.** If `orders.user_id`
|
|
52
|
+
references `users`, the `orders` schema holds a plain FK column. The
|
|
53
|
+
`orders` repository and service depend only on their own ports — a service
|
|
54
|
+
knows related entities only as an ID.
|
|
55
|
+
|
|
56
|
+
- Need the related row? Resolve it at the contract/controller layer
|
|
57
|
+
(parallel calls to each module's own endpoint), or join against the
|
|
58
|
+
other module's *schema* directly inside the repository (Drizzle query).
|
|
59
|
+
- This keeps every service importing only its own module's port, which is
|
|
60
|
+
what `eslint-base.js`'s `no-restricted-imports` rule for
|
|
61
|
+
`libs/*/service/**` enforces (wired at bootstrap) — a service reaching
|
|
62
|
+
a `*.adapter`, `drizzle-orm`, or `packages/db` fails lint.
|
|
63
|
+
|
|
64
|
+
A junction table (e.g. `order_items`) is one table, one module, with two
|
|
65
|
+
FK-by-ID columns instead of one, each resolved the same way.
|
|
66
|
+
|
|
67
|
+
Every module goes through the same shape, in order:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
schema (Drizzle) — types before data
|
|
71
|
+
contract (Zod / ts-rest) — the boundary
|
|
72
|
+
repository (port + Drizzle adapter)
|
|
73
|
+
service (domain logic) — imports only ports
|
|
74
|
+
controller (thin HTTP)
|
|
75
|
+
hook (TanStack Query) — Phase B only
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Plus, when an operation needs async **and the Queue add-on is on for this
|
|
79
|
+
project** (check `.hedgehog/addons.yaml`'s `queue.on`): **queue = port +
|
|
80
|
+
BullMQ adapter**, same port/adapter shape as the repository. The service
|
|
81
|
+
imports only ports. Queue is one-time project infra, not a compiled
|
|
82
|
+
layer — `full-stack-app/core.yaml` has no `queue` layer, so this step has
|
|
83
|
+
no `hedgehog verify` gate of its own; build it as part of the
|
|
84
|
+
`controller` layer's packet, verified by that layer's own check. If the
|
|
85
|
+
Queue add-on is off, there's no `apps/worker` and no queue step, full
|
|
86
|
+
stop — an operation that seems to want async processing on a Queue-off
|
|
87
|
+
project is a signal to revisit that add-on decision with `planner`, not
|
|
88
|
+
to build a one-off queue outside the add-on's scaffolding.
|
|
89
|
+
|
|
90
|
+
Every layer scaffolds from its own generator in `tools/generators/` (see
|
|
91
|
+
"Scaffolding a layer" below) — package shell, tags, files, barrel wiring,
|
|
92
|
+
and the layer's conventional shape all land in one deterministic step.
|
|
93
|
+
What's authored on top is the entity-specific delta: the field list and
|
|
94
|
+
its types, the module's business rules, and the UX intent behind its
|
|
95
|
+
screen.
|
|
96
|
+
|
|
97
|
+
## Domain Module — Backend Steps (Phase A, every module in scope)
|
|
98
|
+
|
|
99
|
+
A horizontal pass across the whole backend — every module goes through
|
|
100
|
+
these before any module gets a hook or screen. Each row is one compiled
|
|
101
|
+
layer in `full-stack-app/core.yaml`; delegate each module's Phase A
|
|
102
|
+
layers to the `backend-eng` agent, one claimed packet per dispatch — it
|
|
103
|
+
builds the layer, `hedgehog verify` gates and commits it.
|
|
104
|
+
|
|
105
|
+
| # | Layer | Lives in | Commit |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| 1 | `schema` | `packages/db` (Drizzle) | `feat(<module>): schema` |
|
|
108
|
+
| 2 | `contract` | `packages/contracts` (Zod via `drizzle-zod` + ts-rest) | `feat(<module>): contract` |
|
|
109
|
+
| 3 | `repository` | `libs/<module>/repository` (port + Drizzle adapter) | `feat(<module>): repository` |
|
|
110
|
+
| 4 | `service` | `libs/<module>/service` (domain logic — imports only ports) | `feat(<module>): service` |
|
|
111
|
+
| 5 | `controller` | `apps/api` (thin HTTP, wires contract → service; bundles Queue infra, see above, if that add-on is on and this module needs it) | `feat(<module>): api` |
|
|
112
|
+
|
|
113
|
+
Repeat 1–5 per module in scope, via `hedgehog claim`/`hedgehog verify`.
|
|
114
|
+
The API is complete, typed, and callable (Postman/curl/contract tests)
|
|
115
|
+
before frontend work starts.
|
|
116
|
+
|
|
117
|
+
## Domain Module — Frontend Steps (Phase B, after Phase A closes for the module)
|
|
118
|
+
|
|
119
|
+
| # | Layer | Lives in | Commit |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| 6 | `hook` | `packages/hooks` (TanStack Query) | `feat(<module>): hooks` |
|
|
122
|
+
| 6a | UX rationale | `docs/design/<module>.md`, `ux-planner` agent | bundled into layer 7's commit |
|
|
123
|
+
| 7 | `screen` | `apps/web`, plus `apps/mobile` when the Mobile add-on is on | `feat(<module>): screen-web` |
|
|
124
|
+
|
|
125
|
+
Phase B starts once Phase A is done for the scope. The frontend is a pure
|
|
126
|
+
consumer of an already-finished API. Delegate each module's Phase B
|
|
127
|
+
layers to the `front-end-eng` agent, same reasoning as `backend-eng` for
|
|
128
|
+
Phase A — one claimed packet per dispatch, in its own context. Step
|
|
129
|
+
6a is where "how it should feel" gets decided — once per module, after
|
|
130
|
+
the `hook` layer's task is `complete` and before `front-end-eng` starts
|
|
131
|
+
the `screen` layer — via `ux-planner`, starting from whatever `planner`
|
|
132
|
+
filed in `docs/design/<module>-notes.md` at planning intake, or the raw
|
|
133
|
+
UX spec directly if that file is absent, or — where the archive holds
|
|
134
|
+
neither — from the contract and hook plus whatever the user supplies when
|
|
135
|
+
it asks. Its first run for a module also
|
|
136
|
+
signals to the user that Phase B has started, and is the point a mockup,
|
|
137
|
+
screenshot, or export (Google Stitch, Figma) can be handed over. It
|
|
138
|
+
writes `docs/design/<module>.md`, not its own compiled layer — the
|
|
139
|
+
`screen` layer's `hedgehog verify` is what gates and commits it.
|
|
140
|
+
|
|
141
|
+
## The Loop (every unit of work)
|
|
142
|
+
|
|
143
|
+
1. **Run `hedgehog claim --count N --owner <owner>`.** `<owner>` is this
|
|
144
|
+
session (a stable id — session id or equivalent). Claim is atomic and
|
|
145
|
+
lease-based, safe for concurrent claimers, and is the entry point into
|
|
146
|
+
the loop — `hedgehog next` still exists as a read-only preview of the
|
|
147
|
+
single next task, but claim is what actually reserves work. `--count
|
|
148
|
+
N` is a maximum, not a promise: it returns however many tasks are
|
|
149
|
+
safe to run together right now (the conflict predicate already
|
|
150
|
+
filtered them against each other), which may be fewer than N, or
|
|
151
|
+
zero. `hedgehog ready` previews the same decision without claiming
|
|
152
|
+
anything — CLAIMABLE vs HELD BACK, with the reason for each holdback —
|
|
153
|
+
useful for understanding the scheduler before committing to a claim.
|
|
154
|
+
2. **Dispatch each claimed packet to its own subagent** — `backend-eng`
|
|
155
|
+
(Phase A) or `front-end-eng` (Phase B), matching each packet's ALLOWED
|
|
156
|
+
SCOPE — in ONE message with parallel tool calls, not one agent call
|
|
157
|
+
after another. This is a Claude session orchestrating via the Agent
|
|
158
|
+
tool's parallel-call mechanism: N claimed tasks means N Agent calls in
|
|
159
|
+
the same message.
|
|
160
|
+
3. Each agent **runs typecheck/lint/test on its own work** (mirrors
|
|
161
|
+
lefthook, wired at bootstrap) as a sanity check before reporting
|
|
162
|
+
back — necessary, not sufficient. Per task, per agent: the agent
|
|
163
|
+
reports its work as done; it does not move the task and does not
|
|
164
|
+
commit.
|
|
165
|
+
4. **As each report arrives, verify it — one at a time, serially.** Run
|
|
166
|
+
`hedgehog verify <task-id> --owner <owner>` (the same owner that
|
|
167
|
+
claimed it; verify requires the lease owner). Building happens in
|
|
168
|
+
parallel; verifying does not — verify writes a commit, and commits go
|
|
169
|
+
through one at a time. It checks the touched files against the
|
|
170
|
+
packet's ALLOWED SCOPE, runs the layer's VERIFICATION command, and on
|
|
171
|
+
a pass writes the commit (the exact Conventional Commit message from
|
|
172
|
+
the tables above, plus the updated build graph) and unlocks the next
|
|
173
|
+
layer. On a scope violation or a failing check, the task moves to
|
|
174
|
+
`blocked` with a `blocked_reason` of `scope_violation` or
|
|
175
|
+
`verification_failed`, and nothing downstream unlocks. Fix the work,
|
|
176
|
+
then run `hedgehog retry <task-id>` to return the task to `planned`,
|
|
177
|
+
claim it again (by task id — see below), and verify again —
|
|
178
|
+
`hedgehog verify` only accepts a task you currently hold in
|
|
179
|
+
`building`, so a blocked task has to go back through `retry` and
|
|
180
|
+
`claim` first. Don't hand-commit around it.
|
|
181
|
+
|
|
182
|
+
A `blocked` task anywhere in the graph — in this module or any other —
|
|
183
|
+
makes `hedgehog claim --count N` refuse to hand out anything at all,
|
|
184
|
+
with a non-zero exit naming the blocked task(s). `hedgehog status`
|
|
185
|
+
lists them too, under NEEDS ATTENTION. Fix and `retry` the named
|
|
186
|
+
task(s) before claiming more. A **targeted** `hedgehog claim <task-id>
|
|
187
|
+
--owner <owner>` is exempt — that's how the just-retried task gets
|
|
188
|
+
reclaimed in the step above. A lease the same `claim` call reaps for
|
|
189
|
+
having just expired is exempt too: that call still claims whatever
|
|
190
|
+
else is ready, and the reaped task lands in NEEDS ATTENTION for the
|
|
191
|
+
next `claim` call to stop on.
|
|
192
|
+
5. **Repeat** — `hedgehog claim --count N --owner <owner>` again for the
|
|
193
|
+
next batch.
|
|
194
|
+
|
|
195
|
+
Each claimed packet is the full packet — STATUS/INTENT/RELEVANT
|
|
196
|
+
RULES/INHERITED DEBT/WHY NOW/BLOCKED DOWNSTREAM/ALLOWED
|
|
197
|
+
SCOPE/VERIFICATION — and its **INTENT** block carries the goal and
|
|
198
|
+
outcome of the whole intent, not just this layer's objective. A layer's
|
|
199
|
+
verify command runs the tests that layer wrote, so it measures internal
|
|
200
|
+
consistency, never coverage of what was asked; build the layer's share of
|
|
201
|
+
the goal and say so when the packet doesn't account for something the
|
|
202
|
+
goal asks for. When `hedgehog verify` closes the **last** layer of an
|
|
203
|
+
intent it prints the goal and outcome back as an **INTENT CHECK** — read
|
|
204
|
+
the built work against it there, because nothing else in the build does.
|
|
205
|
+
|
|
206
|
+
A layer that hits a limitation the next layer must compensate for
|
|
207
|
+
declares it with `hedgehog debt add <task-id> "<note>"`; the note lands
|
|
208
|
+
in the **INHERITED DEBT** section of every packet that depends on that
|
|
209
|
+
task. A comment in a source file is not a mechanism — nothing reads it.
|
|
210
|
+
|
|
211
|
+
Each `hedgehog verify` call commits exactly one layer, built right for
|
|
212
|
+
what's known now; a wrong layer is fixed forward later via the
|
|
213
|
+
Correction Protocol. Valid task statuses are `planned`, `ready`,
|
|
214
|
+
`building`, `verifying`, `complete`, and `blocked`; a task in `blocked`
|
|
215
|
+
also carries a `blocked_reason` (`scope_violation`, `verification_failed`,
|
|
216
|
+
or `lease_expired`).
|
|
217
|
+
|
|
218
|
+
## Scaffolding a layer
|
|
219
|
+
|
|
220
|
+
`tools/generators/` holds one Nx generator per layer, and every layer
|
|
221
|
+
starts from its own:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
nx g ./tools/generators:schema --module=<module> --fields='<name:type,...>'
|
|
225
|
+
nx g ./tools/generators:contract --module=<module> --fields='<name:type,...>' [--toggleField=<boolField>]
|
|
226
|
+
nx g ./tools/generators:repository --module=<module>
|
|
227
|
+
nx g ./tools/generators:service --module=<module> [--toggleField=<boolField>]
|
|
228
|
+
nx g ./tools/generators:controller --module=<module> --fields='<name:type,...>' [--toggleField=<boolField>]
|
|
229
|
+
nx g ./tools/generators:hook --module=<module> [--toggleField=<boolField>]
|
|
230
|
+
nx g ./tools/generators:screen --module=<module>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`--module` is the domain module's plural kebab-case name (`tasks`,
|
|
234
|
+
`order-items`). `--fields` is a comma-separated list of `name:type` pairs
|
|
235
|
+
over `string`, `text`, `boolean`, `integer`, and `timestamp`, with a
|
|
236
|
+
trailing `?` marking the column nullable
|
|
237
|
+
(`--fields='title:string,done:boolean,dueDate:timestamp?'`); `contract`
|
|
238
|
+
and `controller` take the same list the module's `schema` was generated
|
|
239
|
+
with. A `string` field takes an optional length in parentheses
|
|
240
|
+
(`title:string(500)`), threaded to both the Drizzle `varchar` and the Zod
|
|
241
|
+
`.max()` so the two cannot disagree; omitted, it is 255. Check every
|
|
242
|
+
length against the intent's own rules in the packet's **RELEVANT RULES** —
|
|
243
|
+
a rule like "at most 500 characters" is the field list's business, not
|
|
244
|
+
the authored delta's, and a Zod bound that outruns its column surfaces as
|
|
245
|
+
a driver error at the database rather than a 400 at the boundary.
|
|
246
|
+
|
|
247
|
+
`--toggleField` names a boolean field from the schema to expose as a
|
|
248
|
+
toggle, and is passed to `contract`, `service`, `controller`, and `hook`
|
|
249
|
+
alike — one flag, four layers, so the route, the domain method, the
|
|
250
|
+
handler, and the mutation are generated from one source. The toggle is
|
|
251
|
+
server-side by construction: `POST /<module>/:id/toggle` carries no body,
|
|
252
|
+
and the service reads the current value and flips it inside the same
|
|
253
|
+
transaction its `update` uses. The client sends only the id, so a stale
|
|
254
|
+
cached row cannot overwrite a newer state — which is exactly what
|
|
255
|
+
computing the new value on the client would do, losing one of any two
|
|
256
|
+
flips that raced. Note that `@ts-rest/core` generates no `body` parameter
|
|
257
|
+
for a `c.noBody()` route: the call is `client.toggle({ params: { id } })`,
|
|
258
|
+
and passing `body: undefined` is a compile error.
|
|
259
|
+
|
|
260
|
+
Each generator lands the whole conventional shape of its layer in one
|
|
261
|
+
deterministic step — the package shell (`package.json`, `tsconfig*.json`,
|
|
262
|
+
`vitest.config.mts`, `src/index.ts`) where the layer creates one, the
|
|
263
|
+
`nx.tags` pair `packages/config/eslint-base.js`'s `depConstraints` keys
|
|
264
|
+
on, the port-discipline file suffixes lint checks for, the Nest module
|
|
265
|
+
and controller pair with `@Controller()` left bare (the ts-rest contract
|
|
266
|
+
already encodes full route paths), and every barrel export the new files
|
|
267
|
+
need. Hand-copying a sibling module's files invites exactly the drift
|
|
268
|
+
`hedgehog verify`'s lint step then has to catch: missing tags, missing
|
|
269
|
+
project references, a doubled route prefix.
|
|
270
|
+
|
|
271
|
+
What the generator lands is the layer's skeleton, not the layer. Author
|
|
272
|
+
the entity-specific delta on top: the module's business rules in
|
|
273
|
+
`service`, its domain-error mapping in `controller`, and — for `screen`,
|
|
274
|
+
which is skeleton-only by design — the layout, information hierarchy, and
|
|
275
|
+
interaction pattern from `ux-planner`'s rationale, over the placeholders
|
|
276
|
+
the generator leaves for the list, filter shell, empty state, and form.
|
|
277
|
+
|
|
278
|
+
Registration inside `apps/api` is automatic and stays that way:
|
|
279
|
+
`apps/api/src/app/feature-modules.ts` globs
|
|
280
|
+
`apps/api/src/app/*/*.module.ts` and is regenerated by the
|
|
281
|
+
`generate-feature-modules` Nx target that `build`/`typecheck`/`test`
|
|
282
|
+
depend on. Never register a module by editing `app.module.ts` — a shared
|
|
283
|
+
file no module-scoped task can safely touch. Validation is ts-rest + Zod,
|
|
284
|
+
so this core has no Nest DTOs and no class-validator.
|
|
285
|
+
|
|
286
|
+
The root page is the same: `apps/web/src/app/page.tsx` renders whatever
|
|
287
|
+
`module-routes.ts` holds, and that file is regenerated by the
|
|
288
|
+
`generate-module-routes` Nx target from every
|
|
289
|
+
`apps/web/src/app/*/page.tsx` on disk. A screen layer creates its own
|
|
290
|
+
`page.tsx` in its own directory and the root page picks it up — never
|
|
291
|
+
edit either the root page or the generated file to add a link.
|
|
292
|
+
|
|
293
|
+
**A new package needs wiring into the workspace before `hedgehog verify`
|
|
294
|
+
runs on it.** A package that exists on disk isn't yet part of the
|
|
295
|
+
workspace:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pnpm install # link the new workspace:* deps
|
|
299
|
+
pnpm nx sync # regenerate TypeScript project references
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`pnpm-workspace.yaml` already globs `packages/*`, `apps/*` and `libs/*/*`,
|
|
303
|
+
so a package under any of those needs no edit there. This is the common
|
|
304
|
+
case, not an edge case: any layer that is the first arrival in a package
|
|
305
|
+
(`contract`, `hook`, each module's `repository` and `service`) or that
|
|
306
|
+
wires a new package into an existing one (`controller`, adding the
|
|
307
|
+
module's `contracts`/`repository`/`service` packages to `apps/api`) needs
|
|
308
|
+
it — on a module's first pass through Phase A/B that is most of the
|
|
309
|
+
layers, not an occasional one.
|
|
310
|
+
|
|
311
|
+
The building agent runs `pnpm install` / `pnpm nx sync` and reports back
|
|
312
|
+
which shared files changed (typically `pnpm-lock.yaml`, root
|
|
313
|
+
`tsconfig.json`, and — on a `controller` layer — `apps/api/package.json`,
|
|
314
|
+
`apps/api/tsconfig.app.json`), because it has the shell access to run
|
|
315
|
+
them, but it never commits: no agent reporting success moves a task or
|
|
316
|
+
touches git, only `hedgehog verify`'s passing exit code does (see the
|
|
317
|
+
building agents' own Workflow step on this). Committing those shared
|
|
318
|
+
files is the orchestrating session's job, done between dispatch and
|
|
319
|
+
`hedgehog verify` on every layer where the agent flagged a change: expect
|
|
320
|
+
it, don't wait to be reminded.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
git add pnpm-lock.yaml tsconfig.json # plus apps/*/package.json,
|
|
324
|
+
# apps/*/tsconfig.app.json on a
|
|
325
|
+
# controller layer
|
|
326
|
+
git commit -m "chore(workspace): sync project references"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
These files are mechanically derived by `pnpm install` and `pnpm nx
|
|
330
|
+
sync`, not authored content, and sit outside every module-scoped layer's
|
|
331
|
+
scope — they belong to no layer, and no override covers them. Committing
|
|
332
|
+
them separately, before `hedgehog verify` runs, keeps the layer's own
|
|
333
|
+
commit exactly the layer.
|
|
334
|
+
|
|
335
|
+
This is the orchestrating session's step rather than a verify post-step
|
|
336
|
+
on purpose: `hedgehog verify` gates the tree it's handed, and a gate that
|
|
337
|
+
mutates that tree would manufacture the scope violation it then reports.
|
|
338
|
+
|
|
339
|
+
## First arrival in a package
|
|
340
|
+
|
|
341
|
+
Every layer scope names a directory *inside* a package
|
|
342
|
+
(`packages/contracts/src/{module}/**`, `libs/{module}/repository/**`), and
|
|
343
|
+
on the first module through that layer the package itself doesn't exist
|
|
344
|
+
yet. Its shell — `package.json`, `tsconfig*.json`, `vitest.config.mts`,
|
|
345
|
+
`src/index.ts` — necessarily lands outside the layer's scope glob, because
|
|
346
|
+
no `{module}`-bearing glob can cover a package root. Left alone those files
|
|
347
|
+
sit on disk uncommitted until the `join` layer's `**` scope sweeps them in,
|
|
348
|
+
so `git log -- packages/contracts/` shows source with no buildable package
|
|
349
|
+
behind it for the whole middle of the build.
|
|
350
|
+
|
|
351
|
+
A generator can also drop shared, package-wide source at the `src/` root
|
|
352
|
+
alongside the module's own files on that same first pass — the `contract`
|
|
353
|
+
generator's `timestamp.ts` is one (a shared Zod util every module in the
|
|
354
|
+
package imports, written once, sibling to `src/index.ts`). That file needs
|
|
355
|
+
the same widening as the shell itself.
|
|
356
|
+
|
|
357
|
+
The packet says so: when the package a scope points into has no
|
|
358
|
+
`package.json` on disk yet, `hedgehog next`/`show` prints a **FIRST
|
|
359
|
+
ARRIVAL** section under ALLOWED SCOPE carrying the exact command for that
|
|
360
|
+
task. Run it before building — the widening is only available while the
|
|
361
|
+
task is still `ready`, and a verify that rejects the shell paths blocks
|
|
362
|
+
the task and turns this into a five-command recovery.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
hedgehog override add TASKS-CONTRACT \
|
|
366
|
+
--scope 'packages/contracts/*' \
|
|
367
|
+
--scope 'packages/contracts/src/*' \
|
|
368
|
+
--reason 'first module through the contract layer also creates the package shell'
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`packages/contracts/src/*` is non-recursive, so it covers `src/index.ts`
|
|
372
|
+
and `src/timestamp.ts` without also granting the module subdirectory the
|
|
373
|
+
layer's own `packages/contracts/src/{module}/**` scope already covers.
|
|
374
|
+
|
|
375
|
+
`.hedgehog/overrides/*.json` is additive, per-task, committed, and replayed
|
|
376
|
+
by `plan`, `--recompile` and `db rebuild` alike, so the exception survives a
|
|
377
|
+
rebuild and stays reviewable in the diff — unlike a hand-edited task row,
|
|
378
|
+
which the next rebuild silently drops. It widens exactly the one task that
|
|
379
|
+
creates the package, not the layer, so module two's task keeps the narrow
|
|
380
|
+
scope.
|
|
381
|
+
|
|
382
|
+
Which tasks need it: the first module through `contract`
|
|
383
|
+
(`packages/contracts`) and through `hook` (`packages/hooks`), and every
|
|
384
|
+
module's `repository` and `service`, since `libs/{module}/repository` and
|
|
385
|
+
`libs/{module}/service` are new libs per module — there, the layer's own
|
|
386
|
+
`libs/{module}/repository/**` glob already covers the package root, so no
|
|
387
|
+
override is needed. `packages/db` and `packages/config` ship with core, so
|
|
388
|
+
`schema` never needs one. `controller` never needs one either — `apps/api`
|
|
389
|
+
ships with core, and its `apps/api/src/app/{module}/**` scope already
|
|
390
|
+
covers the module's generated directory.
|
|
391
|
+
|
|
392
|
+
Never widen a scope to route around a violation the Correction Protocol
|
|
393
|
+
should handle — this is for a package shell the layer genuinely creates,
|
|
394
|
+
nothing else. The shell itself comes from the layer's generator
|
|
395
|
+
("Scaffolding a layer" above), which is also where the workspace wiring a
|
|
396
|
+
new package needs lives.
|
|
397
|
+
|
|
398
|
+
## Intra-step conventions
|
|
399
|
+
|
|
400
|
+
The Nx boundaries, phase gate, and lint own the *structural* rules
|
|
401
|
+
(what imports what, what gets built when). These are the conventions
|
|
402
|
+
*inside* a step that those gates can't see — apply them uniformly so a
|
|
403
|
+
fresh-context session builds module N the same way it built module 1. The
|
|
404
|
+
`reviewer` agent checks these at a phase boundary.
|
|
405
|
+
|
|
406
|
+
- **Errors are thrown, typed, and domain-named.** A service throws a
|
|
407
|
+
domain error (`OrderNotFoundError`, not a bare `Error` or an HTTP
|
|
408
|
+
exception) — services don't know they're behind HTTP. The controller is
|
|
409
|
+
the only layer that maps domain errors to status codes. Never return
|
|
410
|
+
`null`/`undefined` to signal a failure a caller must branch on.
|
|
411
|
+
- **Repository not-found returns `undefined`; the service decides.** A
|
|
412
|
+
`findById` that misses returns `undefined` (a plain absence, not an
|
|
413
|
+
error); the service turns that into a thrown domain error when the
|
|
414
|
+
operation requires the row. Adapters don't throw domain errors — they
|
|
415
|
+
report absence, the service interprets it.
|
|
416
|
+
- **Validation lives at the contract boundary, once.** Input is
|
|
417
|
+
Zod-validated at the controller via the ts-rest contract. Past that
|
|
418
|
+
boundary, types are trusted — services and repositories don't re-parse.
|
|
419
|
+
A service-level invariant that isn't expressible in the Zod schema
|
|
420
|
+
(e.g. "can't cancel after payment") is enforced in the service as a
|
|
421
|
+
thrown domain error, not a second validation pass.
|
|
422
|
+
- **Multi-write operations are transactional.** A service method that
|
|
423
|
+
writes more than once wraps the writes in one Drizzle transaction,
|
|
424
|
+
passed through the port — partial writes never escape a failed
|
|
425
|
+
operation.
|
|
426
|
+
- **Services are pure domain logic.** No logging, no HTTP, no queue
|
|
427
|
+
mechanics inside a service method — those live at the controller /
|
|
428
|
+
adapter edge. A service reads as the business rule and nothing else.
|
|
429
|
+
|
|
430
|
+
## Friction log
|
|
431
|
+
|
|
432
|
+
Real friction during a build — an agent's instructions were unclear, a
|
|
433
|
+
redline had to be issued twice for the same underlying gap, the user
|
|
434
|
+
had to correct the same kind of mistake more than once, or user
|
|
435
|
+
feedback implied something was wrong even without a direct correction
|
|
436
|
+
(a preference stated once that, read plainly, means an earlier step
|
|
437
|
+
missed something) — is signal worth keeping past this session, separate
|
|
438
|
+
from the Correction Protocol that fixes it in the moment. Log one entry
|
|
439
|
+
via `hedgehog friction add "<note>" [--task <task-id>]` when that
|
|
440
|
+
happens: what was tried, what went wrong or was implied, why if visible,
|
|
441
|
+
and the commit/message it traces to, all in the note text; pass `--task`
|
|
442
|
+
with the layer's task id when the friction traces to one. This is a log,
|
|
443
|
+
not a todo list — don't let it block or slow the Loop; log and keep
|
|
444
|
+
moving. `tweaker` reads it (via `hedgehog friction list`) once the build
|
|
445
|
+
reaches its Stop Condition.
|
|
446
|
+
|
|
447
|
+
## Correction Protocol
|
|
448
|
+
|
|
449
|
+
When a downstream step reveals an upstream step was wrong:
|
|
450
|
+
|
|
451
|
+
1. **Quiesce.** Dispatch nothing new. Let in-flight tasks finish and
|
|
452
|
+
verify normally — do NOT kill running subagents. Release anything
|
|
453
|
+
claimed but not yet started (`hedgehog release <task-id> --owner
|
|
454
|
+
<owner>`).
|
|
455
|
+
2. Once nothing is in flight (`hedgehog quiesce` exits 0), patch the
|
|
456
|
+
upstream step directly, in place.
|
|
457
|
+
3. Fast-forward every dependent step that breaks, each its own small
|
|
458
|
+
commit. If the patched step lives in a workspace package (e.g.
|
|
459
|
+
`packages/hooks`, `packages/contracts`) that a running `web`/`mobile`
|
|
460
|
+
dev server consumes, run that package's `nx run <pkg>:build` before
|
|
461
|
+
re-verifying — the dev server resolves the package's built `dist/`,
|
|
462
|
+
not its `src/`, so an unbuilt patch looks unchanged to anything
|
|
463
|
+
downstream even though the source is fixed.
|
|
464
|
+
4. The commit messages are the explanation.
|
|
465
|
+
5. Resume — `hedgehog claim` again.
|
|
466
|
+
|
|
467
|
+
Quiescing is correct, not a cautious fallback. The conflict predicate
|
|
468
|
+
already guarantees a correction cannot collide with in-flight work: if
|
|
469
|
+
the correction's scope conflicted with something currently building, the
|
|
470
|
+
scheduler would not have co-scheduled it in the first place. Letting
|
|
471
|
+
in-flight tasks finish and verify rather than killing them costs nothing
|
|
472
|
+
and throws away no progress.
|
|
473
|
+
|
|
474
|
+
The orchestrating session runs this protocol. A phase-owning agent that
|
|
475
|
+
hits the problem reports it rather than correcting across steps: the
|
|
476
|
+
commits at step 3 are the session's act, the same way `hedgehog verify`
|
|
477
|
+
always is.
|
|
478
|
+
|
|
479
|
+
Use `conventional-commits` when a correction touches several steps in one
|
|
480
|
+
working-tree pass and needs splitting back into per-step commits.
|
|
481
|
+
|
|
482
|
+
### Post-build entry
|
|
483
|
+
|
|
484
|
+
The protocol also runs after a build has reached its Stop Condition, when
|
|
485
|
+
a `tweaker` session finds that something structural is wrong rather than
|
|
486
|
+
something small (`tweaker` routes it here). Steps 2, 3, and 4 are
|
|
487
|
+
unchanged. The two ends differ:
|
|
488
|
+
|
|
489
|
+
- There is nothing to **quiesce** — no task is in flight. Start by naming
|
|
490
|
+
which committed step was wrong and what revealed it.
|
|
491
|
+
- There is no loop to **resume**: every task is already `complete`, so
|
|
492
|
+
`hedgehog claim` has nothing to claim. Return to the `tweaker` session
|
|
493
|
+
instead.
|
|
494
|
+
|
|
495
|
+
Every task the correction touches is already `complete` and stays that
|
|
496
|
+
way — a correction is fixed forward in new commits, never by reopening a
|
|
497
|
+
finished task. Verify each patched step by running that step's own verify
|
|
498
|
+
command directly. Log the correction with `hedgehog friction add` so the
|
|
499
|
+
next friction review sees what the build got wrong.
|
|
500
|
+
|
|
501
|
+
## Phase Transition Checks
|
|
502
|
+
|
|
503
|
+
Before starting Phase B for a module, confirm:
|
|
504
|
+
|
|
505
|
+
- `hedgehog status` shows that module's `controller` task `complete`
|
|
506
|
+
(equivalently, a `feat(<module>): api` commit exists).
|
|
507
|
+
- The contract is callable and typed (contract tests pass).
|
|
508
|
+
|
|
509
|
+
Use the `reviewer` agent for this — it checks what the mechanical gate
|
|
510
|
+
can't. Everything lefthook already enforces (typecheck, lint, unit test
|
|
511
|
+
pass/fail) is out of scope for that review; these are the checks this
|
|
512
|
+
core adds on top of it, and the list `reviewer` works from at a
|
|
513
|
+
full-stack-app phase boundary:
|
|
514
|
+
|
|
515
|
+
- **Port discipline**: a module's port interface and its Drizzle adapter
|
|
516
|
+
share one lib, so the tag graph has to allow `type:service →
|
|
517
|
+
type:adapter` and the real check is at the import level: does the
|
|
518
|
+
service import the port from the repository lib's entry point, or the
|
|
519
|
+
concrete `*.adapter`? Does anything in `apps/api` outside a
|
|
520
|
+
`*.module.ts` construct an adapter? `eslint-base.js`'s
|
|
521
|
+
`no-restricted-imports` rules catch the named cases — read the actual
|
|
522
|
+
imports anyway, since an adapter file not named `*.adapter.ts` opts
|
|
523
|
+
itself out of the rule. Use `nx show project <name> --json` (per the
|
|
524
|
+
`nx-workspace` skill) to check a project's resolved tags and
|
|
525
|
+
dependencies rather than reading `project.json` directly — it only
|
|
526
|
+
holds partial configuration, not tags inferred by plugins.
|
|
527
|
+
- **FK-by-ID discipline**: does a module's repository/service reach into
|
|
528
|
+
another module's tables directly, or only resolve related entities by
|
|
529
|
+
ID at the contract/controller layer (cross-module references, above)?
|
|
530
|
+
- **Module granularity**: is this actually one table = one module, or has
|
|
531
|
+
scope crept — two tables sharing a service, or a junction table
|
|
532
|
+
absorbed into one side's module instead of standing alone?
|
|
533
|
+
- **Contract shape**: does the Zod/ts-rest contract match what Phase B
|
|
534
|
+
will need, or does it leak implementation detail that will force a
|
|
535
|
+
breaking change once hooks are built against it?
|
|
536
|
+
- **Phase leakage**: any hook or screen code, or frontend-shaped
|
|
537
|
+
reasoning, showing up before this module has a `feat(<module>): api`
|
|
538
|
+
commit?
|
|
539
|
+
- **Queue seam**: if the Queue add-on is on and queue infra was added,
|
|
540
|
+
does the operation genuinely need async (long-running, retries,
|
|
541
|
+
fan-out) — or was the seam reached for out of habit? If the Queue
|
|
542
|
+
add-on is off (check `.hedgehog/addons.yaml`'s `queue.on`), there should
|
|
543
|
+
be no `apps/worker` and no queue infra at all for this module — queue
|
|
544
|
+
infra appearing anyway is itself a finding, not something to review the
|
|
545
|
+
contents of.
|
|
546
|
+
- **Intra-step conventions**: does the module follow the conventions the
|
|
547
|
+
gate can't see (Intra-step conventions, above)? Check against that list
|
|
548
|
+
rather than re-deriving it. A module drifting from them is a Warning
|
|
549
|
+
unless it breaks Phase B.
|
|
550
|
+
- **Security/correctness**: unvalidated input reaching a Drizzle query
|
|
551
|
+
outside the Zod-validated contract boundary, secrets, obvious logic
|
|
552
|
+
errors — same bar any reviewer would apply, scoped to what's new since
|
|
553
|
+
the last review point.
|
|
554
|
+
|
|
555
|
+
The review point is the last `feat(<module>): api` commit; `git diff`
|
|
556
|
+
from there, then read every layer of the module rather than the diff
|
|
557
|
+
alone — boundary violations are invisible from a diff.
|
|
558
|
+
|
|
559
|
+
Before starting Phase A for a module, confirm it's inside the stated scope
|
|
560
|
+
boundary from planning intake (`planner`). If not, stop and ask — and if
|
|
561
|
+
the answer is that the scope really should grow, that's `planner`'s
|
|
562
|
+
Re-entry pass, which adds it to the graph properly. Don't build a module
|
|
563
|
+
the graph doesn't have a task for.
|
|
564
|
+
|
|
565
|
+
## Rules
|
|
566
|
+
|
|
567
|
+
- **Phase A closes before Phase B opens.** Every module in scope has a
|
|
568
|
+
working, tested API before any hook or screen starts.
|
|
569
|
+
- **Concurrent within a phase, bounded by the scheduler.** Never assume
|
|
570
|
+
two tasks are safe to run together because they look independent — ask
|
|
571
|
+
`hedgehog ready`.
|
|
572
|
+
- **Queue infra is conditional twice over** — only if the Queue add-on is
|
|
573
|
+
on for this project at all (per `.hedgehog/addons.yaml`'s `queue.on`),
|
|
574
|
+
and even then only when a given operation genuinely needs async
|
|
575
|
+
(long-running, retries, fan-out); the normal case has no queue.
|
|
576
|
+
- **A wrong step gets fixed at its source** — the Correction Protocol, not
|
|
577
|
+
a downstream workaround.
|
|
578
|
+
- **Tests gate every commit** in the sequence.
|
|
579
|
+
- A module's frontend code (hook, screen) is built after its API is
|
|
580
|
+
committed.
|
|
581
|
+
- The screen step doesn't start blank — `ux-planner` runs once per module,
|
|
582
|
+
after the hook is committed, before `front-end-eng` starts the screen.
|
|
583
|
+
- `packages/config` is the single source for shared config; a per-app
|
|
584
|
+
override request signals to fix the base config at the source.
|
|
585
|
+
|
|
586
|
+
## Stop Condition
|
|
587
|
+
|
|
588
|
+
A build session ends when `hedgehog status` shows every task for every
|
|
589
|
+
module in scope `complete` (Phase A and Phase B both closed), or when
|
|
590
|
+
scope is ambiguous enough that continuing means guessing — ask one
|
|
591
|
+
question and wait.
|
|
592
|
+
|
|
593
|
+
On the former (a real build completion, not an ambiguity stop), offer a
|
|
594
|
+
fresh-context handoff before doing anything else: tell the user the
|
|
595
|
+
build is complete, and that clearing context now costs nothing. The
|
|
596
|
+
permanent record is the committed intents, friction log, root
|
|
597
|
+
`core.yaml` (the shipped core definition — not `.hedgehog/core.yaml`,
|
|
598
|
+
which only exists on an authored core), and the commit history itself —
|
|
599
|
+
not `.hedgehog/hedgehog.db`, which is
|
|
600
|
+
gitignored and derived, rebuildable at any time via `hedgehog db
|
|
601
|
+
rebuild`. That's what makes the next session cheap.
|
|
602
|
+
|
|
603
|
+
Before offering that handoff, run `hedgehog boundary` and only declare
|
|
604
|
+
the Stop Condition met once it exits 0. Every task showing `complete` is
|
|
605
|
+
not sufficient on its own: a lease can be outstanding without a visible
|
|
606
|
+
status change, and the working tree can still hold uncommitted work.
|
|
607
|
+
`boundary` checks all three — nothing in flight, clean tree, last closed
|
|
608
|
+
task completed its intent — and names which one failed when it exits
|
|
609
|
+
non-zero. `hedgehog quiesce` covers only the first of the three; it is
|
|
610
|
+
the right check mid-correction, not the right check for a handoff.
|
|
611
|
+
|
|
612
|
+
The same command answers the mid-build question the project instructions
|
|
613
|
+
file's **Managing context** section depends on: whether *this* moment,
|
|
614
|
+
not just the end of the build, is one to clear the conversation at. Run
|
|
615
|
+
it at any point you're considering `/clear`, and start the next session
|
|
616
|
+
from `hedgehog boundary --handoff`, which prints where the build is,
|
|
617
|
+
what's next and why, and what's blocked, straight from the graph.
|
|
618
|
+
|
|
619
|
+
Name **both** ways forward, because which one applies depends on what the
|
|
620
|
+
user wants next:
|
|
621
|
+
|
|
622
|
+
- **Adjustments to what's built** — a `tweaker` session, in a *new* chat
|
|
623
|
+
window, not a subagent call inside this one — this session's context
|
|
624
|
+
has been building the whole project and is exactly what "clearing
|
|
625
|
+
context now costs nothing" above means to discard. Tell the user
|
|
626
|
+
plainly: close this chat window and open a new one, then paste this to
|
|
627
|
+
start it:
|
|
628
|
+
|
|
629
|
+
> The build is complete. Use the tweaker agent: first review the
|
|
630
|
+
> friction log and ask me for feedback on the build, then take my
|
|
631
|
+
> tweak requests one at a time.
|
|
632
|
+
|
|
633
|
+
In the new window, `tweaker` starts clean, reviews the friction log
|
|
634
|
+
(`hedgehog friction list`) once for a possible discipline-improvement
|
|
635
|
+
suggestion, and takes tweak requests one at a time from there.
|
|
636
|
+
- **New scope** — a new module, a new feature, anything beyond adjusting
|
|
637
|
+
what exists — goes to `planner`, which runs
|
|
638
|
+
`hedgehog-planning-intake`'s Re-entry pass: it adds intents for the new
|
|
639
|
+
work without re-running planning from scratch, and without disturbing
|
|
640
|
+
anything already built. A completed build is extendable, not sealed.
|
|
641
|
+
|
|
642
|
+
Don't start making tweaks or planning new scope in the current,
|
|
643
|
+
already-large context; that's what the fresh session is for.
|