@skyf0xx/hedgehog 2.0.13 → 3.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.
Files changed (35) hide show
  1. package/README.md +9 -3
  2. package/bin/cli.mjs +463 -19
  3. package/package.json +3 -2
  4. package/src/agents/backend-eng.md +56 -45
  5. package/src/agents/bootstrap.md +67 -73
  6. package/src/agents/front-end-eng.md +31 -18
  7. package/src/agents/planner.md +163 -84
  8. package/src/agents/reviewer.md +4 -4
  9. package/src/agents/tweaker.md +138 -106
  10. package/src/db/core.mjs +141 -0
  11. package/src/db/friction.mjs +25 -0
  12. package/src/db/init.mjs +35 -0
  13. package/src/db/intent.mjs +101 -0
  14. package/src/db/next.mjs +179 -0
  15. package/src/db/plan.mjs +222 -0
  16. package/src/db/schema.mjs +95 -0
  17. package/src/db/status.mjs +113 -0
  18. package/src/db/verify.mjs +286 -0
  19. package/src/db/why.mjs +97 -0
  20. package/src/golden-cores/full-stack-app/core.yaml +41 -0
  21. package/src/golden-cores/landing-page/core.yaml +41 -0
  22. package/src/skills/conventional-commits/SKILL.md +1 -1
  23. package/src/skills/hedgehog-bootstrap/SKILL.md +38 -41
  24. package/src/skills/hedgehog-bootstrap-full-stack-app-core/SKILL.md +8 -12
  25. package/src/skills/hedgehog-bootstrap-landing-page-core/SKILL.md +7 -9
  26. package/src/skills/hedgehog-core-design/SKILL.md +239 -0
  27. package/src/skills/hedgehog-landing-loop/SKILL.md +91 -57
  28. package/src/skills/hedgehog-loop/SKILL.md +109 -77
  29. package/src/skills/hedgehog-planning-intake/SKILL.md +72 -97
  30. package/src/templates/CLAUDE.core.full-stack-app.md +31 -24
  31. package/src/templates/CLAUDE.core.landing-page.md +11 -7
  32. package/src/templates/CLAUDE.md +46 -38
  33. package/src/templates/TODO.core.full-stack-app.md +0 -51
  34. package/src/templates/TODO.core.landing-page.md +0 -31
  35. package/src/templates/TODO.md +0 -12
