@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.
- package/README.md +9 -3
- package/bin/cli.mjs +463 -19
- package/package.json +3 -2
- package/src/agents/backend-eng.md +56 -45
- package/src/agents/bootstrap.md +67 -73
- package/src/agents/front-end-eng.md +31 -18
- package/src/agents/planner.md +163 -84
- package/src/agents/reviewer.md +4 -4
- package/src/agents/tweaker.md +138 -106
- package/src/db/core.mjs +141 -0
- package/src/db/friction.mjs +25 -0
- package/src/db/init.mjs +35 -0
- package/src/db/intent.mjs +101 -0
- package/src/db/next.mjs +179 -0
- package/src/db/plan.mjs +222 -0
- package/src/db/schema.mjs +95 -0
- package/src/db/status.mjs +113 -0
- package/src/db/verify.mjs +286 -0
- package/src/db/why.mjs +97 -0
- package/src/golden-cores/full-stack-app/core.yaml +41 -0
- package/src/golden-cores/landing-page/core.yaml +41 -0
- package/src/skills/conventional-commits/SKILL.md +1 -1
- package/src/skills/hedgehog-bootstrap/SKILL.md +38 -41
- package/src/skills/hedgehog-bootstrap-full-stack-app-core/SKILL.md +8 -12
- package/src/skills/hedgehog-bootstrap-landing-page-core/SKILL.md +7 -9
- package/src/skills/hedgehog-core-design/SKILL.md +239 -0
- package/src/skills/hedgehog-landing-loop/SKILL.md +91 -57
- package/src/skills/hedgehog-loop/SKILL.md +109 -77
- package/src/skills/hedgehog-planning-intake/SKILL.md +72 -97
- package/src/templates/CLAUDE.core.full-stack-app.md +31 -24
- package/src/templates/CLAUDE.core.landing-page.md +11 -7
- package/src/templates/CLAUDE.md +46 -38
- package/src/templates/TODO.core.full-stack-app.md +0 -51
- package/src/templates/TODO.core.landing-page.md +0 -31
- 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
|
-
-
|
|
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 (
|
|
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
|
|
14
|
-
this closes, `hedgehog-loop` takes
|
|
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
|
|
76
|
-
|
|
77
|
-
sequence below; turning it off means that step is skipped
|
|
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 —
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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,
|
|
193
|
-
|
|
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
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
`hedgehog-bootstrap-full-stack-app-core`)
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
253
|
-
|
|
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
|