@skyf0xx/hedgehog 5.0.0 → 5.1.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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # Turn AI from a code generator into a reliable software engineer ⭐
2
2
 
3
3
  [![Total downloads](https://raw.githubusercontent.com/skyf0xx/hedgehog/master/badges/npm-downloads.svg)](https://www.npmjs.com/package/@skyf0xx/hedgehog)
4
+ [![Support Hedgehog](https://raw.githubusercontent.com/skyf0xx/hedgehog/master/badges/kofi-top.svg)](https://ko-fi.com/skyf0xx)
4
5
 
5
6
  AI can write code in seconds.
6
7
 
@@ -42,6 +43,7 @@ Hedgehog combines:
42
43
  - **TDD and progressive layering** — build one tested layer at a time
43
44
  - **Mechanical enforcement** — use tooling and phase gates instead of trusting the AI to follow instructions
44
45
  - **Small context loops** — keep every change focused, verifiable, and easy to review
46
+ - **Deterministic scaffolding** — generate boilerplate and schemas instead of asking the AI to write it freehand
45
47
 
46
48
  Software that stays structured as it grows.
47
49
 
@@ -138,7 +140,7 @@ Artifact
138
140
 
139
141
  ### Anything else
140
142
 
141
- A CLI, a library, a browser extension, a data pipeline, etc. fitting neither shape gets its own build order, designed at intake rather than chosen from a menu — starting from a [battle-tested blueprint](src/skills/hedgehog-core-design/blueprints) for the system's shape where one exists.
143
+ A CLI, a library, a browser extension, a data pipeline, etc. fitting neither shape gets its own build order, designed at intake rather than chosen from a menu — starting from a battle-tested blueprint (in [`hedgehog-core-authored`](https://github.com/skyf0xx/hedgehog-core-authored)'s `hedgehog-core-design` skill) for the system's shape where one exists.
142
144
 
143
145
  Run `init` with no core flag: planning intake names the system shape, picks
144
146
  the stack, derives the layers, and locks them to `.hedgehog/core.yaml`,
@@ -195,17 +197,6 @@ npx @skyf0xx/hedgehog update
195
197
 
196
198
  This refreshes the installed agents and skills in a specific repo (note, not vendor skills)
197
199
 
198
- ### Installing a specific core
199
-
200
- Naming a core up front installs it with the shared payload. Without one, `init` installs the shared payload alone and leaves the choice to planning intake, where `planner` selects a core — `full-stack-app`, `landing-page`, or one it designs for the project:
201
-
202
- ``` bash
203
- npx @skyf0xx/hedgehog init --core full-stack-app
204
- npx @skyf0xx/hedgehog cores list # every core this release can install, and what each is for
205
- ```
206
-
207
- Each named core ships as its own npm package (`@skyf0xx/hedgehog-core-full-stack-app`, `@skyf0xx/hedgehog-core-landing-page`); `init` fetches the version `src/registry/cores.json` names and caches it locally, so a repeat install on the same version needs no network.
208
-
209
200
  ## Why Hedgehog
210
201
 
211
202
  Most AI coding tools improve prompting.
@@ -253,6 +244,8 @@ pnpm-only workspace convention.
253
244
 
254
245
  ## Support Hedgehog
255
246
 
256
- If Hedgehog helps you build better software with AI, give it a ⭐ on GitHub.
247
+ If Hedgehog helps you build better software with AI, **give it a ⭐ on GitHub**, or [buy me a coffee on Ko-fi](https://ko-fi.com/skyf0xx).
257
248
 
258
- [![GitHub stars](https://img.shields.io/github/stars/skyf0xx/hedgehog?style=social)](https://github.com/skyf0xx/hedgehog/stargazers)
249
+ [![GitHub stars](https://raw.githubusercontent.com/skyf0xx/hedgehog/master/badges/github-stars.svg)](https://github.com/skyf0xx/hedgehog/stargazers)
250
+ [![Total downloads](https://raw.githubusercontent.com/skyf0xx/hedgehog/master/badges/npm-downloads.svg)](https://www.npmjs.com/package/@skyf0xx/hedgehog)
251
+ [![Support Hedgehog](https://raw.githubusercontent.com/skyf0xx/hedgehog/master/badges/kofi.svg)](https://ko-fi.com/skyf0xx)
package/bin/cli.mjs CHANGED
@@ -5,6 +5,7 @@
5
5
  // Usage:
6
6
  // npx @skyf0xx/hedgehog init install; planner picks the core at intake
7
7
  // npx @skyf0xx/hedgehog init --ts-full-stack-app scaffold the full-stack-app core now
8
+ // npx @skyf0xx/hedgehog init --pwa-app scaffold the pwa-app core now
8
9
  // npx @skyf0xx/hedgehog init --landing-page scaffold the landing-page core now
9
10
  // npx @skyf0xx/hedgehog init --cursor install for Cursor (default: Claude Code)
10
11
  // npx @skyf0xx/hedgehog init --all-hosts install for every supported coding agent
@@ -36,6 +37,7 @@ import {
36
37
  releaseTask,
37
38
  renewLease,
38
39
  retryTask,
40
+ reapExpiredLeases,
39
41
  } from '../src/db/claim.mjs';
40
42
  import { readyTasks, formatReady } from '../src/db/ready.mjs';
41
43
  import { graphStatus, formatStatus } from '../src/db/status.mjs';
@@ -104,7 +106,7 @@ const DOTFILE_RENAMES = { 'gitignore.template': '.gitignore' };
104
106
 
105
107
  // Build artifacts that must never be copied into a consuming project,
106
108
  // even if a future publish accidentally includes them: the full-stack-app
107
- // golden core documents that `node_modules` is regenerated by `pnpm
109
+ // core documents that `node_modules` is regenerated by `pnpm
108
110
  // install` in step 4, not shipped, and a platform-specific `node_modules`
109
111
  // snapshot from the publisher's machine can be stale or broken for
110
112
  // everyone else (e.g. a dangling optional-dependency symlink).
@@ -148,7 +150,7 @@ async function namedCore(args) {
148
150
  // which core applies left for `planner` to decide. `bootstrap` lands the
149
151
  // core workspace and fills the CLAUDE.md section for whichever core
150
152
  // `planner` picks — the first time either way. An explicit flag
151
- // (`--ts-full-stack-app`, `--landing-page`) is a confirmed choice, so it
153
+ // (`--ts-full-stack-app`, `--pwa-app`, `--landing-page`) is a confirmed choice, so it
152
154
  // scaffolds that workspace immediately, at install time.
153
155
  //
154
156
  // A core is passed in as `{ manifest, root }` from fetchCore — the parsed
@@ -482,6 +484,7 @@ ${bold('Usage')}
482
484
  npx @skyf0xx/hedgehog init install; planner picks the core at intake
483
485
  npx @skyf0xx/hedgehog init --core <name> install that core now, by name
484
486
  npx @skyf0xx/hedgehog init --ts-full-stack-app scaffold the full-stack-app core now
487
+ npx @skyf0xx/hedgehog init --pwa-app scaffold the pwa-app core now
485
488
  npx @skyf0xx/hedgehog init --landing-page scaffold the landing-page core now
486
489
  npx @skyf0xx/hedgehog cores list every core this release can install
487
490
  npx @skyf0xx/hedgehog init --cursor install for Cursor (default: Claude Code)
@@ -811,7 +814,7 @@ async function update({ hosts }) {
811
814
  // refresh.
812
815
  const UNRESOLVED = Symbol('unresolved core');
813
816
 
814
- // A root `core.yaml` is a Golden Core's own file: `corePayload` lands it
817
+ // A root `core.yaml` is a core's own file: `corePayload` lands it
815
818
  // as part of that core's `workspace` at install time, so its presence is
816
819
  // direct evidence a core package's files are on disk — independent of
817
820
  // `.hedgehog/core.yaml`, the authored-core path, which is a different
@@ -928,7 +931,7 @@ async function dbCommand(args) {
928
931
  }
929
932
 
930
933
  // Resolves the project's core definition: an authored .hedgehog/core.yaml
931
- // takes precedence (spec: "Authored cores"); otherwise the Golden Core's
934
+ // takes precedence (spec: "Authored cores"); otherwise the core's
932
935
  // own core.yaml, which lands at repo root along with the rest of that
933
936
  // core package's workspace. Neither exists yet on a deferred install
934
937
  // (plain `init`, no core named) until `bootstrap` runs — this returns
@@ -1222,7 +1225,9 @@ async function intentCommand(args) {
1222
1225
  try {
1223
1226
  intent = await addIntent(db, record);
1224
1227
  } catch (err) {
1225
- console.error(`${red('Failed to add intent:')} ${err.message}\n`);
1228
+ console.error(
1229
+ `${red('Failed to add intent:')} ${err.message}\n\nUsage: hedgehog intent add --id <id> --goal <goal> --outcome <outcome> [--rule <r>]... [--depends-on <id>]...\n or: hedgehog intent add --file <path.json>\n`,
1230
+ );
1226
1231
  process.exitCode = 1;
1227
1232
  return;
1228
1233
  } finally {
@@ -2062,6 +2067,12 @@ async function statusCommand() {
2062
2067
  const db = openDb();
2063
2068
  let result;
2064
2069
  try {
2070
+ // A task whose lease expired with no intervening `claim`/`verify`
2071
+ // call is still sitting in `building`/`verifying` in the DB —
2072
+ // without this, status keeps reporting a dead agent's task as
2073
+ // in-flight indefinitely, and `hedgehog retry` refuses it as "not
2074
+ // blocked" until some other command happens to reap it first.
2075
+ reapExpiredLeases(db);
2065
2076
  result = graphStatus(db, { core, overrides });
2066
2077
  } finally {
2067
2078
  db.close();
@@ -2136,6 +2147,7 @@ async function quiesceCommand() {
2136
2147
  const db = openDb();
2137
2148
  let inFlight;
2138
2149
  try {
2150
+ reapExpiredLeases(db);
2139
2151
  ({ inFlight } = graphStatus(db));
2140
2152
  } finally {
2141
2153
  db.close();
@@ -2612,7 +2624,9 @@ async function debtCommand(args) {
2612
2624
  try {
2613
2625
  entry = addDebt(db, { taskId, note });
2614
2626
  } catch (err) {
2615
- console.error(`${red('Failed to declare debt:')} ${err.message}\n`);
2627
+ console.error(
2628
+ `${red('Failed to declare debt:')} ${err.message}\n\nRun ${bold('hedgehog status')} to see valid task ids.\n`,
2629
+ );
2616
2630
  process.exitCode = 1;
2617
2631
  return;
2618
2632
  } finally {
@@ -2698,6 +2712,10 @@ async function main() {
2698
2712
  await help();
2699
2713
  return;
2700
2714
  }
2715
+ if (args.includes('--version') || args.includes('-v')) {
2716
+ console.log(PKG_VERSION);
2717
+ return;
2718
+ }
2701
2719
  const cmd = args[0];
2702
2720
  const force = args.includes('--force') || args.includes('-f');
2703
2721
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyf0xx/hedgehog",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Install the Hedgehog build discipline (agents + skills) into a repo, for Claude Code, Cursor, or Gemini CLI.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -89,26 +89,85 @@ A core whose `when` fits and whose `flag` is listed is chosen by name
89
89
  and handed to `bootstrap`, which installs that core's package and
90
90
  follows the bootstrap skill it ships.
91
91
 
92
- Data that gets stored is `full-stack-app`, at any size: a todo list, a
93
- notes app, a tracker of any kind. Never talk the user down to
94
- browser-local storage, an in-memory array, or a single-file page because
95
- the app sounds small, and never offer that as a quicker start the core
96
- ships a real database, and reaching for less is the drift this
97
- discipline exists to prevent.
92
+ **Before matching against `when` paragraphs, ask up to three
93
+ clarifying questions if the description alone doesn't already settle
94
+ them.** Most users describing a project for the first time are not
95
+ naming their own architecture they're naming what the thing does
96
+ so this step exists to pull out the handful of facts that actually
97
+ distinguish one core's `when` from another's, in the same plain
98
+ language the user already used, not in the vocabulary of the cores
99
+ themselves. Skip a question outright whenever the description already
100
+ answers it; skip the whole step when the description is unambiguous
101
+ (a landing page with no state, an obvious full-stack SaaS with several
102
+ described entities and accounts) — this is a gap-filler, not a
103
+ mandatory interview. Never exceed three, and never ask more than one
104
+ at a time if the conversation is turn-by-turn.
105
+
106
+ The three questions this step exists for, asked as an engineer
107
+ eliciting requirements would ask them, never as an architecture quiz
108
+ the user can't answer without already knowing the answer:
109
+
110
+ - **Where does the data really need to live?** — "does this need to
111
+ work on one device, or be the same for you across your phone and
112
+ laptop, or shared with other people?" Distinguishes `pwa-app`
113
+ (single-device or a small, known set of people) from `full-stack-app`
114
+ (a system other people or services interact with independently) —
115
+ never phrase this as "will your app have a full API backend," which
116
+ assumes the answer.
117
+ - **Is there ongoing behavior, or just a page?** — "once it's built,
118
+ does anything keep happening on its own — reminders, scheduled
119
+ updates, other people's actions showing up — or is it something you
120
+ open and use?" Distinguishes `landing-page` (nothing ongoing, no
121
+ state of its own) from either app core.
122
+ - **Is this a one-time thing, or a real, evolving artifact?** — "is
123
+ this a one-off script or document, or something you'll keep coming
124
+ back to and adding to?" Separates "nothing to build here" and
125
+ "authored core" from the shipped cores above.
126
+
127
+ Answers here are inputs to the `when`-paragraph match below, not a
128
+ replacement for it — don't skip reading every core's `when` just
129
+ because a question landed on a name. State which questions you're
130
+ asking and why in one line each, so the user can see this is
131
+ narrowing a real ambiguity rather than a rote checklist. This step
132
+ never runs on re-entry (Phase 0 as a whole is skipped there) and never
133
+ substitutes for `hedgehog-planning-intake`'s own elicitation once a
134
+ core is chosen — that's a full BMAD-driven pass; this is three
135
+ questions to pick which pass to run.
136
+
137
+ Data that gets stored is not, by itself, `full-stack-app` — that used to
138
+ be the rule, and it swallowed every candidate for `pwa-app` (a tracker,
139
+ journal, notebook, or planner whose data belongs on the user's own
140
+ device). The real question is where the data lives and who needs to
141
+ enforce the rules around it. A description naming a local-first app —
142
+ offline capability or installability named explicitly is a strong
143
+ signal — is `pwa-app`, even with sharing, accounts, or multi-device sync
144
+ in scope (Dexie Cloud covers that), and even with a small number of
145
+ entities that must be server-authoritative (those go `--remote`, backed
146
+ by Supabase, without moving the whole project off `pwa-app`). What
147
+ routes a project to `full-stack-app` instead is server-side logic across
148
+ *most* of the app: authorization more expressive than per-object
149
+ row-level security, background jobs or webhooks as the app's primary
150
+ function, server-rendered or SEO-critical pages, or a working set too
151
+ large for a device. Never talk the user down to browser-local storage
152
+ for `full-stack-app` shape sounding small, and never talk them up to a
153
+ server for `pwa-app` shape sounding real — the core ships a real
154
+ database either way, and picking the wrong one because of what a core
155
+ "sounds like" is the drift this discipline exists to prevent.
98
156
 
99
157
  This is a distinct question from project *size*. A single-table, single-
100
- user tool (one person's task list, a personal habit tracker) is still
101
- `full-stack-app`, scoped through the Add-ons decision, not routed to
102
- landing-page for being small. Likewise a landing page with a dozen
103
- sections is still `landing-page`, not promoted to `full-stack-app` for
104
- being long. Shape decides the core; size decides nothing.
158
+ user tool (one person's task list, a personal habit tracker) fits
159
+ `pwa-app` if its data is local, or `full-stack-app` scoped through the
160
+ Add-ons decision if it isn't size alone decides neither. Likewise a
161
+ landing page with a dozen sections is still `landing-page`, not promoted
162
+ to `full-stack-app` for being long. Shape decides the core; size decides
163
+ nothing.
105
164
 
106
165
  Three outcomes are decided here rather than in the registry, because
107
166
  none of them is a description matching a `when` paragraph:
108
167
 
109
168
  - **Neither shipped core fits, but something is being built** — the
110
169
  description names a real artifact a Builder step would produce, just
111
- not in either Golden Core's shape. This project gets an **authored
170
+ not in either shipped core's shape. This project gets an **authored
112
171
  core**, designed by you and written to `.hedgehog/core.yaml`. Don't ask
113
172
  the user what layers to build in — someone who could name the right
114
173
  sequence unprompted wouldn't need a discipline to enforce it. Run
@@ -118,7 +177,7 @@ none of them is a description matching a `when` paragraph:
118
177
  archive: it names the system shape, picks the stack, derives the
119
178
  layers, decides the module axis, and writes `.hedgehog/core.yaml` plus
120
179
  its rationale at its own Confirm & Lock. An authored core is a weaker
121
- guarantee than a Golden Core (the sequence was designed for this
180
+ guarantee than a shipped core (the sequence was designed for this
122
181
  project, not battle-tested across many) but carries the same
123
182
  enforcement — ordered layers, scoped file access, verification before
124
183
  completion — and the loader has no leniency for it
@@ -143,7 +202,7 @@ none of them is a description matching a `when` paragraph:
143
202
  everything above: it's not about which core fits new work, because no
144
203
  new workspace gets built at all. Route straight to `hedgehog-adopt` —
145
204
  bootstrap and every other Phase 0 outcome are skipped entirely, since
146
- there is no workspace to scaffold and no golden stack to adopt toward.
205
+ there is no workspace to scaffold and no shipped stack to adopt toward.
147
206
  `hedgehog-adopt` runs its own read-only intake and writes its own
148
207
  `.hedgehog/core.yaml`; don't run `hedgehog-planning-intake`'s BMAD shelf
149
208
  first — the drivers that skill elicits (persistence, stack, deployment
@@ -174,6 +233,15 @@ the first-run shape; on re-entry, run `hedgehog-planning-intake`'s
174
233
  Hedgehog's own build discipline."* BMAD elicits and produces planning
175
234
  documents; it has no execution discipline of its own — Hedgehog starts
176
235
  where BMAD's output ends.
236
+ - **`pwa-app`** → open `hedgehog-planning-intake` and follow it in full,
237
+ exactly as `full-stack-app` does: Phase 0 runs the same vendored BMAD
238
+ shelf, archived to `.hedgehog/BMAD/`; Phase 1 mines `04-prd.md` into
239
+ intent records the same way, one domain module per PRD Feature; the
240
+ same Confirm & Lock stage is the hard stop before anything gets
241
+ written. State the same BMAD attribution as full-stack-app before that
242
+ Phase 0 begins. The one addition: this core's own Add-ons-equivalent
243
+ decision (sync, remote entities — see below) instead of
244
+ full-stack-app's Auth/Queue/Mobile.
177
245
  - **`landing-page`** → open `hedgehog-landing-loop`'s planning-intake
178
246
  section and follow it: it opens with `hedgehog-planning-intake`'s
179
247
  Phase 0 (the same vendored BMAD shelf `full-stack-app` runs, in full,
@@ -190,9 +258,9 @@ the first-run shape; on re-entry, run `hedgehog-planning-intake`'s
190
258
  `hedgehog-planning-intake`.
191
259
 
192
260
  Either way, this is the mechanical procedure; the judgment — what's
193
- actually in scope, where a table becomes a module (full-stack-app) or
194
- what the page's single job actually is (landing-page) — stays yours
195
- throughout.
261
+ actually in scope, where a table becomes a module (full-stack-app,
262
+ pwa-app) or what the page's single job actually is (landing-page) —
263
+ stays yours throughout.
196
264
 
197
265
  ## The Add-ons decision (full-stack-app only)
198
266
 
@@ -246,6 +314,42 @@ differently. Written once at Phase 1; a later run (new scope entering
246
314
  play) only edits it if new scope genuinely changes a trigger (e.g.
247
315
  accounts get added where there were none).
248
316
 
317
+ ## The sync/remote-entities decision (pwa-app only)
318
+
319
+ Two independent booleans, the `pwa-app` counterpart to full-stack-app's
320
+ Add-ons decision above — same mechanism (`.hedgehog/addons.yaml`, decided
321
+ once while mining `04-prd.md`, shown at Confirm & Lock), different
322
+ triggers, since this core has no Auth/Queue/Mobile infra to decide:
323
+
324
+ - **Sync** — on if the PRD describes more than one user or more than one
325
+ device sharing the same data (a shared list, a two-person journal, a
326
+ small team's board) — Dexie Cloud, wired by this core's bootstrap skill.
327
+ - **Remote entities** — on if the PRD names at least one entity a client
328
+ must not be able to write to directly (a points balance, a reward
329
+ ledger, anything server-arbitrated) — Supabase, wired the same way.
330
+ This is a project-wide bootstrap decision (whether the Supabase client
331
+ gets wired at all); *which* entities are generated `--remote` is a
332
+ later, per-entity, build-time choice, not decided here.
333
+
334
+ Infer first, gap-fill second, same discipline as the Add-ons decision:
335
+ "is this used by more than one person, or shared across your own
336
+ devices?" for sync, "is there any balance or record here a user
337
+ shouldn't be able to edit directly?" for remote entities. A "no" is a
338
+ resolved answer. Neither disqualifies `pwa-app` as the core — see Phase
339
+ 0 above — they only decide what bootstrap wires.
340
+
341
+ ```yaml
342
+ sync:
343
+ on: true
344
+ reason: shared list, two members
345
+ remote_entities:
346
+ on: false
347
+ reason: no server-authoritative entity in scope
348
+ ```
349
+
350
+ A project can take either, both, or neither — independent booleans, same
351
+ as full-stack-app's Auth/Queue/Mobile trio.
352
+
249
353
  ## Core Responsibilities
250
354
 
251
355
  - Decide which core applies before running any planning-intake skill —
@@ -256,6 +360,10 @@ accounts get added where there were none).
256
360
  artifacts; the
257
361
  intent records Phase 1 writes via `hedgehog intent add` live in the
258
362
  build graph, not a file this agent owns.
363
+ - **pwa-app**: same shape as full-stack-app — owns `.hedgehog/BMAD/` and
364
+ `.hedgehog/addons.yaml` (sync, remote entities, per "The sync/
365
+ remote-entities decision" above) as artifacts; intent records live in
366
+ the build graph.
259
367
  - **landing-page**: owns `.hedgehog/BMAD/` and
260
368
  `.hedgehog/chain/00-brief.md` as artifacts.
261
369
  - **brownfield adoption**: owns nothing here — `hedgehog-adopt` owns
@@ -294,7 +402,9 @@ accounts get added where there were none).
294
402
 
295
403
  Read the commit log alongside it for what's already built —
296
404
  full-stack-app: `feat(<module>): api` commits and each task's status in
297
- the graph mark modules with a closed Phase A. Landing-page: a
405
+ the graph mark modules with a closed Phase A. pwa-app: `feat(<module>):
406
+ screen` commits and each task's status mark a module's closed
407
+ sequence, per its own five-layer `core.yaml`. Landing-page: a
298
408
  `complete` phase task marks that phase's artifact as committed.
299
409
  Authored core: each `complete` task marks that layer committed, per
300
410
  `.hedgehog/core.yaml`'s own commit messages. On re-entry this is what
@@ -319,6 +429,9 @@ accounts get added where there were none).
319
429
  the user directly only for whatever the PRD leaves unresolved. The
320
430
  mining step is the same either way; only how the archive was
321
431
  produced differs.
432
+ - **First run, pwa-app**: identical procedure to full-stack-app above,
433
+ substituting the sync/remote-entities decision for the Add-ons
434
+ decision.
322
435
  - **First run, landing-page**: run the same vendored BMAD shelf in
323
436
  full, then mine `.hedgehog/BMAD/` into a draft subject statement
324
437
  (subject, audience, single page job) — asking the user directly only
@@ -328,7 +441,8 @@ accounts get added where there were none).
328
441
  only what's new — the BMAD shelf does not run again.
329
442
  6. **Run the matching Confirm & Lock** before writing anything — the
330
443
  first-run stage on a first run, the extension variant on re-entry.
331
- 7. **Write the intent records**: full-stack-app writes each intent via
444
+ 7. **Write the intent records**: full-stack-app and pwa-app each write
445
+ every intent via
332
446
  `hedgehog intent add`, one call per PRD Feature (per new module, on
333
447
  re-entry), plus `.hedgehog/addons.yaml`; landing-page writes
334
448
  `.hedgehog/chain/00-brief.md` per its own Confirm & Lock, in the shape
@@ -344,8 +458,8 @@ accounts get added where there were none).
344
458
  `chore(planning): intake` on a first run, `chore(planning): extend
345
459
  scope` on re-entry, so the passes are distinguishable in the log. It
346
460
  carries the committed `.hedgehog/hedgehog.db` (its new intent and task
347
- rows), `.hedgehog/addons.yaml` (full-stack-app only, and on re-entry
348
- only if a trigger actually changed), this core's own archival planning
461
+ rows), `.hedgehog/addons.yaml` (full-stack-app and pwa-app only, and on
462
+ re-entry only if a trigger actually changed), this core's own archival planning
349
463
  output (`.hedgehog/BMAD/` or `.hedgehog/chain/`, first run only), the
350
464
  authored core's `.hedgehog/core.yaml` and `.hedgehog/core-design.md` if
351
465
  step 4 ran, and root `CLAUDE.md`'s filled placeholders (first run
@@ -354,8 +468,9 @@ accounts get added where there were none).
354
468
  intake's own unit of work, landed before `bootstrap` touches anything.
355
469
  9. **First run only, and not on the brownfield path — hand off to the
356
470
  `bootstrap` agent** once the commit lands. It scaffolds the chosen
357
- core's workspace (and, for full-stack-app, whichever add-ons are on)
358
- before any build step starts. On re-entry on any other core the
471
+ core's workspace (and, for full-stack-app, whichever add-ons are on;
472
+ for pwa-app, whichever of sync/remote entities is on) before any build
473
+ step starts. On re-entry on any other core the
359
474
  workspace already exists: hand straight to that core's loop skill
360
475
  instead, which picks the new work up from `hedgehog next`.
361
476
  10. **Return a summary**: which core (naming it as authored or adopted,
@@ -365,8 +480,9 @@ accounts get added where there were none).
365
480
  ## Constraints
366
481
 
367
482
  - Never write or modify application code. Read-only against the
368
- codebase; you may write `.hedgehog/addons.yaml` (full-stack-app only —
369
- see "The Add-ons decision" below), `.hedgehog/core.yaml` and
483
+ codebase; you may write `.hedgehog/addons.yaml` (full-stack-app and
484
+ pwa-app only — see "The Add-ons decision" and "The sync/remote-entities
485
+ decision" below), `.hedgehog/core.yaml` and
370
486
  `.hedgehog/core-design.md` (authored cores only, via
371
487
  `hedgehog-core-design`), `.hedgehog/core.yaml` and
372
488
  `.hedgehog/adoption.md` (brownfield adoption only, via
@@ -378,7 +494,7 @@ accounts get added where there were none).
378
494
  `hedgehog intent add` and `hedgehog plan` are how you write the build
379
495
  graph itself — not a file you edit directly.
380
496
  - On the brownfield path, never route toward converting the host repo's
381
- existing stack, structure, or conventions toward any Golden Core's —
497
+ existing stack, structure, or conventions toward any shipped core's —
382
498
  not even as a suggestion. `hedgehog-adopt` designs `verify` commands
383
499
  and layer order around what the repo already uses; it doesn't propose
384
500
  Nx, Drizzle, or any other opinionated choice a shipped core would make.
package/src/db/claim.mjs CHANGED
@@ -373,6 +373,13 @@ export function claimTask(db, taskId, { owner, leaseMinutes = 45 }) {
373
373
  // would — still subject to its dependencies being complete.
374
374
  export function retryTask(db, taskId) {
375
375
  return inTransaction(db, () => {
376
+ // Same reason verifyTask reaps before its own status check: a task
377
+ // whose lease expired with no intervening `claim` call is still
378
+ // sitting in `building`/`verifying` here, not yet swept to `blocked`
379
+ // — without this, retry on that task reads "not blocked" and refuses,
380
+ // even though the lease is in fact dead and this is exactly the
381
+ // situation retry exists to recover from.
382
+ reapExpiredLeases(db);
376
383
  const task = loadTask(db, taskId);
377
384
  if (task === undefined) return { retried: false, reason: 'no_such_task' };
378
385
  if (task.status !== 'blocked') return { retried: false, reason: 'not_blocked', task };
package/src/db/core.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // Core-definition loader. Parses `core.yaml` — a Golden Core's own shipped
1
+ // Core-definition loader. Parses `core.yaml` — a shipped core's own
2
2
  // definition, or an authored .hedgehog/core.yaml — into the same in-memory
3
3
  // shape either way. See hedgehog-persistent-build-graph.md, "Core
4
4
  // definitions".
@@ -19,6 +19,7 @@ export const AGENT_CAPABILITY = {
19
19
  'landing-executor': 'full',
20
20
  'layer-eng': 'full',
21
21
  planner: 'full',
22
+ 'pwa-eng': 'full',
22
23
  tweaker: 'full',
23
24
 
24
25
  // Author artifacts, but never run commands.
@@ -5,6 +5,9 @@ one, read its file and follow it for that task, passing the `hedgehog
5
5
  next` packet in full. Where a subagent is available, pass the file's
6
6
  entire body as that subagent's prompt — the file is the role.
7
7
 
8
+ Independent steps can go out as several concurrent tool calls in one
9
+ response. Steps that depend on each other stay sequential.
10
+
8
11
  Each agent file opens with the tools that role may use. Honor it: Cursor
9
12
  grants tools per session, so the constraint is yours to keep. `hedgehog
10
13
  verify` checks the touched files against the packet's ALLOWED SCOPE and
@@ -7,7 +7,16 @@
7
7
  "version": "^1.0.0",
8
8
  "language": "typescript",
9
9
  "repository": "https://github.com/skyf0xx/hedgehog-core-full-stack-app",
10
- "selects_when": "The description names persistent domain data with its own lifecycle: something that gets created, changes state, gets queried back later, or needs accounts/auth, background jobs, or a real app beyond a single page. Data that gets stored is this core at any size — a todo list, a notes app, a tracker of any kind never talked down to browser-local storage or a single-file page for sounding small. If the project has both a marketing page and a real app behind it, this is still full-stack-app: the page becomes routes inside apps/web, not a separate project."
10
+ "selects_when": "The description names server-side logic across most of the app: authorization more expressive than per-object row-level security, background jobs, scheduled work or webhook receivers as the app's primary function, server-rendered or SEO-critical pages beyond an app shell, or a data model whose working set doesn't sensibly fit on a device. Persistent domain data alone is not the signal — a tracker, journal, notebook, or planner whose data fits on the user's device is pwa-app, even with sharing, accounts, or multi-device sync in scope, and even with one or two entities that must be server-authoritative. If the project has both a marketing page and a real app behind it, this is still full-stack-app: the page becomes routes inside apps/web, not a separate project."
11
+ },
12
+ {
13
+ "name": "pwa-app",
14
+ "flag": "--pwa-app",
15
+ "package": "@skyf0xx/hedgehog-core-pwa-app",
16
+ "version": "^1.0.0",
17
+ "language": "typescript",
18
+ "repository": "https://github.com/skyf0xx/hedgehog-core-pwa-app",
19
+ "selects_when": "The description names an app whose data model fits on the user's device and whose reads and writes are the user's own — a tracker, journal, notebook, planner, offline reference, or utility. Offline capability or installability named explicitly is a strong signal. Sharing, collaboration, accounts, and multi-device sync do NOT disqualify a project: Dexie Cloud provides sync, authentication, and server-enforced per-object access control, so a shared list, a family calendar, or a small team's board is still this core. A small number of entities that must be server-authoritative — a points balance, a reward ledger, anything a client must not write to directly — do NOT disqualify a project either: those entities are declared --remote and backed by Supabase (Postgres + RLS + Edge Functions) behind the same repository interface, while the rest of the app stays local-first. What routes a project to full-stack-app is server-side logic across most of the app, not the presence of one or two such entities: authorization beyond row-level policies, background jobs or webhooks as the app's primary function, server-rendered pages, or a working set too large for a device."
11
20
  },
12
21
  {
13
22
  "name": "landing-page",
@@ -24,7 +33,7 @@
24
33
  "version": "^1.0.0",
25
34
  "language": "typescript",
26
35
  "repository": "https://github.com/skyf0xx/hedgehog-core-authored",
27
- "selects_when": "Neither shipped core fits, but the description names a real artifact a Builder step would produce — just not in either Golden Core's shape. This core is designed by the planner rather than chosen from a fixed set: hedgehog-planning-intake's Phase 0 elicits the drivers first, then hedgehog-core-design names the system shape, picks the stack, derives the layers, and writes .hedgehog/core.yaml. It carries the same enforcement as a Golden Core — ordered layers, scoped file access, verification before completion — but the sequence is designed for this project rather than battle-tested across many."
36
+ "selects_when": "Neither shipped core fits, but the description names a real artifact a Builder step would produce — just not in either shipped core's shape. This core is designed by the planner rather than chosen from a fixed set: hedgehog-planning-intake's Phase 0 elicits the drivers first, then hedgehog-core-design names the system shape, picks the stack, derives the layers, and writes .hedgehog/core.yaml. It carries the same enforcement as a shipped core — ordered layers, scoped file access, verification before completion — but the sequence is designed for this project rather than battle-tested across many."
28
37
  }
29
38
  ]
30
39
  }
@@ -1,6 +1,6 @@
1
1
  // Core package registry. One entry per Hedgehog core — full-stack-app,
2
2
  // landing-page, authored — naming the npm package that ships its agents,
3
- // skills, and (for the two Golden Cores) scaffold, plus the CLI flag
3
+ // skills, and (for the two shipped cores) scaffold, plus the CLI flag
4
4
  // `hedgehog init` accepts for it and the prose `planner` reads aloud in
5
5
  // Phase 0 to choose one. A fixed table, one entry per core, discovered by
6
6
  // name or flag rather than convention.
@@ -1,24 +1,25 @@
1
1
  ---
2
2
  name: hedgehog-planning-intake
3
- description: Use on any core for first-run planning intake — Phase 0 runs the vendored BMAD-METHOD planning shelf, shared by every core, and Phase 1 (mining `04-prd.md` into intent records plus the Add-ons decision) is full-stack-app's own procedure. Phase 0 also defines compressed intake, the path a user's explicit "just build it" choice takes on full-stack-app and authored cores: one batched round of questions in place of the shelf, writing the same archive at the same path so Phase 1, `ux-planner`, and the Re-entry pass all keep their documented source. Also use for the Re-entry pass, which mines new scope into additional intents without re-running the shelf, on any core with a module axis to add an intent to (full-stack-app, authored) — landing-page has none, so its own new-scope path runs through `hedgehog-landing-loop`'s Correction Protocol instead. Invoked by the `planner` agent, which decides the path; don't run standalone. landing-page runs this skill's Phase 0 on first run, then mines the same archive through `hedgehog-landing-loop`'s own planning-intake section, that core's counterpart to this skill's Phase 1. An authored core runs this skill's Phase 0, then `hedgehog-core-design`, then this skill's Phase 1 mining against the designed layer sequence. A brownfield adoption (`hedgehog-adopt`) never runs this skill's shelf at all — the drivers BMAD elicits are already settled facts of a repo that already exists.
3
+ description: Use on any core for first-run planning intake — Phase 0 runs the vendored BMAD-METHOD planning shelf, shared by every core, and Phase 1 (mining `04-prd.md` into intent records plus the Add-ons/sync-and-remote-entities decision) is full-stack-app's and pwa-app's shared procedure — identical mechanics, a different decision at step 5/8. Phase 0 also defines compressed intake, the path a user's explicit "just build it" choice takes on full-stack-app, pwa-app, and authored cores: one batched round of questions in place of the shelf, writing the same archive at the same path so Phase 1, `ux-planner`, and the Re-entry pass all keep their documented source. Also use for the Re-entry pass, which mines new scope into additional intents without re-running the shelf, on any core with a module axis to add an intent to (full-stack-app, pwa-app, authored) — landing-page has none, so its own new-scope path runs through `hedgehog-landing-loop`'s Correction Protocol instead. Invoked by the `planner` agent, which decides the path; don't run standalone. landing-page runs this skill's Phase 0 on first run, then mines the same archive through `hedgehog-landing-loop`'s own planning-intake section, that core's counterpart to this skill's Phase 1. An authored core runs this skill's Phase 0, then `hedgehog-core-design`, then this skill's Phase 1 mining against the designed layer sequence. A brownfield adoption (`hedgehog-adopt`) never runs this skill's shelf at all — the drivers BMAD elicits are already settled facts of a repo that already exists.
4
4
  ---
5
5
 
6
6
  # Hedgehog Planning Intake
7
7
 
8
8
  Turns a person's description of a problem into planning material, by
9
- running the vendored BMAD-METHOD planning shelf (Phase 0, shared by both
10
- cores) and mining its output. On full-stack-app that mining is this
11
- skill's own Phase 1, into intent records written via `hedgehog intent
9
+ running the vendored BMAD-METHOD planning shelf (Phase 0, shared by
10
+ every core) and mining its output. On full-stack-app and pwa-app that
11
+ mining is this skill's own Phase 1, into intent records written via
12
+ `hedgehog intent
12
13
  add`; on landing-page it's `hedgehog-landing-loop`'s planning-intake
13
14
  section, into a subject/audience/job statement. This is the mechanics
14
15
  `planner` calls once its Phase 0 core-selection check has picked a core —
15
16
  the interpretive judgment (which Feature becomes which intent, Confirm &
16
17
  Lock either way) belongs to `planner`; this skill (Phase 0, and Phase 1 on
17
- full-stack-app) and `hedgehog-landing-loop` (landing-page's own mining)
18
- are the fixed procedures that judgment runs inside.
18
+ full-stack-app and pwa-app) and `hedgehog-landing-loop` (landing-page's
19
+ own mining) are the fixed procedures that judgment runs inside.
19
20
 
20
21
  That shelf run is a **first run**, once per project. When new scope
21
- enters play later on a core with a module axis (full-stack-app,
22
+ enters play later on a core with a module axis (full-stack-app, pwa-app,
22
23
  authored), `planner` runs the **Re-entry pass** at the end of this file
23
24
  instead: it reads the existing archive as context and elicits only what's
24
25
  new, adding intents to a graph that keeps everything already built.
@@ -98,7 +99,7 @@ states the source repo, pinned version (`vendor-skills/BMAD/ATTRIBUTION.md` has
98
99
  the pinned commit), date, which intake mode ran (`full`, below, or
99
100
  `compressed`), and which skills ran.
100
101
 
101
- ### Compressed intake (full-stack-app, authored core)
102
+ ### Compressed intake (full-stack-app, pwa-app, authored core)
102
103
 
103
104
  A user who opens with "just build it" — no clarifying questions — is
104
105
  asking for something Phase 0's live elicitation can't give them.
@@ -120,11 +121,13 @@ compressing the elicitation removes the thing the chain audits against.
120
121
  A "just build it" landing-page request is a conflict to surface, not a
121
122
  mode to switch into.
122
123
 
123
- **The Add-ons decision is what the batched round is for.** Auth, Queue,
124
- and Mobile must each be *answered* inferred from a concrete trigger in
125
- the user's brief, or asked directly in that one round. Compressed intake
126
- compresses BMAD's elicitation, never `planner`'s gate; an add-on left as
127
- a guess is the same error here as on a full run.
124
+ **The Add-ons decision (or, on pwa-app, the sync/remote-entities
125
+ decision) is what the batched round is for.** Auth, Queue, and Mobile
126
+ (full-stack-app) or sync and remote entities (pwa-app) must each be
127
+ *answered*: inferred from a concrete
128
+ trigger in the user's brief, or asked directly in that one round.
129
+ Compressed intake compresses BMAD's elicitation, never `planner`'s gate;
130
+ a boolean left as a guess is the same error here as on a full run.
128
131
 
129
132
  Write the manifest and the PRD always, and the experience spec where the
130
133
  brief gives it something to say — at the same path and in the same
@@ -174,11 +177,12 @@ drivers, and that's the user's call to accept there.
174
177
  Nothing in `hedgehog-loop`'s day-to-day operation, `hedgehog-bootstrap`,
175
178
  or `reviewer` reads this folder live — `planner` reads it exactly once,
176
179
  right after the shelf completes, to mine it (this skill's Phase 1 below
177
- on full-stack-app; `hedgehog-landing-loop`'s planning-intake section on
180
+ on full-stack-app and pwa-app; `hedgehog-landing-loop`'s planning-intake
181
+ section on
178
182
  landing-page). After that it's historical record only, the same
179
183
  relationship the commit log has to a merged PR.
180
184
 
181
- ## Phase 1 — Mining (full-stack-app only)
185
+ ## Phase 1 — Mining (full-stack-app, pwa-app)
182
186
 
183
187
  landing-page's counterpart to this Phase 1 is
184
188
  `hedgehog-landing-loop`'s own planning-intake section, run once Phase 0
@@ -211,7 +215,8 @@ Procedure:
211
215
  description across the two if it names both the capability and the
212
216
  result; otherwise the same sentence can serve both).
213
217
 
214
- On a module-axis core (`full-stack-app`, and any authored core whose
218
+ On a module-axis core (`full-stack-app`, `pwa-app`, and any authored
219
+ core whose
215
220
  layers scope by `{module}`), **name the id plural** — `tasks`, not
216
221
  `task`; `order-items`, not `order-item`. The id is substituted as
217
222
  `{module}` into every layer's scope glob and verify command, and the
@@ -238,8 +243,10 @@ Procedure:
238
243
  holding the foreign key depends on the intent for the entity it
239
244
  references. A relationship entirely inside one Feature's entities
240
245
  produces no row — it's already the same intent.
241
- 5. **Run the Add-ons decision** (`planner`'s own judgment call — see that
242
- agent's "The Add-ons decision") for Auth, Queue, and Mobile.
246
+ 5. **Run this core's Add-ons-equivalent decision** (`planner`'s own
247
+ judgment call): full-stack-app's "The Add-ons decision" (Auth, Queue,
248
+ Mobile), or pwa-app's "The sync/remote-entities decision" (sync,
249
+ remote entities) — see that agent.
243
250
  6. **Run Confirm & Lock** (below) before writing anything.
244
251
  7. **Write each intent via `hedgehog intent add`** — one invocation per
245
252
  Feature: `--acceptance` per row from step 2, `--rule` per row from step
@@ -247,7 +254,7 @@ Procedure:
247
254
  <path.json>` batch matching the same shape (`{ id, goal, outcome,
248
255
  rules, acceptance, depends_on, priority }`). This is Phase 1's only
249
256
  write to the build graph.
250
- 8. **Write `.hedgehog/addons.yaml`** with the Add-ons decision from step 5.
257
+ 8. **Write `.hedgehog/addons.yaml`** with the decision from step 5.
251
258
  9. **Fill root `CLAUDE.md`'s `{{PROJECT_NAME}}` and `{{PROJECT_SUMMARY}}`
252
259
  placeholders**, first run only, then delete the installer's HTML
253
260
  comment block at the top of that file. Leave every other line
@@ -266,8 +273,9 @@ stops being true, so it's a hard stop, not a recap in passing.
266
273
 
267
274
  - Each intent about to be added: `id`, `goal`, `outcome`, its
268
275
  `requirements` (rule/acceptance), and its `depends_on` list.
269
- - The Add-ons decision (Auth / Queue / Mobile, each explicitly on or
270
- off, with the one-line reason).
276
+ - This core's Add-ons-equivalent decision, each boolean explicitly on or
277
+ off with the one-line reason (Auth / Queue / Mobile on full-stack-app;
278
+ sync / remote entities on pwa-app).
271
279
  - Which intake mode ran, and on a full run which BMAD skills ran — or,
272
280
  on a compressed run, what the batched round asked and what was inferred
273
281
  from the brief without asking. Either way, where the output lives
@@ -276,7 +284,7 @@ stops being true, so it's a hard stop, not a recap in passing.
276
284
  Then state plainly what happens on confirmation, before it happens:
277
285
 
278
286
  > This writes each intent above via `hedgehog intent add` and the
279
- > Add-ons decision to `.hedgehog/addons.yaml`, then shows the compiled
287
+ > decision above to `.hedgehog/addons.yaml`, then shows the compiled
280
288
  > graph with `hedgehog status`. Phase A build (schema first) starts on
281
289
  > the first ready task once that closes. Anything wrong or missing — say
282
290
  > so now; it's a normal edit before this point, and a Correction Protocol
@@ -288,7 +296,7 @@ the confirmation holds. Once confirmed, after every `hedgehog intent add`
288
296
  call lands, run `hedgehog status` and show it in full as the graph's
289
297
  confirmation view.
290
298
 
291
- ## Re-entry pass — new scope on an existing project (full-stack-app, authored core)
299
+ ## Re-entry pass — new scope on an existing project (full-stack-app, pwa-app, authored core)
292
300
 
293
301
  Runs when `planner`'s Workflow step 2 finds intents already in the graph,
294
302
  on a core with a module axis to add an intent to: new scope entering play
@@ -321,8 +329,8 @@ their commits.
321
329
  3. **Elicit only what's new.** A short, scoped set of questions — not a
322
330
  full interview:
323
331
  - What is the new scope, in the project's own vocabulary?
324
- - Which entities/tables does it introduce? On full-stack-app each
325
- table is its own module, same rule as Phase 1.
332
+ - Which entities/tables does it introduce? On full-stack-app and
333
+ pwa-app each table is its own module, same rule as Phase 1.
326
334
  - What does it depend on that already exists? Each answer becomes a
327
335
  `--depends-on` onto an existing intent.
328
336
  - What has to be true for it to be done? Each answer becomes an
@@ -334,10 +342,13 @@ their commits.
334
342
  If the answers reveal this isn't new scope at all but a change to
335
343
  something already built, stop: that's the Correction Protocol, not an
336
344
  extension.
337
- 4. **Check whether any add-on trigger actually changed** (full-stack-app).
338
- Usually none has. Only if the new scope genuinely introduces one — the
339
- first accounts in a project that had none, the first long-running job —
340
- edit `.hedgehog/addons.yaml`, and say plainly that turning an add-on on
345
+ 4. **Check whether any boolean trigger actually changed**
346
+ (full-stack-app's Add-ons, or pwa-app's sync/remote-entities). Usually
347
+ none has. Only if the new scope genuinely introduces one the
348
+ first accounts in a project that had none, the first long-running job,
349
+ the first shared device on a pwa-app project, the first entity that
350
+ must be server-authoritative —
351
+ edit `.hedgehog/addons.yaml`, and say plainly that turning a boolean on
341
352
  after bootstrap needs its Bootstrap step run before anything depends on
342
353
  it. Never rewrite root `CLAUDE.md`'s `{{PROJECT_NAME}}`/
343
354
  `{{PROJECT_SUMMARY}}` placeholders here; they describe the project,