@@ -0,0 +1,286 @@
1
+ // `hedgehog verify <task-id>` — scope pre-check, then verify_command, then
2
+ // state transition. See hedgehog-persistent-build-graph.md, "Task
3
+ // lifecycle", "`hedgehog verify`", and "Scope enforcement is a hard
4
+ // pre-verification check".
5
+ //
6
+ // Two gates, in order:
7
+ // 1. `git diff --name-only` (working tree) against the task's
8
+ // scope_globs. Any touched path outside scope refuses to run
9
+ // verification at all — the task stays `implemented`, no
10
+ // `verifications` row written. This is a scope violation, not a
11
+ // failing check.
12
+ // 2. Only once every touched path matches scope does verify_command run.
13
+ // Exit 0: verifications row (passed) → verified → artifacts recorded
14
+ // → git commit with commit_message → complete → direct dependents
15
+ // re-evaluated (a dependent is ready once every dependency is
16
+ // complete — same check as the readiness SELECT in next.mjs).
17
+ // Nonzero: verifications row (failed, output retained) → failed,
18
+ // dependents stay blocked.
19
+
20
+ import { execSync } from 'node:child_process';
21
+ import { DB_PATH } from './init.mjs';
22
+
23
+ // The build graph file itself is engine state, written only by this CLI,
24
+ // never by an agent — it's excluded from every task's scope check (and
25
+ // from artifacts/commits), or verify's own writes ahead of the
26
+ // verify_command run would trip the very check it's performing. Covers
27
+ // SQLite's journal/WAL/SHM sidecar files too.
28
+ function isEngineStatePath(path) {
29
+ return path === DB_PATH || path.startsWith(`${DB_PATH}-`);
30
+ }
31
+
32
+ // Minimal glob → RegExp, no dependency: `**` matches any path segment
33
+ // span (including zero), `*` matches within one segment, everything else
34
+ // is literal. Matches the scope_globs shapes core.yaml produces, e.g.
35
+ // `packages/db/src/schema/**`, `libs/{module}/repository/**`.
36
+ function globToRegExp(glob) {
37
+ let pattern = '';
38
+ for (let i = 0; i < glob.length; i++) {
39
+ const c = glob[i];
40
+ if (c === '*' && glob[i + 1] === '*') {
41
+ pattern += '.*';
42
+ i++;
43
+ if (glob[i + 1] === '/') i++;
44
+ } else if (c === '*') {
45
+ pattern += '[^/]*';
46
+ } else if ('.+^${}()|[]\\'.includes(c)) {
47
+ pattern += `\\${c}`;
48
+ } else {
49
+ pattern += c;
50
+ }
51
+ }
52
+ return new RegExp(`^${pattern}$`);
53
+ }
54
+
55
+ function matchesAnyGlob(path, globs) {
56
+ return globs.some((glob) => globToRegExp(glob).test(path));
57
+ }
58
+
59
+ // Working-tree diff (relative paths), covers modified, added, deleted,
60
+ // and untracked files — everything the agent could have touched.
61
+ function changedPaths() {
62
+ const tracked = execSync('git diff --name-only HEAD', { encoding: 'utf8' });
63
+ const untracked = execSync('git ls-files --others --exclude-standard', {
64
+ encoding: 'utf8',
65
+ });
66
+ const paths = new Set(
67
+ [...tracked.split('\n'), ...untracked.split('\n')].map((p) => p.trim()).filter(Boolean),
68
+ );
69
+ return [...paths].filter((p) => !isEngineStatePath(p));
70
+ }
71
+
72
+ function loadTask(db, taskId) {
73
+ return db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId);
74
+ }
75
+
76
+ function setTaskStatus(db, taskId, status) {
77
+ db.prepare('UPDATE tasks SET status = ? WHERE id = ?').run(status, taskId);
78
+ }
79
+
80
+ const insertVerification = (db) =>
81
+ db.prepare(`
82
+ INSERT INTO verifications (task_id, command, exit_code, output, status)
83
+ VALUES (?, ?, ?, ?, ?)
84
+ `);
85
+
86
+ const insertArtifact = (db) =>
87
+ db.prepare(`
88
+ INSERT INTO artifacts (task_id, path, kind, commit_sha)
89
+ VALUES (?, ?, ?, ?)
90
+ `);
91
+
92
+ // Direct dependents of `taskId` — same shape as next.mjs's
93
+ // loadDirectDependents, kept local since verify.mjs owns the write side
94
+ // and next.mjs owns the read side of the same query.
95
+ function loadDirectDependents(db, taskId) {
96
+ return db
97
+ .prepare(
98
+ `
99
+ SELECT t.* FROM tasks t
100
+ JOIN dependencies d ON d.task_id = t.id
101
+ WHERE d.depends_on_task_id = ?
102
+ ORDER BY t.priority, t.id
103
+ `,
104
+ )
105
+ .all(taskId);
106
+ }
107
+
108
+ // True when every dependency of `taskId` is `complete` — the same
109
+ // condition the readiness SELECT in next.mjs checks, reused here so a
110
+ // dependent is only ever unlocked by the one rule the engine has for
111
+ // readiness.
112
+ function hasNoIncompleteDependency(db, taskId) {
113
+ const blocker = db
114
+ .prepare(
115
+ `
116
+ SELECT 1 FROM dependencies d
117
+ JOIN tasks dep ON dep.id = d.depends_on_task_id
118
+ WHERE d.task_id = ? AND dep.status <> 'complete'
119
+ `,
120
+ )
121
+ .get(taskId);
122
+ return blocker === undefined;
123
+ }
124
+
125
+ // Marks `taskId`'s direct dependents `ready` wherever every one of their
126
+ // dependencies (not just this one) is now `complete`. Siblings with other
127
+ // unmet dependencies are left untouched (still `planned`, still blocked).
128
+ function unlockReadyDependents(db, taskId) {
129
+ const unlocked = [];
130
+ for (const dependent of loadDirectDependents(db, taskId)) {
131
+ if (hasNoIncompleteDependency(db, dependent.id)) {
132
+ setTaskStatus(db, dependent.id, 'ready');
133
+ unlocked.push(dependent.id);
134
+ }
135
+ }
136
+ return unlocked;
137
+ }
138
+
139
+ // Marks `intentId` complete once every task compiled from it is
140
+ // `complete`. Completion is terminal bookkeeping, not a cleanup trigger
141
+ // (spec: "Traceability") — the intent's tasks, verifications, and
142
+ // artifacts stay exactly where they are as the provenance trail; the
143
+ // status change only stops `hedgehog plan` from treating it as pending
144
+ // and lets `status`/`next` report the intent honestly.
145
+ function completeIntentIfDone(db, intentId) {
146
+ const openTask = db
147
+ .prepare(
148
+ "SELECT 1 FROM tasks WHERE intent_id = ? AND status <> 'complete'",
149
+ )
150
+ .get(intentId);
151
+ if (openTask !== undefined) return false;
152
+ db.prepare("UPDATE intents SET status = 'complete' WHERE id = ?").run(intentId);
153
+ return true;
154
+ }
155
+
156
+ // Runs `command` via the shell, capturing combined stdout+stderr and exit
157
+ // code without throwing on nonzero exit — a failing verify_command is an
158
+ // expected outcome, not a Node exception.
159
+ function runVerifyCommand(command) {
160
+ try {
161
+ const output = execSync(command, { encoding: 'utf8', stdio: 'pipe' });
162
+ return { exitCode: 0, output };
163
+ } catch (err) {
164
+ const output = `${err.stdout ?? ''}${err.stderr ?? ''}` || err.message;
165
+ return { exitCode: err.status ?? 1, output };
166
+ }
167
+ }
168
+
169
+ // Determines created vs modified against HEAD, then stages and commits
170
+ // exactly the task's touched paths with commit_message. Returns the new
171
+ // commit sha.
172
+ //
173
+ // The build graph is committed in the same commit as the work it
174
+ // describes. The spec's "SQLite as build state" requires the DB be
175
+ // committed to git — that's what makes state survive `/clear`, machine
176
+ // moves, and reclone. Committing it here (rather than leaving it dirty
177
+ // for a later hand-commit) keeps the graph and the code it tracks
178
+ // atomic: a checkout of any commit has a build graph that agrees with
179
+ // the tree. The DB is excluded from the *scope check* (it's engine
180
+ // state, not agent output — see isEngineStatePath) but still belongs in
181
+ // the commit.
182
+ function commitTouchedPaths(paths, commitMessage) {
183
+ const quoted = paths.map((p) => JSON.stringify(p)).join(' ');
184
+ execSync(`git add -- ${quoted}`, { stdio: 'pipe' });
185
+ execSync(`git commit -m ${JSON.stringify(commitMessage)}`, { stdio: 'pipe' });
186
+ return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
187
+ }
188
+
189
+ // Amends the just-written commit to include the build graph. Run after
190
+ // the DB transaction commits, so the file on disk already reflects this
191
+ // task's completion — staging it earlier would capture a pre-commit
192
+ // snapshot of the graph and defeat the point.
193
+ function commitBuildGraph(commitSha) {
194
+ try {
195
+ execSync(`git add -- ${JSON.stringify(DB_PATH)}`, { stdio: 'pipe' });
196
+ execSync('git commit --amend --no-edit', { stdio: 'pipe' });
197
+ return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
198
+ } catch {
199
+ // The graph failing to commit must not undo verified work — the task
200
+ // is already complete and its code is already committed.
201
+ return commitSha;
202
+ }
203
+ }
204
+
205
+ function artifactKind(path) {
206
+ try {
207
+ execSync(`git cat-file -e HEAD:${JSON.stringify(path)}`, { stdio: 'pipe' });
208
+ return 'modified';
209
+ } catch {
210
+ return 'created';
211
+ }
212
+ }
213
+
214
+ // Runs the full verify flow for `taskId`. Returns a result object
215
+ // describing what happened:
216
+ // { outcome: 'scope_violation', offending: [...] }
217
+ // { outcome: 'failed', exitCode, output }
218
+ // { outcome: 'complete', exitCode, output, commitSha, unlocked: [...] }
219
+ export function verifyTask(db, taskId) {
220
+ const task = loadTask(db, taskId);
221
+ if (!task) throw new Error(`no such task: ${taskId}`);
222
+
223
+ const scopeGlobs = JSON.parse(task.scope_globs);
224
+ const touched = changedPaths();
225
+ const offending = touched.filter((path) => !matchesAnyGlob(path, scopeGlobs));
226
+
227
+ if (offending.length > 0) {
228
+ setTaskStatus(db, task.id, 'implemented');
229
+ return { outcome: 'scope_violation', offending };
230
+ }
231
+
232
+ const { exitCode, output } = runVerifyCommand(task.verify_command);
233
+ const runInsertVerification = insertVerification(db);
234
+
235
+ if (exitCode !== 0) {
236
+ db.exec('BEGIN');
237
+ try {
238
+ runInsertVerification.run(task.id, task.verify_command, exitCode, output, 'failed');
239
+ setTaskStatus(db, task.id, 'failed');
240
+ db.exec('COMMIT');
241
+ } catch (err) {
242
+ db.exec('ROLLBACK');
243
+ throw err;
244
+ }
245
+ return { outcome: 'failed', exitCode, output };
246
+ }
247
+
248
+ db.exec('BEGIN');
249
+ let commitSha;
250
+ let unlocked;
251
+ let intentComplete;
252
+ try {
253
+ runInsertVerification.run(task.id, task.verify_command, exitCode, output, 'passed');
254
+ setTaskStatus(db, task.id, 'verified');
255
+
256
+ const runInsertArtifact = insertArtifact(db);
257
+ for (const path of touched) {
258
+ runInsertArtifact.run(task.id, path, artifactKind(path), null);
259
+ }
260
+
261
+ commitSha =
262
+ touched.length > 0 ? commitTouchedPaths(touched, task.commit_message) : null;
263
+
264
+ if (commitSha) {
265
+ db.prepare('UPDATE artifacts SET commit_sha = ? WHERE task_id = ?').run(
266
+ commitSha,
267
+ task.id,
268
+ );
269
+ }
270
+
271
+ setTaskStatus(db, task.id, 'complete');
272
+ unlocked = unlockReadyDependents(db, task.id);
273
+ intentComplete = completeIntentIfDone(db, task.intent_id);
274
+
275
+ db.exec('COMMIT');
276
+ } catch (err) {
277
+ db.exec('ROLLBACK');
278
+ throw err;
279
+ }
280
+
281
+ // Only now does the DB file on disk reflect this task's completion, so
282
+ // this is the first point the graph can be committed truthfully.
283
+ if (commitSha) commitSha = commitBuildGraph(commitSha);
284
+
285
+ return { outcome: 'complete', exitCode, output, commitSha, unlocked, intentComplete };
286
+ }
package/src/db/why.mjs ADDED
@@ -0,0 +1,97 @@
1
+ // `hedgehog why <path>` — walks artifacts → tasks → task_requirements →
2
+ // requirements → intents for a given file path, printing the full
3
+ // provenance chain. See hedgehog-persistent-build-graph.md,
4
+ // "Traceability": artifact → task (with its verification) → requirement
5
+ // → intent.
6
+
7
+ function loadArtifacts(db, path) {
8
+ return db
9
+ .prepare('SELECT * FROM artifacts WHERE path = ? ORDER BY id')
10
+ .all(path);
11
+ }
12
+
13
+ function loadTask(db, taskId) {
14
+ return db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId);
15
+ }
16
+
17
+ // Most recent verification for the task — the one that proved the
18
+ // artifact, per "Traceability"'s "verified: pnpm nx test db, exit 0".
19
+ function loadLatestVerification(db, taskId) {
20
+ return db
21
+ .prepare(
22
+ `
23
+ SELECT * FROM verifications
24
+ WHERE task_id = ?
25
+ ORDER BY id DESC
26
+ LIMIT 1
27
+ `,
28
+ )
29
+ .get(taskId);
30
+ }
31
+
32
+ function loadTaskRequirements(db, taskId) {
33
+ return db
34
+ .prepare(
35
+ `
36
+ SELECT r.* FROM requirements r
37
+ JOIN task_requirements tr ON tr.requirement_id = r.id
38
+ WHERE tr.task_id = ?
39
+ `,
40
+ )
41
+ .all(taskId);
42
+ }
43
+
44
+ function loadIntent(db, intentId) {
45
+ return db.prepare('SELECT * FROM intents WHERE id = ?').get(intentId);
46
+ }
47
+
48
+ // Walks the chain for one artifact row: its task, that task's latest
49
+ // verification, the requirements it satisfies, and their owning intent.
50
+ function traceArtifact(db, artifact) {
51
+ const task = loadTask(db, artifact.task_id);
52
+ const verification = task ? loadLatestVerification(db, task.id) : null;
53
+ const requirements = task ? loadTaskRequirements(db, task.id) : [];
54
+ const intent = task ? loadIntent(db, task.intent_id) : null;
55
+ return { artifact, task, verification, requirements, intent };
56
+ }
57
+
58
+ // Returns the full provenance chain for `path`: one entry per artifacts
59
+ // row (a path can be touched by more than one task across its history),
60
+ // each carrying its task, latest verification, requirements, and intent.
61
+ // Empty array when the path has never been recorded as an artifact.
62
+ export function whyPath(db, path) {
63
+ return loadArtifacts(db, path).map((artifact) => traceArtifact(db, artifact));
64
+ }
65
+
66
+ function formatVerification(verification) {
67
+ if (!verification) return '(no verification recorded)';
68
+ return `${verification.command} — exit ${verification.exit_code}, ${verification.status}`;
69
+ }
70
+
71
+ // Renders whyPath()'s chain into the artifact → task → requirement →
72
+ // intent shape from the spec's "Traceability" diagram.
73
+ export function formatWhy(path, chain) {
74
+ if (chain.length === 0) {
75
+ return `${path}\n (no artifact recorded for this path)`;
76
+ }
77
+
78
+ const lines = [];
79
+ lines.push(path);
80
+ for (const { task, verification, requirements, intent } of chain) {
81
+ lines.push(` ↑ artifact of`);
82
+ lines.push(`${task.id} (verified: ${formatVerification(verification)})`);
83
+ if (requirements.length === 0) {
84
+ lines.push(` ↑ satisfies`);
85
+ lines.push(`(no requirements linked)`);
86
+ } else {
87
+ for (const req of requirements) {
88
+ lines.push(` ↑ satisfies`);
89
+ lines.push(`requirement: "${req.statement}"`);
90
+ }
91
+ }
92
+ lines.push(` ↑ of`);
93
+ lines.push(`intent: ${intent.id}`);
94
+ }
95
+
96
+ return lines.join('\n');
97
+ }
@@ -0,0 +1,41 @@
1
+ # Shipped core definition for full-stack-app. Layer order, scope, and
2
+ # verify commands are the same shape src/skills/hedgehog-loop's Domain
3
+ # Module step tables describe in prose — this file is their data form.
4
+ # {module} is filled in by the compiler with the domain module name a
5
+ # task is generated for.
6
+ id: full-stack-app
7
+ layers:
8
+ - id: schema
9
+ scope: ["packages/db/src/schema/**"]
10
+ verify: "pnpm nx test db && pnpm typecheck"
11
+ commit: "feat({module}): schema"
12
+ - id: contract
13
+ depends_on: schema
14
+ scope: ["packages/contracts/src/**"]
15
+ verify: "pnpm nx test contracts && pnpm typecheck"
16
+ commit: "feat({module}): contract"
17
+ - id: repository
18
+ depends_on: contract
19
+ scope: ["libs/{module}/repository/**"]
20
+ verify: "pnpm nx test {module}-repository"
21
+ commit: "feat({module}): repository"
22
+ - id: service
23
+ depends_on: repository
24
+ scope: ["libs/{module}/service/**"]
25
+ verify: "pnpm nx test {module}-service"
26
+ commit: "feat({module}): service"
27
+ - id: controller
28
+ depends_on: service
29
+ scope: ["apps/api/**"]
30
+ verify: "pnpm nx test api"
31
+ commit: "feat({module}): api"
32
+ - id: hook
33
+ depends_on: controller
34
+ scope: ["packages/hooks/src/**"]
35
+ verify: "pnpm nx test hooks && pnpm typecheck"
36
+ commit: "feat({module}): hooks"
37
+ - id: screen
38
+ depends_on: hook
39
+ scope: ["apps/web/**", "apps/mobile/**"]
40
+ verify: "pnpm nx test web && pnpm nx test mobile"
41
+ commit: "feat({module}): screen-web"
@@ -0,0 +1,41 @@
1
+ # Shipped core definition for landing-page. A linear chain, no module
2
+ # axis — the degenerate case of the layer graph (spec: MVP scope item 5).
3
+ # Each phase groups the rows of src/skills/hedgehog-landing-loop's Chain
4
+ # Method table that share one agent context and one commit:
5
+ # brief — planning intake's mined subject statement (Phase 0)
6
+ # feeling — Strategist/Brand Anthropologist/Psychologist/Perfumer (landing-strategist)
7
+ # tokens — Ingredient Director/Copywriter/Systems Designer/Signature Element (landing-systems)
8
+ # sequence — Sequencer, Headline, per-section Copywriter (landing-sequencer, landing-headline-writer, landing-copywriter)
9
+ # artifact — Critic + Builder (landing-critic, landing-builder)
10
+ #
11
+ # feeling, tokens, and sequence each write their own chain-record file
12
+ # (same pattern "brief" already uses) as the artifact hedgehog verify
13
+ # checks for — a test -s only proves the file was written, not that the
14
+ # judgment inside it is sound; that judgment stays landing-critic's job
15
+ # at the artifact layer, unchanged.
16
+ id: landing-page
17
+ layers:
18
+ - id: brief
19
+ scope: [".hedgehog/chain/00-brief.md"]
20
+ verify: "test -s .hedgehog/chain/00-brief.md"
21
+ commit: "chore(planning): intake"
22
+ - id: feeling
23
+ depends_on: brief
24
+ scope: [".hedgehog/chain/01-feeling.md"]
25
+ verify: "test -s .hedgehog/chain/01-feeling.md"
26
+ commit: "feat(landing): strategy"
27
+ - id: tokens
28
+ depends_on: feeling
29
+ scope: [".hedgehog/chain/02-tokens.md", "src/styles/global.css"]
30
+ verify: "test -s .hedgehog/chain/02-tokens.md"
31
+ commit: "feat(landing): systems"
32
+ - id: sequence
33
+ depends_on: tokens
34
+ scope: [".hedgehog/chain/03-sequence.md"]
35
+ verify: "test -s .hedgehog/chain/03-sequence.md"
36
+ commit: "feat(landing): sequence"
37
+ - id: artifact
38
+ depends_on: sequence
39
+ scope: ["src/**"]
40
+ verify: "pnpm build"
41
+ commit: "feat(landing): build"
@@ -42,7 +42,7 @@ Run in parallel:
42
42
  - `git diff` (unstaged)
