@skyf0xx/hedgehog 6.1.1 → 6.1.3

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/bin/cli.mjs CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  reapExpiredLeases,
42
42
  } from '../src/db/claim.mjs';
43
43
  import { readyTasks, formatReady } from '../src/db/ready.mjs';
44
- import { graphStatus, formatStatus } from '../src/db/status.mjs';
44
+ import { graphStatus, formatStatus, inFlightTasks, formatBrief } from '../src/db/status.mjs';
45
45
  import { boundaryState, formatBoundary, formatPosition, formatHandoff } from '../src/db/boundary.mjs';
46
46
  import { commitGateStatus, formatCommitGate } from '../src/db/gate.mjs';
47
47
  import { detectDrift, recompileTasks, formatRecompile } from '../src/db/drift.mjs';
@@ -559,6 +559,7 @@ ${bold('Usage')}
559
559
  npx @skyf0xx/hedgehog verify <task-id> --owner <owner> run scope + verify checks, commit on pass
560
560
  npx @skyf0xx/hedgehog status graph overview: counts by status, ready list, in flight,
561
561
  and any drift from core.yaml
562
+ npx @skyf0xx/hedgehog status --brief one line: what is in flight, and nothing else
562
563
  npx @skyf0xx/hedgehog ready preview which ready tasks are claimable now vs held back
563
564
  npx @skyf0xx/hedgehog quiesce report whether anything is still in flight
564
565
  npx @skyf0xx/hedgehog boundary is this a moment to clear context? exits 0 only if it is,
@@ -2529,7 +2530,18 @@ async function coreWarningLines() {
2529
2530
  ];
2530
2531
  }
2531
2532
 