43
43
  - `git diff --staged` (anything pre-staged)
44
44
  - `git log -10 --oneline` to confirm the project's existing commit style
45
- - Check `TODO.md` for which steps/modules are in flight
45
+ - Run `hedgehog status` for which steps/modules are in flight
46
46
 
47
47
  Read every changed file's diff fully. You cannot group changes you
48
48
  haven't read.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hedgehog-bootstrap
3
- description: Use once, at the start of a new Hedgehog project, to land the core workspace and scaffold whichever add-ons (Auth, Queue, Mobile) planning intake turned on (TODO.md's Add-ons block). Runs when the `bootstrap` agent runs, which `planner` invokes automatically after Confirm & Lock. Scoped to project scaffolding; per-module work runs through the `hedgehog-loop` skill, one step at a time.
3
+ description: Use once, at the start of a new Hedgehog project, to land the core workspace and scaffold whichever add-ons (Auth, Queue, Mobile) planning intake turned on (`.hedgehog/addons.yaml`). Runs when the `bootstrap` agent runs, which `planner` invokes automatically after Confirm & Lock. Scoped to project scaffolding; per-module work runs through the `hedgehog-loop` skill, one step at a time.
4
4
  ---
5
5
 
6
6
  # Hedgehog Bootstrap
@@ -10,8 +10,9 @@ whichever named add-ons (Auth, Queue, Mobile) planning intake's scope
10
10
  boundary (`planner`, running BMAD-METHOD's planning shelf then mining it
11
11
  — see that agent) actually calls for. This is Phase 2 (Scaffold) of the
12
12
  overall bootstrap sequence — Phase 0 (BMAD elicitation) and Phase 1
13
- (mining into `TODO.md`) already closed by the time this skill runs. After
14
- this closes, `hedgehog-loop` takes over per module, one step at a time.
13
+ (mining into the build graph and `.hedgehog/addons.yaml`) already closed
14
+ by the time this skill runs. After this closes, `hedgehog-loop` takes
15
+ over per module, one step at a time.
15
16
  This skill touches no domain modules — no schema, no contract, nothing
16
17
  under `libs/<module>/`. That's Phase A, started fresh after Bootstrap
17
18
  closes.
@@ -72,10 +73,10 @@ Bootstrap runs — not a per-project hand-edit after landing core.
72
73
  ### Add-ons (scaffolded only when planning intake calls for them)
73
74
 
74
75
  Each row is independent — on or off per project, decided at planning
75
- intake's Confirm & Lock (`planner`) and recorded in `TODO.md`'s
76
- `## Add-ons` block. Turning one on inserts its Bootstrap step(s) into the
77
- sequence below; turning it off means that step is skipped entirely, not
78
- stubbed or partially wired.
76
+ intake's Confirm & Lock (`planner`) and recorded in
77
+ `.hedgehog/addons.yaml`. Turning one on inserts its Bootstrap step(s)
78
+ into the sequence below; turning it off means that step is skipped
79
+ entirely, not stubbed or partially wired.
79
80
 
80
81
  | Add-on | Trigger (from planning intake scope) | Adds |
81
82
  |---|---|---|
@@ -137,28 +138,27 @@ bar doesn't get the seam at all — see the Add-ons table above.
137
138
 
138
139
  ## Before running
139
140
 
140
- Confirm planning intake already happened — a scope boundary and domain
141
- vocabulary should exist (`planner` produces these from BMAD's planning
142
- shelf), **and** `TODO.md` should carry an explicit `## Add-ons` block
143
- recording which add-ons (Auth, Queue, Mobile) are on for this project. No
144
- scope boundary yet, or a `TODO.md` with no `## Add-ons` block: stop and
145
- point to `planner` rather than guessing which add-ons apply.
141
+ Confirm planning intake already happened — intent records should exist in
142
+ the build graph (`hedgehog status`) and `.hedgehog/addons.yaml` should
143
+ exist, recording which add-ons (Auth, Queue, Mobile) are on for this
144
+ project. No intents yet, or no `.hedgehog/addons.yaml`: stop and point to
145
+ `planner` rather than guessing which add-ons apply.
146
146
 
147
147
  Run `hedgehog-bootstrap-full-stack-app-core` first, unconditionally, if it hasn't
148
- already landed core (check `TODO.md`'s Bootstrap section, or `nx.json`
149
- at the repo root). That skill has its own re-run guard and Docker check
150
- — don't duplicate those here.
148
+ already landed core (check the commit log, or `nx.json` at the repo
149
+ root). That skill has its own re-run guard and Docker check — don't
150
+ duplicate those here.
151
151
 
152
152
  ## Steps (run in sequence, one commit per step that actually runs)
153
153
 
154
154
  ### 1. `packages/auth` — Better Auth config *(Auth add-on only)*
155
155
 
156
156
  Skip this step entirely if Auth isn't on for this project (check
157
- `TODO.md`'s `## Add-ons` block) — don't scaffold a credential store with
158
- no login anywhere in scope. If skipped, check its
159
- `TODO.md` line off as skipped-and-confirmed (per the `bootstrap` agent's
160
- handling of conditional steps), same treatment as an out-of-scope
161
- `apps/mobile`.
157
+ `.hedgehog/addons.yaml`) — don't scaffold a credential store with no
158
+ login anywhere in scope. If skipped, say so plainly (per the `bootstrap`
159
+ agent's handling of conditional steps) and move on — `.hedgehog/addons.yaml`'s
160
+ `auth.on: false` entry is already the durable record, nothing further to
161
+ write — same treatment as an out-of-scope `apps/mobile`.
162
162
 
163
163
  ```bash
164
164
  npx nx g @nx/js:lib packages/auth --bundler=none --unitTestRunner=vitest
@@ -187,18 +187,18 @@ Commit: `feat(auth): better auth config + global guard`
187
187
  ### 2. `apps/worker` — BullMQ seam (Redis, no consumers yet) *(Queue add-on only)*
188
188
 
189
189
  Skip this step entirely if Queue isn't on for this project (check
190
- `TODO.md`'s `## Add-ons` block) — no operation in scope is long-running,
190
+ `.hedgehog/addons.yaml`) — no operation in scope is long-running,
191
191
  retried, or fanned out, so there's nothing for a queue to seam in for. If
192
- skipped, check its `TODO.md` line off as
193
- skipped-and-confirmed, same treatment as an out-of-scope `apps/mobile`.
192
+ skipped, say so plainly and move on — same treatment as an out-of-scope
193
+ `apps/mobile`.
194
194
 
195
195
  ```bash
196
196
  npx nx g @nx/node:app apps/worker
197
197
  pnpm add bullmq ioredis
198
198
  ```
199
199
 
200
- Add a `redis` service to the root `docker-compose.yml`
201
- `hedgehog-bootstrap-full-stack-app-core` landed (Postgres-only until now) and
200
+ Add a `redis` service to the root `docker-compose.yml` that
201
+ `hedgehog-bootstrap-full-stack-app-core` landed (Postgres-only) and
202
202
  `REDIS_URL: z.string().url()` to `packages/config/env.schema.ts` (it
203
203
  doesn't exist in the core schema), plus a matching `REDIS_URL=` line in
204
204
  the root `.env.example` pointing at that same `docker-compose.yml`
@@ -232,10 +232,9 @@ Commit: `feat(worker): bullmq seam, no consumers`
232
232
  ### 3. `apps/mobile` — Expo shell *(Mobile add-on only)*
233
233
 
234
234
  Skip this step entirely if Mobile isn't on for this project (check
235
- `TODO.md`'s `## Add-ons` block) — don't scaffold speculative infra. If
236
- skipped, check its `TODO.md` line off as skipped-and-confirmed, not left
237
- dangling for a future run to wonder about — same pattern as Auth (step 1)
238
- and Queue (step 2) when their add-on is off.
235
+ `.hedgehog/addons.yaml`) — don't scaffold speculative infra. If skipped,
236
+ say so plainly and move on — same pattern as Auth (step 1) and Queue
237
+ (step 2) when their add-on is off.
239
238
 