2532
- async function statusCommand() {
2533
+ // `--brief` answers one question — is anything `building` or
2534
+ // `verifying` — and prints one line. It exists because `hedgehog-daily`
2535
+ // asks that question before every change request, including the ones
2536
+ // that end in a two-line edit, and the full report below costs a core
2537
+ // load, a drift comparison, a readiness simulation, an override scan,
2538
+ // a commit-gate probe and an update check to answer it. The default
2539
+ // report is unchanged: this adds a cheaper question, it does not
2540
+ // replace the existing one, and a "yes" here is the cue to read the
2541
+ // full report.
2542
+ async function statusCommand(args = []) {
2543
+ const brief = args.includes('--brief');
2544
+
2533
2545
  await ensureDb();
2534
2546
 
2535
2547
  if (!(await exists(DB_PATH))) {
@@ -2538,6 +2550,21 @@ async function statusCommand() {
2538
2550
  return;
2539
2551
  }
2540
2552
 
2553
+ if (brief) {
2554
+ const db = openDb();
2555
+ let inFlight;
2556
+ try {
2557
+ // Same reaping contract as the full report — a dead agent's
2558
+ // expired lease must not read as in-flight work on either path.
2559
+ reapExpiredLeases(db);
2560
+ inFlight = inFlightTasks(db);
2561
+ } finally {
2562
+ db.close();
2563
+ }
2564
+ console.log(formatBrief(inFlight));
2565
+ return;
2566
+ }
2567
+
2541
2568
  // Drift needs the core definition to compare against. A project
2542
2569
  // without one yet (deferred install, pre-bootstrap) simply gets the
2543
2570
  // status it always got; an unparseable one is reported but never
@@ -3514,7 +3541,7 @@ async function main() {
3514
3541
  }
3515
3542
 
3516
3543
  if (cmd === 'status') {
3517
- await statusCommand();
3544
+ await statusCommand(args.slice(1));
3518
3545
  return;
3519
3546
  }
3520
3547
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyf0xx/hedgehog",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
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": {
@@ -18,7 +18,12 @@ commit — the gate already covers that.
18
18
  - **A transition check the core's loop skill defines** — the point where
19
19
  one phase or layer closes and the next opens. That skill names when it
20
20
  calls you and what it wants confirmed; read it rather than assuming a
21
- fixed boundary.
21
+ fixed boundary. Mechanically, that point is where a layer's
22
+ `verify_radius` is wider than its own `scope`, or the layer is
23
+ `exclusive: true` (`core.yaml`) — a join or integration point, where a
24
+ boundary violation would otherwise ship unreviewed. A layer whose
25
+ radius equals its scope needs no visit from you; the loop skill's own
26
+ phrasing of "where" is that fact stated in the core's own vocabulary.
22
27
  - **Correction Protocol**: when a downstream step reveals an upstream step
23
28
  was wrong. Review the patch and its fast-forwarded dependents together,
24
29
  as one unit.
@@ -59,29 +59,29 @@ style, a piece of behavior), the existing codebase, the commit log.
59
59
  discipline as the rest of the build (`fix(<scope>): <what>` or
60
60
  `style(<scope>): <what>`, whichever fits).
61
61
 
62
- A tweak is a small, targeted edit to something that already exists —
63
- not a new module, not a new phase, not scope growth. If a request turns
64
- out to be either of those, say so and route it onward a completed
65
- build is extendable, not sealed, and the user should not hear "no" where
66
- the answer is "that's a different session." Two destinations, and which
67
- one applies depends on whether the core has a module axis to hang new
68
- work on:
62
+ **Size every request with the `hedgehog-daily` skill.** That skill owns
63
+ the tweak / change-work / re-plan decision and its conditions, and it
64
+ reads them against the installed core's own `.hedgehog/core.yaml`run
65
+ it rather than judging the size here. A completed build is extendable,
66
+ not sealed, so a request above the tweak line gets routed, not refused.
69
67
 
70
- - **New scope on a module axis** routes to `planner`, which runs
71
- `hedgehog-planning-intake`'s **Re-entry pass**: it adds intents for the
72
- new work without re-running planning from scratch, and without
73
- disturbing anything already built.
74
- - **Everything else** routes to the **Correction Protocol's post-build
75
- entry**, in the core's own loop skill, which re-runs whichever phases
76
- the change reaches and rebuilds the artifact.
68
+ What each exit means for you:
77
69
 
78
- A core with no module axis has no intent for `planner` to add, so new
79
- work there is the second case, not the first — and the locked planning
80
- artifact that governs it (the brief, the design rationale, the layer
81
- sequence) is never rewritten to accommodate new scope. When that artifact
82
- genuinely no longer holds, the request is a different project and belongs
83
- in its own, not an edit to this one; say so rather than routing it. The
84
- core's own loop skill states which artifact governs and what the test is.
70
+ - **Tweak** make it here, per that skill's tweak exit and this file's
71
+ Workflow step 3.
72
+ - **Change-work** route it onward. On a module axis, that is `planner`
73
+ running `hedgehog-planning-intake`'s **Re-entry pass**, which adds
74
+ intents for the new work without re-running planning from scratch and
75
+ without disturbing anything already built. A core with no module axis
76
+ has no intent for `planner` to add, so it goes to the **Correction
77
+ Protocol's post-build entry** in the core's own loop skill instead,
78
+ which re-runs whichever phases the change reaches and rebuilds the
79
+ artifact.
80
+ - **Re-plan** — the locked planning artifact no longer holds. Route to
81
+ `planner`'s re-entry pass, or, where that artifact's failure means the
82
+ request is a different project rather than an extension of this one,
83
+ say so plainly instead of routing. Never rewrite that artifact to
84
+ accommodate new scope.
85
85
 
86
86
  ### Job 2 — Friction review, user feedback, and issue suggestion
87
87
 
@@ -211,11 +211,11 @@ discipline as `.hedgehog/BMAD/`. A later related incident is its own new
211
211
  drop it either way; a "no" or no response is not a prompt to explain
212
212
  further or ask again later in this session. If the user says yes,
213
213
  hand off to the `hedgehog-contributing` skill.
214
- 3. **Job 1, every run**: take the user's tweak request, read the actual
215
- code it touches (not a summary), make the change, verify it with the
216
- touched layer's own `verify` command from `.hedgehog/core.yaml`
217
- matching whatever the core's own loop skill already gates on — and
218
- commit it as its own small conventional commit.
214
+ 3. **Job 1, every run**: run the `hedgehog-daily` skill on the user's
215
+ request. On its tweak exit, make the change there read the actual
216
+ code it touches (not a summary), edit, verify with the touched layer's
217
+ own `verify` command from `.hedgehog/core.yaml`, commit as its own
218
+ small conventional commit. On either other exit, route as above.
219
219
  4. **Repeat step 3** for as many tweaks as the user has, one at a time —
220
220
  don't batch unrelated tweaks into one commit.
221
221
 
@@ -257,11 +257,11 @@ discipline as `.hedgehog/BMAD/`. A later related incident is its own new
257
257
  `reviewed:` row logged after every other row currently in the log?
258
258
  - Never edit or delete a prior row in the `friction` table — it's
259
259
  write-once per row, same as `.hedgehog/BMAD/`.
260
- - Don't expand a tweak into a rebuild. If a "tweak" actually requires
261
- redoing a phase (the artifact an upstream phase locked has to change,
262
- not just one line of what a later phase produced from it), that's the
263
- Correction Protocol say so and route it
264
- there rather than patching around it here. Use its **post-build entry**
260
+ - Don't expand a tweak into a rebuild. A request `hedgehog-daily` sizes
261
+ above the tweak line gets routed, never patched around here and a
262
+ tweak that turns out mid-edit to reach a second layer or need a file
263
+ that doesn't exist stops and re-enters that gate.
264
+ - When the route is the Correction Protocol, use its **post-build entry**
265
265
  (in this core's own loop skill): the build is already at its Stop
266
266
  Condition, so there's no task in flight to stop and no loop to resume,
267
267
  and the correction is fixed forward in new commits rather than by
package/src/db/core.mjs CHANGED
@@ -180,9 +180,16 @@ function indentOf(line) {
180
180
  return line.length - line.trimStart().length;
181
181
  }
182
182
 
183
+ // The only values `pattern` may declare — named in every rejection
184
+ // message below, so a typo surfaces the valid set instead of silently
185
+ // degrading to "unset" (which would turn conformance checking off with
186
+ // no signal that anything is wrong).
187
+ const VALID_PATTERNS = ['hexagonal', 'layered', 'vertical-slice', 'none'];
188
+
183
189
  // Parses the narrow subset of YAML a core definition needs:
184
190
  // id: <scalar>
185
191
  // pluralizes: <bool> # optional, default true
192
+ // pattern: <scalar> # optional, one of hexagonal|layered|vertical-slice|none
186
193
  // layers:
187
194
  // - id: <scalar>
188
195
  // depends_on: <scalar> # optional
@@ -202,7 +209,7 @@ export function parseCoreYaml(text) {
202
209
  lines.push({ indent: indentOf(noComment), text: noComment.trim() });
203
210
  }
204
211
 
205
- const core = { id: undefined, pluralizes: true, layers: [] };
212
+ const core = { id: undefined, pluralizes: true, pattern: null, layers: [] };
206
213
  let i = 0;
207
214
 
208
215
  while (i < lines.length && lines[i].indent === 0) {
@@ -224,6 +231,21 @@ export function parseCoreYaml(text) {
224
231
  // advisory stops firing on it for good, rather than every user of
225
232
  // that core re-discovering the same false positive.
226
233
  if (key === 'pluralizes') core.pluralizes = parseScalar(value) === 'true';
234
+ // An architecture claim, checked by validateCore below — see that
235
+ // function's pattern-conformance block for what each value asserts.
236
+ // Rejected here, at parse time, rather than left to validateCore:
237
+ // an unrecognized value must never silently resolve to "unset" (the
238
+ // one value that turns conformance checking off), so a typo has to
239
+ // surface as a parse error, not a quietly-skipped check.
240
+ if (key === 'pattern') {
241
+ const declared = parseScalar(value);
242
+ if (!VALID_PATTERNS.includes(declared)) {
243
+ throw new Error(
244
+ `unknown pattern "${declared}" — must be one of: ${VALID_PATTERNS.join(', ')}`,
245
+ );
246
+ }
247
+ core.pattern = declared;
248
+ }
227
249
  i++;
228
250
  }
229
251
 
@@ -498,6 +520,132 @@ export function isModuleAxis(core) {
498
520
  return core.layers.some((layer) => layer.scope.join('').includes('{module}'));
499
521
  }
500
522
 
523
+ // `layered`'s and `hexagonal`'s checks both anchor on "the head layer" —
524
+ // the first-declared layer, by the same convention `core.layers[0]`
525
+ // already carries informally everywhere else in this file (e.g. the
526
+ // once-layer checks below walk `core.layers` in declaration order too).
527
+ function headLayer(core) {
528
+ return core.layers[0];
529
+ }
530
+
531
+ // `pattern: layered` — a strict linear chain: every layer but the head
532
+ // depends on exactly one other, no two layers share a depends_on parent
533
+ // (that would be branching, not a chain), and every layer is reachable
534
+ // from the head by walking depends_on forward. Throws naming the first
535
+ // layer that breaks the shape, in the order the checks below run.
536
+ function checkLayeredPattern(core) {
537
+ const head = headLayer(core);
538
+ const rest = core.layers.filter((layer) => layer.id !== head.id);
539
+
540
+ for (const layer of rest) {
541
+ if (!layer.depends_on) {
542
+ throw new Error(
543
+ `core "${core.id}" declares pattern: layered, but layer "${layer.id}" has no depends_on — every layer but the head ("${head.id}") must depend on exactly one other layer`,
544
+ );
545
+ }
546
+ }
547
+
548
+ const dependents = new Map(); // parent layer id -> the one layer that depends on it
549
+ for (const layer of rest) {
550
+ const prior = dependents.get(layer.depends_on);
551
+ if (prior) {
552
+ throw new Error(
553
+ `core "${core.id}" declares pattern: layered, but both "${prior}" and "${layer.id}" depend on "${layer.depends_on}" — a layered chain is linear, one dependent per layer`,
554
+ );
555
+ }
556
+ dependents.set(layer.depends_on, layer.id);
557
+ }
558
+
559
+ // Walk forward from the head (parent -> its one dependent) and confirm
560
+ // every layer gets visited. This also catches a chain disconnected from
561
+ // the head entirely — e.g. two layers depending on each other with
562
+ // neither reachable from the head — which the checks above don't rule
563
+ // out on their own: each layer still has exactly one depends_on and no
564
+ // parent is shared, they just never connect back to "${head.id}".
565
+ const visited = new Set([head.id]);
566
+ let current = head;
567
+ while (dependents.has(current.id)) {
568
+ current = core.layers.find((layer) => layer.id === dependents.get(current.id));
569
+ visited.add(current.id);
570
+ }
571
+ for (const layer of core.layers) {
572
+ if (!visited.has(layer.id)) {
573
+ throw new Error(
574
+ `core "${core.id}" declares pattern: layered, but layer "${layer.id}" is not reachable from the head layer "${head.id}" by following depends_on`,
575
+ );
576
+ }
577
+ }
578
+ }
579
+
580
+ // `pattern: hexagonal` — Hedgehog has no adapter marker today, so this
581
+ // checks direction alone rather than an actual domain/adapter boundary:
582
+ // the head layer (the domain, by convention) must have no depends_on, and
583
+ // every other layer's depends_on chain must terminate at the head with no
584
+ // cycle — i.e. dependencies all point one way, inward, and the head is the
585
+ // sink every chain ends at. Weaker than the real hexagonal rule (nothing
586
+ // stops an adapter depending on another adapter instead of the domain
587
+ // directly), and deliberately so — see #314's "Not in this issue" for why
588
+ // a real adapter-boundary marker is a separate design decision.
589
+ function checkHexagonalPattern(core) {
590
+ const head = headLayer(core);
591
+ if (head.depends_on) {
592
+ throw new Error(
593
+ `core "${core.id}" declares pattern: hexagonal, but its head layer "${head.id}" has a depends_on — the domain layer must be the sink every dependency chain points to, not itself a dependent`,
594
+ );
595
+ }
596
+
597
+ const byId = new Map(core.layers.map((layer) => [layer.id, layer]));
598
+ for (const layer of core.layers) {
599
+ if (layer.id === head.id) continue;
600
+ if (!layer.depends_on) {
601
+ throw new Error(
602
+ `core "${core.id}" declares pattern: hexagonal, but layer "${layer.id}" has no depends_on — only the domain layer ("${head.id}") may have none`,
603
+ );
604
+ }
605
+ const seen = new Set([layer.id]);
606
+ let current = layer;
607
+ while (current.depends_on) {
608
+ const next = byId.get(current.depends_on);
609
+ if (seen.has(next.id)) {
610
+ throw new Error(
611
+ `core "${core.id}" declares pattern: hexagonal, but layer "${layer.id}"'s depends_on chain cycles back through "${next.id}" instead of terminating at the domain layer "${head.id}"`,
612
+ );
613
+ }
614
+ seen.add(next.id);
615
+ current = next;
616
+ }
617
+ if (current.id !== head.id) {
618
+ throw new Error(
619
+ `core "${core.id}" declares pattern: hexagonal, but layer "${layer.id}"'s depends_on chain terminates at "${current.id}", not the domain layer "${head.id}" — every layer must point inward toward the domain`,
620
+ );
621
+ }
622
+ }
623
+ }
624
+
625
+ // Dispatches on `core.pattern` to the check above matching what was
626
+ // declared. `null` (never set) and `'none'` (set, explicitly no enforced
627
+ // direction — the adopted-repo default) both skip checking entirely: an
628
+ // absent pattern must validate exactly as it did before this field
629
+ // existed, and `none` recording "no direction" is a fact, not a finding.
630
+ function checkPatternConformance(core) {
631
+ if (!core.pattern || core.pattern === 'none') return;
632
+ if (core.pattern === 'vertical-slice') {
633
+ if (!isModuleAxis(core)) {
634
+ throw new Error(
635
+ `core "${core.id}" declares pattern: vertical-slice, but no layer's scope contains {module} — vertical-slice is a chain instantiated per module, so at least one layer must vary by module`,
636
+ );
637
+ }
638
+ return;
639
+ }
640
+ if (core.pattern === 'layered') {
641
+ checkLayeredPattern(core);
642
+ return;
643
+ }
644
+ if (core.pattern === 'hexagonal') {
645
+ checkHexagonalPattern(core);
646
+ }
647
+ }
648
+
501
649
  // Enforces the interview's rule (spec: "Authored cores") — a layer without
502
650
  // scope or without a verify command is rejected. Applied uniformly to
503
651
  // shipped and authored cores alike; the loader has no shipped-core-only
@@ -575,6 +723,13 @@ export function validateCore(core) {
575
723
  }
576
724
  }
577
725
 
726
+ // An architecture claim, checked mechanically — an unchecked `pattern`
727
+ // is a comment, and a comment that can silently disagree with the graph
728
+ // is worse than no field at all. Depends on depends_on already being
729
+ // resolved to real layer ids (the loop just above), which every check
730
+ // below relies on.
731
+ checkPatternConformance(core);
732
+
578
733
  // A `once: true` layer compiles a single task for the whole build, so
579
734
  // there is no module to substitute into its templates. Left unchecked,
580
735
  // a stray {module} would survive verbatim into scope_globs — a glob
package/src/db/status.mjs CHANGED
@@ -55,6 +55,27 @@ function loadInFlightTasks(db) {
55
55
  return db.prepare(IN_FLIGHT_TASKS_SQL).all();
56
56
  }
57
57
 
58
+ // The in-flight list on its own, without the rest of graphStatus. The
59
+ // full report costs a drift comparison against core.yaml, a readiness
60
+ // simulation, an override scan, and two side-channel reads — everything
61
+ // `hedgehog status` prints. `hedgehog-daily` asks only "is a build in
62
+ // flight" and asks it before every change request, so it must not pay
63
+ // for a report it discards. Same query and same reaping contract as the
64
+ // full path; only the other sections are skipped.
65
+ export function inFlightTasks(db) {
66
+ return loadInFlightTasks(db);
67
+ }
68
+
69
+ // One line for `hedgehog status --brief`: what is in flight, or that
70
+ // nothing is. Deliberately not a subset of formatStatus's sections — a
71
+ // caller reading this wants a verdict, and the full report stays the
72
+ // right thing to read once the verdict is "something is".
73
+ export function formatBrief(inFlight) {
74
+ if (inFlight.length === 0) return 'IN FLIGHT 0 — nothing building or verifying';
75
+ const ids = inFlight.map((task) => task.id).join(', ');
76
+ return `IN FLIGHT ${inFlight.length} — ${ids}`;
77
+ }
78
+
58
79
  function countTasksByStatus(db) {
59
80
  const rows = db
60
81
  .prepare('SELECT status, COUNT(*) AS n FROM tasks GROUP BY status')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgehog",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
4
4
  "description": "Hedgehog build discipline: ordered, tested, verified build steps.",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: filing-issues
3
+ description: Use when filing one or more GitHub issues for planned work — "file an issue for this", "turn this plan into issues", "open a tracking issue". Covers filing everything on skyf0xx/hedgehog regardless of which repo's code changes, whether to split a plan into a tracking issue plus sub-issues, which account files the issue, labels, and acceptance criteria. For how to word an issue, see the pr-writing skill.
4
+ ---
5
+
6
+ # Filing issues
7
+
8
+ The mechanics of getting planned work into GitHub. `pr-writing` owns how
9
+ an issue is worded — title shape, the Why/What structure, `<details>`
10
+ folding, and style. This skill owns everything before and around that.
11
+
12
+ ## Which repo
13
+
14
+ Every issue is filed on `skyf0xx/hedgehog`, never on a core's own repo
15
+ (e.g. the one shipping `full-stack-app` or `pwa-app`) — one issue queue
16
+ to track and review, regardless of which repo's code changes.
17
+
18
+ When the fix actually lands in a core repo, open the issue body with a
19
+ line naming that repo and linking it, so a contributor knows where the
20
+ PR belongs even though the issue itself doesn't live there.
21
+
22
+ ## One issue or several
23
+
24
+ Split when the parts have different reviewers, different repos, different
25
+ risk, or can be worked in parallel. Keep one issue when the work is a
26
+ single reviewable change, even a large one.
27
+
28
+ A split gets a tracking issue plus sub-issues:
29
+
30
+ - The tracking issue carries the Why, the scope boundary, and a numbered
31
+ list of sub-issues with a one-line description of each.
32
+ - Each sub-issue is self-contained — someone picking it up should not
33
+ have to read the tracker to know what to do.
34
+ - State the dependency edges explicitly, including their absence:
35
+ which can be picked up now, which blocks which, and which merely
36
+ prefer an order without blocking.
37
+ - File the tracker first so sub-issues can reference its number, then
38
+ patch the tracker with the real numbers once they exist.
39
+ - Comment `Part of #<tracker>` on each sub-issue.
40
+
41
+ ## Which account files it
42
+
43
+ Issues go out as the user's own `gh` session by default.
44
+
45
+ Some projects have a bot identity for maintainer actions. Use it only
46
+ when the user asks for it by name. A bot voice tuned for short notes
47
+ does not apply to a planned-work issue — write the issue at full length
48
+ and say that is what you are doing.
49
+
50
+ ## Labels
51
+
52
+ Read the target repo's labels before filing (`gh label list --repo
53
+ <owner/repo>`) — the engine repo and the core repos carry overlapping but
54
+ not identical sets.
55
+
56
+ Apply what is verifiable at filing time: a type label (`feature`, `bug`,
57
+ `documentation`) and any `risk:` label the change clearly earns.
58
+ `good-first-issue` fits an issue that is genuinely self-contained with an
59
+ obvious done state. Leave `size:` labels alone — they describe a diff
60
+ that does not exist yet.
61
+
62
+ ## Acceptance criteria
63
+
64
+ End every issue with a checkbox list a contributor ticks off and a
65
+ reviewer checks against. Each line is one observable outcome, not a
66
+ restatement of the task list.
67
+
68
+ - Testable by inspection or a command, not by judgment. "`pnpm nx test
69
+ mobile -- src/{module}/` passes on generated output" — not "the
70
+ generator works well."
71
+ - Include the things that must *not* change: the existing behavior that
72
+ still has to pass, the version that must not be bumped.
73
+ - Cover the whole change. A criterion nobody can check is noise; a
74
+ missing one is a gap a reviewer has to find themselves.
75
+
76
+ On a tracking issue, the criteria are the sub-issues plus the end-to-end
77
+ outcome that proves the whole set landed.
78
+
79
+ ## Before filing
80
+
81
+ Verify every claim the issue makes about existing behavior, and cite it
82
+ `file:line`. An issue is public and durable — a wrong claim in one sends
83
+ a contributor down a path that does not exist.
84
+
85
+ Confirm the split and the identity with the user before creating
86
+ anything — the repo is fixed, not a decision to make each time. Issue
87
+ creation is public and hard to reverse.
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: hedgehog-daily
3
+ description: Use when a change request lands on a project that already has `.hedgehog/` and no build in flight — a finished build being adjusted, or an adopted repo's next piece of work. Triggers on any "change this", "fix this", "add this" on such a project. Sizes the request against the installed core's own layers and routes it to one of three exits: a tweak made and committed here, change-work through `hedgehog intent add` and the core's loop, or a re-plan. Not for a build still in progress — that is the core's loop skill's own job.
4
+ ---
5
+
6
+ # Daily change-work
7
+
8
+ One gate, three exits, for every change request on a project whose build
9
+ graph already exists. It reads the installed core's layer sequence,
10
+ scope globs and verify commands out of `.hedgehog/core.yaml`, so it is
11
+ the same gate on every core.
12
+
13
+ The gate exists to stop pricing a two-line edit at the cost of the
14
+ largest change the discipline can handle. Routing up is a real decision
15
+ with a real cost, taken on stated conditions — not the safe default.
16
+
17
+ ## Entry
18
+
19
+ 1. **`.hedgehog/` exists.** Without it there is no core to read and no
20
+ graph to add to; this skill does not apply.
21
+ 2. **Nothing is in flight.** `hedgehog status --brief` — one line. If it
22
+ names any task, a build is mid-flight: this gate does not run. Read
23
+ the full `hedgehog status` and hand the request back to the core's own
24
+ loop skill, which owns work in progress.
25
+ 3. **Read `.hedgehog/core.yaml`.** The layer list is the input to every
26
+ decision below: each layer's `id`, `scope` globs, `verify` command,
27
+ `verify_radius` and `exclusive`. Read the file, not a memory of it.
28
+
29
+ ## The three exits
30
+
31
+ Decide by the conditions, in order. The first one that holds is the exit.
32
+
33
+ ### Re-plan
34
+
35
+ The locked planning artifact that governs this project no longer
36
+ describes what is being asked for. The core's own loop skill names which
37
+ artifact governs — the brief and layer sequence for a shipped core,
38
+ `.hedgehog/core-design.md` for an authored one, `.hedgehog/adoption.md`
39
+ for an adopted one.
40
+
41
+ Route to `planner`'s re-entry pass, which adds intents for new work
42
+ without re-running planning from scratch and without disturbing anything
43
+ already built.
44
+
45
+ Where the artifact's failure means the request is a different project
46
+ rather than an extension of this one, say so plainly instead of routing.
47
+ That artifact is never rewritten to accommodate new scope.
48
+
49
+ ### Change-work
50
+
51
+ Either condition puts the request here:
52
+
53
+ - It reaches more than one of the core's layers.
54
+ - It introduces a file, module, or capability that does not exist yet.
55
+
56
+ `hedgehog intent add`, then the installed core's own loop, unchanged.
57
+ Nothing about that path changes because this gate ran.
58
+
59
+ ### Tweak
60
+
61
+ Both conditions hold:
62
+
63
+ - Every file it touches is inside one layer's `scope` globs — one
64
+ layer, not two.
65
+ - Every file it touches already exists.
66
+
67
+ Then, in this session, with no subagent dispatched:
68
+
69
+ 1. Read the code it touches. Not a summary of it.
70
+ 2. Make the smallest correct edit.
71
+ 3. Run that layer's own `verify` command from `core.yaml`, at the depth
72
+ the next section states.
73
+ 4. Commit as one conventional commit, in the format the
74
+ `conventional-commits` skill states.
75
+
76
+ No `hedgehog intent add`, no `hedgehog plan`, no `hedgehog claim`, no
77
+ subagent. Nothing is written to the build graph.
78
+
79
+ ### Tweak is the default under ambiguity
80
+
81
+ When the conditions do not clearly place a request above the tweak line,
82
+ it takes the tweak exit. A gate that escalates when unsure prices every
83
+ change at its worst case, which is the failure this gate exists to
84
+ avoid.
85
+
86
+ An escalation the tweak reveals is cheap: a tweak that turns out to
87
+ touch a second layer or need a file that does not exist stops there and
88
+ re-enters this gate at the change-work exit, having cost one read.
89
+
90
+ ## Test and review depth on the tweak exit
91
+
92
+ A tweak inherits the test and review bar of the layer it lands in.
93
+
94
+ - A layer whose `verify_radius` equals its `scope`: run the layer's
95
+ `verify` command. No new tests, no `reviewer` pass.
96
+ - A layer with a wider `verify_radius`, or `exclusive: true`: the same
97
+ real test bar and `reviewer` pass that layer gets in the loop.
98
+
99
+ That is the loop's own rule — "Test depth follows verify radius. Review
100
+ follows exclusivity", stated in full in the core's loop skill — applied
101
+ to a layer instead of a compiled task. `verify_radius` and `exclusive`
102
+ are declared on the layer in `core.yaml`, so both are readable on a path
103
+ that compiles no task.
104
+
105
+ **A tweak landing in a wide-radius or exclusive layer is a signal.**
106
+ Integration layers are where behavior gets proven, so a change reaching
107
+ one is rarely as small as it looked when it was asked for. Re-check that
108
+ the tweak exit was the right exit. Do not bolt the loop's ceremony onto
109
+ the tweak path instead.
110
+
111
+ **The floor does not move.** A tweak to code with no tests does not get
112
+ to leave it that way where the layer's own bar says otherwise.
113
+
114
+ ## Hard rules
115
+
116
+ - Never take the tweak exit on a file that does not exist yet. A new
117
+ file is change-work by condition, whatever its size.
118
+ - Never widen a layer's `scope` to make a change fit the tweak exit.
119
+ A change that needs a wider scope is change-work.
120
+ - Never commit a tweak whose layer `verify` command fails. A failing
121
+ gate means the change is not done.
122
+ - Never batch two unrelated tweaks into one commit.
123
+ - Never rewrite the locked planning artifact to accommodate new scope —
124
+ that is the re-plan exit's decision, and its answer may be that this
125
+ is a different project.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pr-writing
3
- description: Use whenever writing a PR title/description, a commit message body, a code review comment, or an issue — in Hedgehog's own repo or any consuming project. Triggers on "open a PR", "write the PR description", "comment on this PR", "file an issue". Covers writing style (terse, info-dense, Simplified Technical English) and the pre-open checklist (CI status, scope, verified claims only).
3
+ description: Use whenever writing a PR title/description, a commit message body, a code review comment, or an issue — in Hedgehog's own repo or any consuming project. Triggers on "open a PR", "write the PR description", "comment on this PR", "file an issue". Covers writing style (terse, info-dense, Simplified Technical English), the Why/What shape, folding deep reasoning under `<details>`, and the pre-open checklist (CI status, scope, verified claims only).
4
4
  ---
5
5
 
6
6
  # PR Writing
@@ -47,13 +47,52 @@ as a record of the work session.
47
47
 
48
48
  ## Shape
49
49
 
50
- - **Title**: `<type>(<scope>): <summary>`, imperative mood, under ~70
50
+ - **PR title**: `<type>(<scope>): <summary>`, imperative mood, under ~70
51
51
  chars.
52
- - **Description**: 1-3 bullets — what changed, why. A test plan section
52
+ - **PR description**: 1-3 bullets — what changed, why. A test plan section
53
53
  listing what you actually ran, not what should theoretically pass.
54
+ - **Issue title**: plain English a non-technical reader would say out
55
+ loud, not a commit-style `<type>(<scope>): <summary>`. Name the
56
+ outcome, not the mechanism — "Improve how Hedgehog tracks and enforces
57
+ a project's architecture", not "feat(core): add pattern field".
54
58
  - **Comments**: lead with the concrete finding, then (if needed) the fix
55
59
  requested. No preamble.
56
60
 
61
+ ## Why/What for issues
62
+
63
+ An issue proposing a change — a feature, a fix worth explaining, a
64
+ `ROADMAP.md` item being picked up — states **Why** before **What**:
65
+
66
+ - **Why**: the problem, as a short list of plain-language facts. Each
67
+ bullet is one observation a reader can verify or disagree with, not a
68
+ justification wrapped in caveats. State the problem first, then (if
69
+ the fix isn't obvious from the problem) a short "to fix this" list of
70
+ intended outcomes.
71
+ - **What**: the change itself — the concrete steps, fields, or sub-issues.
72
+ Numbered if sequenced, bulleted if not.
73
+
74
+ Skip the Why section only when the title already states the problem in
75
+ full (a one-line bug report needs no restatement). Never skip What.
76
+
77
+ ## Fold deep reasoning under `<details>`
78
+
79
+ An issue or PR body written for a human reader stays short. Extended
80
+ reasoning — architecture rationale, alternatives considered, prior
81
+ decisions, anything aimed at an AI agent picking up the work or a reader
82
+ who wants the full trail — goes under a collapsed section, not inline:
83
+
84
+ ```markdown
85
+ <details>
86
+ <summary>Full reasoning (for AI agents and anyone who wants the detail)</summary>
87
+
88
+ ...
89
+ </details>
90
+ ```
91
+
92
+ Ask first whether that detail needs to exist in the issue at all — a
93
+ link to an existing doc or prior discussion is often enough. Only fold
94
+ in content that has no better home.
95
+
57
96
  ## When NOT to apply
58
97
 
59
98
  - Internal scratch notes, planning docs, or anything not read by another