240
239
  ```bash
241
240
  npx nx g @nx/expo:app apps/mobile
@@ -278,23 +277,21 @@ A per-app override request signals to fix the base config at the source.
278
277
 
279
278
  ## After Bootstrap
280
279
 
281
- Update `TODO.md`: check off every add-on line now built or explicitly
282
- skipped (core's four lines are already checked by
283
- `hedgehog-bootstrap-full-stack-app-core`). Leave Phase A/B sections as-is (per-module,
284
- filled in by `planner` during planning intake or when new scope enters play).
285
- Hand off to `hedgehog-loop` — from here, every domain module goes
286
- through Phase A steps 1–5(a) one at a time, gated by lefthook, each its
287
- own commit.
280
+ Once every `on` add-on in `.hedgehog/addons.yaml` has its commit landed
281
+ (core's commit already landed via
282
+ `hedgehog-bootstrap-full-stack-app-core`), hand off to `hedgehog-loop` —
283
+ from here, every domain module goes through Phase A layers one at a
284
+ time via `hedgehog next`/`hedgehog verify`, each its own commit.
288
285
 
289
286
  ## Constraints
290
287
 
291
288
  - Run `hedgehog-bootstrap-full-stack-app-core` first, unconditionally, before any step
292
289
  in this file — never scaffold an add-on against a core that hasn't
293
290
  landed and verified clean.
294
- - Add-on steps (Auth, Queue, Mobile) run only if `TODO.md`'s `## Add-ons`
295
- block (written by `planner` at planning intake) turns that add-on on —
296
- check off its `TODO.md` line as skipped-and-confirmed otherwise, don't
297
- leave it dangling.
291
+ - Add-on steps (Auth, Queue, Mobile) run only if `.hedgehog/addons.yaml`
292
+ (written by `planner` at planning intake) turns that add-on on — say so
293
+ plainly and skip otherwise, don't leave it ambiguous whether the step
294
+ was considered.
298
295
  - Don't add domain schema, contracts, or any `libs/<module>/*` content —
299
296
  that's Phase A, started after Bootstrap, one module at a time.
300
297
  - Don't deviate from the package/library choices above, for whichever
@@ -17,9 +17,9 @@ calls this skill first, unconditionally, then continues with its own
17
17
  add-on steps (Auth, Queue, Mobile) — those genuinely vary per project
18
18
  and stay live.
19
19
 
20
- This skill has no per-project decisions to make: no `TODO.md` Add-ons
21
- dependency, no Add-ons check, nothing to ask. Core is identical on every
22
- Hedgehog project.
20
+ This skill has no per-project decisions to make: no Add-ons dependency,
21
+ no Add-ons check, nothing to ask. Core is identical on every Hedgehog
22
+ project.
23
23
 
24
24
  ## What lands
25
25
 
@@ -142,13 +142,9 @@ Homebrew-installed shadow.
142
142
  feat(config): workspace + shared config
143
143
  ```
144
144
 
145
- One commit for all of core, landed as a verified copy.
146
-
147
- ### 7. `TODO.md`'s core lines
148
-
149
- The four core Bootstrap lines ship pre-checked in the `TODO.md` template.
150
- If step 3's fallback copy ran, check them now. Leave every add-on line
151
- (Auth/Queue/Mobile) untouched; `hedgehog-bootstrap` owns those.
145
+ One commit for all of core, landed as a verified copy. That commit
146
+ existing is the record that core landed — `bootstrap` checks for it via
147
+ the commit log, not a checklist line.
152
148
 
153
149
  ## Known issues baked into the full-stack-app core
154
150
 
@@ -249,8 +245,8 @@ not here.
249
245
 
250
246
  - Run once per project, always as `hedgehog-bootstrap`'s first move —
251
247
  never invoked on its own by a user.
252
- - No add-on awareness. If a check here ever seems to need `TODO.md`'s
253
- `## Add-ons` block, that check belongs in `hedgehog-bootstrap`
248
+ - No add-on awareness. If a check here ever seems to need
249
+ `.hedgehog/addons.yaml`, that check belongs in `hedgehog-bootstrap`
254
250
  instead — this skill's whole point is being identical across every
255
251
  project.
256
252
  - Don't hand-edit any file this step lands to work around a verification