heyspex 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/bin/hs.mjs +85 -25
  2. package/bin/spexcode.mjs +31 -0
  3. package/dev-factory/lib/local-firecrawl.mjs +294 -0
  4. package/dev-factory/lib/seat-context.mjs +20 -4
  5. package/dev-factory/model-catalog.json +222 -222
  6. package/dev-factory/spexcrawl/firecrawl-cli.mjs +755 -0
  7. package/dist/cli/hs-connect.mjs +20 -2
  8. package/dist/cli/hs-defer.mjs +19 -1
  9. package/dist/cli/hs-index-rebuild.mjs +19 -1
  10. package/dist/cli/hs-index-reconcile.mjs +19 -1
  11. package/dist/cli/hs-init.mjs +2 -2
  12. package/dist/cli/hs-install.mjs +2 -2
  13. package/dist/cli/hs-join.mjs +20 -2
  14. package/dist/cli/hs-next-launch.mjs +89 -10
  15. package/dist/cli/hs-spawn.mjs +27 -1
  16. package/dist/cli/hs-vault-pull.mjs +19 -1
  17. package/dist/cli/hs-vault-push.mjs +19 -1
  18. package/dist/cli/hs-vault.mjs +21 -7
  19. package/dist/cli/hs-workspace.mjs +19 -1
  20. package/dist/cli-templates/customer-vault/specs/entity-model.md +1728 -1728
  21. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +464 -464
  22. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -122
  23. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +46 -46
  24. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +530 -530
  25. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -83
  26. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
  27. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
  28. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
  29. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -30
  30. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
  31. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
  32. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
  33. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
  34. package/dist/cli-templates/harness-commands/claude/hs-res.md +20 -8
  35. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
  36. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -28
  37. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
  38. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
  39. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
  40. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
  41. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
  42. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -24
  43. package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
  44. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
  45. package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
  46. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
  47. package/dist/cli-templates/harness-commands/cursor/hs-res.md +20 -8
  48. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
  49. package/dist/cli-templates/harness-commands/cursor/hs-view.md +23 -23
  50. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
  51. package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
  52. package/docs/copilot-plugin-install.md +7 -0
  53. package/docs/mcp-client-config.md +6 -0
  54. package/package.json +7 -2
  55. package/plugins/heyspex/README.md +6 -0
  56. package/plugins/heyspex/bin/launch-mcp.mjs +17 -0
  57. package/plugins/heyspex/hooks/hooks.json +418 -418
  58. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +2 -2
  59. package/scripts/hs-companion.mjs +38 -8
  60. package/scripts/lib/companion-install.mjs +768 -0
  61. package/scripts/lib/customer-kit.mjs +5 -3
  62. package/scripts/lib/hs-companion-settings.mjs +1 -0
  63. package/scripts/lib/hs-source-push.mjs +386 -386
  64. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -29
  65. package/spexharness/src/adapters/copilot-models.mjs +179 -0
  66. package/spexharness/src/adapters/harness-detect.mjs +148 -0
  67. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -132
  68. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +153 -125
  69. package/spexharness/src/seat-context.mjs +10 -5
  70. package/spexharness/src/session-bootstrap.mjs +6 -3
  71. package/vendor/heyspex-mcp/cli.mjs +6 -1
  72. package/vendor/heyspex-mcp/config-seed.yaml +15 -15
  73. package/vendor/heyspex-mcp/dev-factory-overlay.mjs +142 -0
  74. package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
  75. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
  76. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -327
  77. package/vendor/heyspex-mcp/procedure-tools.mjs +1209 -1181
  78. package/vendor/heyspex-mcp/research-tools.mjs +22 -2
  79. package/vendor/heyspex-mcp/seat-assign-cli.mjs +242 -0
  80. package/vendor/heyspex-mcp/seat-assignment-rules.mjs +96 -0
  81. package/vendor/heyspex-mcp/seat-assignments-client.mjs +265 -0
  82. package/vendor/heyspex-mcp/seat-defaults-cli.mjs +45 -0
  83. package/vendor/heyspex-mcp/seat-defaults.mjs +232 -0
  84. package/vendor/heyspex-mcp/seat-identity.mjs +171 -0
  85. package/vendor/heyspex-mcp/vault-io.mjs +720 -720
@@ -1,530 +1,530 @@
1
- ---
2
- id: HS-PROTO-spec-hierarchy
3
- name: Spec hierarchy protocol — Milestone → Epic → Task → child tasks
4
- type: protocol
5
- status: draft
6
- priority: 0
7
- scope: vault
8
- owner: TODO — your name/handle
9
- created: '2026-05-27'
10
- updated: '2026-07-04'
11
- tags:
12
- - vault
13
- - specs
14
- - hierarchy
15
- - milestone
16
- - epic
17
- - task
18
- - agent-autonomy
19
- - foundational
20
- triggers:
21
- - file_milestone
22
- - file_epic
23
- - file_task
24
- - protocol_edit
25
- related:
26
- - "[[entity-model]]"
27
- - "[[session-close-protocol]]"
28
- - "[[memory-protocol]]"
29
- summary: |
30
- Governs the Milestone to Epic to Task hierarchy: filing rules, how acceptance criteria propagate and freeze, the Expected Breakage section, sequencing language constraints, and the change-log review seed close-out steps.
31
-
32
- ---
33
-
34
- # Spec hierarchy protocol — Milestone → Epic → Task → child tasks
35
-
36
- The **canonical hierarchy** of trackable work in HeySpex. Every piece of trackable work belongs at exactly one level of this hierarchy. Agents filing new work use this protocol to decide *which level* a new spec belongs at, *what frontmatter* it needs, and *how much autonomy* they have at each level.
37
-
38
- This protocol exists because the hierarchy was previously implicit — codified in example specs (`HS-HMNJ-MIL`, `HS-84HT-EPIC`) and partially in `entity-model.md`, but never written as a single authoritative procedure an agent can read. Pattern-matching off example files leads to drift; this doc is what agents read **before** filing a milestone, epic, or task.
39
-
40
- The Milestone entity type itself is **in-flight**: HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing (under HS-RM22-IART-MIL-console-ia-restructure) is formalizing the type/folder/validator. Until MILE lands, `type: milestone` and `HS-MIL-*` are recognized by the vault walker but not enforced by validators. The required-parent rule on Epics lands in HS-OFR5-M2J3-EPIC-introduce-milestones-as-highest-entity within HS-1JC8-HMNJ-MIL-status-and-roadmap-surface-refactor with a grace window. This protocol describes the **target** state.
41
-
42
- ## The four levels
43
-
44
- | Level | Folder | ID format | What it is | Status today |
45
- |---|---|---|---|---|
46
- | **Milestone** | `specs/milestones/` | `HS-MIL-<suffix>` | A coherent multi-epic ship. Closes when every child epic closes and the milestone's own acceptance gate is met. | Entity exists (4 filed); type formalization in HS-VK7C |
47
- | **Epic** | `specs/epics/` | `HS-EPIC-<suffix>` | A bounded body of work, typically 1–10 tasks. Has one parent milestone (required after HS-OFR5) and N children tasks. | Stable |
48
- | **Task** | `specs/tasks/` | `HS-TASK-<suffix>` | An atomic unit of agent-executable work. Has one parent epic. May or may not have child tasks (agent's call — see §Agent autonomy). | Stable |
49
- | **Child task** | `specs/tasks/` | `HS-TASK-<suffix>` | A task with `parent:` pointing at another task (not an epic). Used when an agent fans out a task during execution. Same shape as a task; differentiated only by parent. | Stable |
50
-
51
- Suffix format for all four: 4+ uppercase alphanumeric per vault-id-assignment-protocol. Legacy zero-padded epic IDs (`HS-0029-EPIC`) and 4-character mnemonic milestone IDs (`HS-HMNJ-MIL`) both remain valid.
52
-
53
- **Shared-suffix families:** when a task is filed under an epic, it inherits the epic's 4-char suffix (e.g. epic `HS-D5YF-EPIC-…` → tasks `HS-TASK-D5YF-<slug>.md`). The slug keeps each filename unique; a bare `HS-TASK-<suffix>` alone is a **family token**, not a unique handle — wikilinks must use the full basename. Retroactive re-suffixing of existing tasks is out of scope. Full rules: vault-id-assignment-protocol §Shared-suffix families; decision: bare-hs-task-suffix-is-a-family-token-af.
54
-
55
- ## What each level is *for*
56
-
57
- | If the work… | …file it as |
58
- |---|---|
59
- | Spans multiple epics, has its own acceptance gate, and represents a coherent ship | Milestone |
60
- | Is a single bounded ship — one outcome, 1–10 tasks, one DoD | Epic |
61
- | Is one atomic unit of agent execution (under one context window, usually) | Task |
62
- | Is sub-work discovered *during* a task that wasn't in the original task's scope but must complete before the parent task can close | Child task |
63
- | Is a **user-reported bug fix** — executable, but not planned roadmap work that belongs under an epic | Diagnose (see §Diagnose below) |
64
-
65
- **Anti-patterns** — these are not separate levels and should not be filed as such:
66
- - A "phase" or "sprint" inside an epic → those are sections in the epic body or sub-tasks, not a new level.
67
- - A "follow-up" that's discovered after a task closes → file as a new task under the same epic (not a child of the closed task).
68
- - A "research" finding that needs implementation → research lives in `specs/research/`; the implementation work is filed as task(s) under an epic, with `discovered_from: [[<research>]]` linking back.
69
-
70
- ### Diagnose — the bug-fix lane (a sibling executable entity, outside the milestone→epic→task chain)
71
-
72
- A **`diagnose`** (`specs/diagnoses/`, `HS-DIAG-<suffix>`) is a user-reported bug fix as first-class executable work. It is **not** a fifth level of the planning hierarchy — it sits *beside* it:
73
-
74
- - **Standalone by default.** A diagnose needs **no** parent. It is explicitly **exempt** from the epic→milestone mandatory-parent rule. An optional `parent:` (an `HS-EPIC-*` **or** `HS-MIL-*`) groups related fixes when more organization helps — but absence is the normal, valid state (the same "top-level" allowance [[entity-model]] grants tasks, made intentional and typed).
75
- - **Claimable like a task — while it is a ONE-FIX bug.** A one-fix diagnose is its own `ClaimTarget` (level `"task"`-equivalent — a leaf), and that is the common path. It is not the only one: a write-up whose own `## Fix` section lists **several separately-verifiable fixes** goes through the **breakdown stage** instead (`heyspex_promote_diagnose` — the same stage `heyspex_promote_research` uses, taking a bug write-up as its second input kind, HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose). Grounding runs first (what open milestone/epic already covers this area?), then placement descends: a matching epic gets **tasks**, a matching milestone with no fitting epic gets **one new epic**, and only when neither exists is a milestone minted — that one asks the owner first. Every epic spawned this way carries a milestone that resolves on disk, with no exemption.
76
- - **It LINKS to what it spawns; it never parents it.** The write-up gains `spawned_epics:` / `spawned_tasks:` and every spawned spec gains `derived_from:` pointing back — the same shape a research doc uses. An epic parented to a bug report would not render on the roadmap at all, so the `parent:` above keeps its own separate job of grouping several small bugs under one epic and the breakdown never touches it. Once a write-up has spawned work it leaves the on-deck queue and its Resolution row stops offering a claim, so one agent cannot claim the write-up while another works its tasks.
77
- - **It closes on its reproduction, not on its children.** A broken-down write-up reaches `closed` only after its `## Reproduction` is re-run and no longer reproduces (`heyspex_verify_reproduction`); a `closed` flip without that verdict is refused, for a human as much as for an agent. The write-up is the record of the investigation, and the investigation is over when the bug is gone.
78
- - **Surfaces on Resolution, advises the scheduler.** An open diagnose appears on the Resolution surface as a derived `kind: "bug"` row (the row is derived; the diagnose spec is the work). It advises the scheduler via `severity:` (a candidate competing for on-deck — channel B) and optional `blocks:` edges (gating a named epic — channel A), never via a rival queue (resolution-advises-scheduler-via-severity-and-gating). Full design: HS-AO2F-1KZK-RES-bug-fix-resolution-task-entity-model-and, milestone HS-OXBS.
79
- - **Required body sections:** Symptom, Reproduction, Root cause, Fix, Acceptance criteria.
80
-
81
- **Single-intent test — the deliverable, not the surface count.** When deciding whether work belongs at one level or splits horizontally, the "intent" for SRP-for-plans is the **deliverable** (the milestone's acceptance gate / the epic's DoD / the task's PR), **not** the surface count. A migration epic with N file-tasks is single-intent — the deliverable is "the migration is done." A bundled "platform refresh" milestone whose acceptance gate names three independently-acceptable ships is multi-intent — split it horizontally per §"Milestone sizing — epic cap" below. This is the plan-side counterpart of the doc-side rule in doc-canonicalization-protocol (per evergreen-doc-architecture-srp-and-seo-pillars); both protocols share one principle, applied at different surfaces.
82
-
83
- **Closed specs are historical record — frozen + immutable.** Trackable plan-side specs (epics / tasks / milestones / reviews) are *living documents* while their status is `open` / `in_progress` / `in_review`. The moment they close (`status: closed`), the body is **frozen**: no further edits to scope, acceptance criteria, narrative, or `related:` links. The closed spec captures the plan exactly as it was at the moment of close — and that's its job from then on. The actual delivered behavior is the git history of the shipped change; the closed spec is the *plan that closed*, preserved as historical record. Full rule + the four implicit references it generalizes + what you CAN still touch on a closed spec (the close-out change-log row; `status:`; `close_reason:`; `superseded_by:`) + where new information goes instead (new task / new memory / supersession): closed-trackable-specs-are-historical-record-not-living-docs. The Shipped console surface that displays the archive: HS-ZUVY-S1A6-EPIC-shipped-surface-closure-event-archive-wi. The storage convention (archive = filter not folder; closed specs stay in their normal folders): [[entity-model]] §close_reason.
84
-
85
- ### Sequencing language — disambiguate "after X"
86
-
87
- When a task body or scope item references another spec as a precondition for some piece of work ("land after X," "after X closes," "this happens after X"), the wording must name *which signal* the agent should wait for. Pick exactly one:
88
-
89
- - **"after X's code lands"** — wait until X's implementation is in the working tree, regardless of X's status. Use when the dependent work describes X's final code state (typically: comment rewrites, doc updates, edits that reference X's predicate signature / env var / new column).
90
- - **"after X closes"** — wait until X's `status: closed` (the reviewer's commit). Use **only** when the trigger is genuinely the close-out event itself — e.g., audit-only timing, archive sweeps, "do this after the reviewer signs off because the next step depends on the sign-off."
91
- - **Specific behavioral marker** — e.g., "after `isXyz()` returns Y," "after migration 0010 is applied," "after the cron has run once." Use when the trigger is concrete and observable from the code or operational state.
92
-
93
- The bare **"after X"** is banned: in MVP mode where the reviewer is the owner and close-out is a formality, agents have read it as both "code lands" (and updated the dependent comments in the same session) and "status flips" (and over-deferred to a future session that wasn't needed). The two readings are not equivalent; the spec author must pick one.
94
-
95
- **Reading legacy specs that use bare "after X":** default to "after X's code lands" — it's the cheaper read in MVP mode and matches the spec author's typical intent (they wanted the dependent work to describe X's final semantics, not to wait on the reviewer's clock). Treat the close-out reading as the exception, required only when the surrounding context makes the close-out signal explicit.
96
-
97
- This rule generalizes the feedback_mvp_mode_after_x_closes_means_after_code_lands incident pattern. The matching enforcement at session close (the **Deferred items audit** subsection in the HS-REV template) catches over-deferral that slipped past this rule — see [[session-close-protocol]] §HS-REV companion template §`## Reviewer notes` §`### Deferred items audit`.
98
-
99
- ### Milestone sizing — epic cap
100
-
101
- A milestone groups epics into a single coherent ship. **A milestone holds at most 9 epics (hard cap).** Exceeding 9 requires the owner's explicit permission — there is no soft/at-cap band; 9 is the line.
102
-
103
- - **Hard cap: 9 epics.** At or below 9 is fine. To add a 10th, either file a horizontal split (peer milestones at the same level — sub-milestones are explicitly out of scope, see §Open questions / in-flight below) or obtain the owner-override.
104
- - **Owner-override exception.** A milestone with a single product thesis and shared infrastructure that genuinely cannot be cleaved without breaking the gate may exceed 9 with the owner's explicit permission. The exception is documented inline in the milestone body as a `## Exception — over-cap rationale` section naming the shared thesis, the shared infrastructure, and what would be lost on splitting. The owner's permission is recorded in the milestone's change log row.
105
-
106
- **Cap rationale.** The primary reason is the console Milestone-card UI: epics render as a **3×3 grid (three rows of three)**, so 9 fills the card exactly and reads cleaner than 8 or 10. Two reinforcing reasons: the deterministic scheduler scopes its priority pass to the active milestone's epics (HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler), and the card's chip budget overflows beyond a full grid. Capacity lost by capping at 9 rather than 10 is one epic — negligible against a clean grid. Basis: HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t.
107
-
108
- **Filing flow when a new epic would push a milestone over cap:**
109
-
110
- 1. Check whether the new epic belongs to this milestone at all — often it's a separate concern that should land under a different (or new) milestone.
111
- 2. If yes, check whether the milestone qualifies for the owner-override exception (single thesis + shared infra that splits would break). If unsure, surface to the owner.
112
- 3. Otherwise, propose a horizontal split — daughter milestones at the same level. The parent flips to `status: parked` with `parked_reason: split-into-peers` and gains a `Split into [[daughter-A]] + [[daughter-B]] on YYYY-MM-DD` header in its §Status section.
113
-
114
- ### Research → milestone/epic filing — default to a new milestone (mandatory)
115
-
116
- When filing a milestone/epic set from a completed research doc (the `heyspex_promote_research` path), **the default is always to create a NEW milestone.** Omit `target_milestone` or pass `target_milestone: "NEW"`. Attaching spawned epics to an existing milestone is the **high-bar exception**, not the default — and agents must **not** recommend an existing milestone unless the research clearly extends that milestone's single coherent thesis and shared infrastructure.
117
-
118
- **Decision rule (agents + tools):**
119
-
120
- | Situation | Action |
121
- |-----------|--------|
122
- | Research promotion (default) | `target_milestone: "NEW"` + `new_milestone_name` |
123
- | Research clearly extends one open milestone's thesis | May attach — target must be `status: open` only |
124
- | Agent would cram into nearest plausible milestone | **Stop** — file NEW instead |
125
- | Research spans multiple milestone theses | Split first (HS-TQQS-GTH9-TASK-hs-split-research-mcp-tool), then promote each child to NEW or its one open best-fit |
126
-
127
- **Existing-milestone attach gate (enforced in `promote-research.mjs`):**
128
-
129
- | Target milestone `status:` | Spawn epics allowed? |
130
- |----------------------------|----------------------|
131
- | `open` | Yes (still requires clear thesis fit — agent judgment) |
132
- | `in_progress` | **Blocked by default.** Rare owner exception only: `attach_to_in_progress_milestone: true` + `attach_override_reason` (≥40 chars, audit trail) |
133
- | `in_review`, `review_pending`, `closed`, `parked`, `deferred`, `hibernated`, … | **Blocked** — use NEW |
134
-
135
- Still check existing milestones first — but as a high-bar gate, not an eager search. Attach to an existing milestone **only** when the research clearly extends that milestone's single thesis and shared infrastructure (best-fit is obvious, not merely that *a* fit exists). Absent that clear best-fit, file a new milestone. Filing a new milestone is a strategic decision — confirm it with the owner per the autonomy table below. Rationale: the common case is one research doc → one new milestone; cramming epics into the nearest plausible existing milestone is a helpful-but-wrong habit that over-stuffs milestones and blurs their thesis. Basis: HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t.
136
-
137
- ## Frontmatter requirements per level
138
-
139
- All four levels share the base shape from [[entity-model]]: `id`, `name`, `type`, `status`, `owner`, `created`, `updated`, `tags`, `related`.
140
-
141
- **Author `summary:` at file time.** A plain-language `summary:` is written as part of the act of filing — the same step as giving the spec a name. **Planning phase (milestone + epic):** summary at plan ingest / research promotion / milestone breakdown — milestones and epics are filed in this wave only, never with tasks. **Execution phase (task):** summary only when filing from What's Next → Launch → `/hs-next` breakdown. Pass `summary` to `heyspex_file_spec` / include it in the `POST /api/specs` body. Full contract: [[spec-summary-authoring]].
142
-
143
- **Milestone-specific** (`type: milestone`):
144
- - `epics:` — array of `[[wikilinks]]` to every child epic (mirror of each child epic's `milestone:` pointer)
145
- - `target_date:` — optional ISO date or empty string
146
- - Body must include: `## Explain Like I'm 12` **as the first body section** (see below), a `## Intended outcome` re-derived from the source research's outcome (see §Intended outcome — re-derived at every level), rationale ("why this is a milestone, not loose epics"), acceptance gate (numbered closure conditions), suggested landing order (sequencing guidance, not a complete roster or a count), out-of-scope, and `## Expected Breakage & Consequences` (see below). The body must **not** re-list the child epics as a roster or state their count — see §Child rosters live in frontmatter, not prose.
147
-
148
- **Epic-specific** (`type: epic`):
149
- - `milestone:` — `[[wikilink]]` to parent milestone (will be **required** post-HS-OFR5; today recommended)
150
- - `pillar:` — **required** `[[wikilink]]` to the perpetual `HS-PILR` theme this epic's learning belongs to. **Hard-blocked at filing**: `POST /api/specs` / `heyspex_file_spec` refuse an epic with no resolvable pillar (`400 pillar_required`). When none fits, author a net-new pillar first. Full rules: pillar-assignment-protocol.
151
- - `children:` — array of `[[wikilinks]]` to every child task
152
- - `kind:` — the scheduler **tier** vocabulary: `infra` | `foundation` | `feature` | `polish` (lower tiers rank first; read by the deterministic scheduler's tier stage per HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler). Unrecognized values sort last.
153
- - `effort:` — Fibonacci 1/2/3/5/8/13
154
- - `requires_user_testing:` — boolean
155
- - `builder_profile:` — optional at plan ingest: `mechanical` | `architectural` | `mixed` — signals anticipated local builder class for breakdown (builder-fleet-capability-reference).
156
- - Body must include: `## Explain Like I'm 12` **as the first body section** (see below), a `## Intended outcome` re-derived from the parent milestone's outcome (see §Intended outcome — re-derived at every level), scope, acceptance criteria, out-of-scope, and `## Expected Breakage & Consequences` (see below). Optional at plan ingest: **`## Builder fleet notes`** (cross-file blast radius, expected model/machine class, breakdown constraints — see builder-fleet-capability-reference). The body must **not** enumerate the child tasks as a roster or state their count — see §Child rosters live in frontmatter, not prose.
157
- - Body must include `## Why this epic exists` — the **substantive priority rationale** the scheduler "Why" surfaces (the mechanical tier/layer order is computed; *why the work matters* must be authored). See §Why-this-epic-exists rationale below. Auto-scaffolded with prompts (`canonical-frontmatter-templates` §epic / `BODY_SKELETONS.epic`).
158
-
159
- **Task-specific** (`type: task`):
160
- - `parent:` — `[[wikilink]]` to parent epic (or to a parent task, for child-task case)
161
- - `kind:` — same enum as epic
162
- - `effort:` — same scale as epic
163
- - `requires_user_testing:` — boolean
164
- - `target:` — **required for dev-factory dispatch** — repo-relative path to the primary file the builder edits
165
- - `depends_on:` — optional array of `[[wikilinks]]` to other tasks that must close first. Canonical blocker field per depends-on-canonical-blocker-field; `blocked_by` is deprecated but still tolerated by the resolver. For **phased epic children**, declare `depends_on: ['[[previous_phase_id]]']` on each non-first phase — the `/hs-next` resolver classifies blockers by predecessor status (`hard_blocked` for open/draft predecessors; `soft_review` with `start_batched` hint for in_review predecessors so epic-batch workflows aren't gated mid-flight).
166
- - **Builder dispatch (optional frontmatter — recommended when filing from breakdown):** `suggested_model:`, `suggested_machine:`, `recommended_num_ctx:` (16384 | 32768 | 65536 | 131072), `estimated_context_tokens:`, `context_files:` (YAML list → builder `--extra`), `fit_assessment:` (`safe` | `tight` | `overflow`). Controller and `pick-num-ctx` honor these as preferences; routing-table fallback remains when a machine is busy. Full contract: builder-fleet-capability-reference.
167
- - Body **should** include a `## Memories` section as the first body section (immediately under the H1, before `## Scope`) listing 3–5 wikilinks to memories relevant to the work. MCP-side `POST /api/specs` auto-populates this when the task's parent is an `epic` (scoring by `parent.tags ∪ task.tags`); hand-authored tasks add it manually per plan-ingest-agent-workflow §1c. Rationale + cross-link with the runtime injection on `/hs-next`: HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio, feedback_creation_time_memory_embedding_complements_runtime_recall.
168
- - Body must include: **`## Builder brief`** (required at `/hs-next` task filing) — target path, explicit context/`--extra` file list sized to estimated tokens, `recommended_num_ctx`, suggested model/machine, and what **not** to include (defer to sibling tasks). Monitorable acceptance criteria the gate can check (tsc + vitest or embedded `--self-test`). See builder-fleet-capability-reference for the template.
169
- - Body must include: a `## Intended outcome` re-derived from the parent epic's outcome and stated as **monitorable + controllable conditions that seed the acceptance criteria** (see §Intended outcome — re-derived at every level), scope, acceptance criteria, and `## Expected Breakage & Consequences` (see below).
170
-
171
- **Child task** — identical to task, except `parent:` points at another task (not an epic). The grandparent epic is reachable by walking `parent:` twice. **Console roadmap rendering treats child tasks as belonging to the grandparent epic for column placement** (until HS-OFR5 adds grain-aware drilldown).
172
-
173
- ## Intended outcome — re-derived at every level (not copied)
174
-
175
- Every level carries an intended-outcome section — `## Conclusion & intended outcome` on the research doc, `## Intended outcome` on milestone/epic/task. It **cascades by re-derivation, never copy-down** (grounded in HS-K4D4-2I29-RES-outcome-cascade-through-a-requirements-h: GORE/KAOS refine DOWN by asking HOW and justify UP by asking WHY; the OKR rule that copying a parent's wording down the hierarchy is the core failure mode — "the appearance of order while stripping out ownership" — whose cure is *translation*).
176
-
177
- At each level the section must (a) state the outcome **at that altitude** (the milestone/epic/task's own contribution, not the parent's words), (b) draw an explicit **↑ link** to the parent outcome it moves, and (c) name the **mechanism of contribution** — *how* it moves the parent. A child outcome that reads like a paste of its parent is **outcome theater**; rewrite it.
178
-
179
- **The task level is the verification bridge.** A leaf (task) outcome must be expressed as conditions an agent can **monitor and control** — and those conditions are exactly what the task's `## Acceptance criteria` are, each traceable ↑ to the epic outcome. This is the KAOS leaf-operationalization rule: refinement terminates only when a goal is realizable and testable. So the acceptance criteria are *derived from* the intended outcome, not invented separately.
180
-
181
- The materializers inject these sections automatically (`promote-capture.mjs` research stub; `promote-research.mjs` milestone + epic; `BODY_SKELETONS` in `spex-console/src/lib/vault.ts` for epic/task/milestone). Hand-authored specs add them per [[canonical-frontmatter-templates]].
182
-
183
- ## Child rosters live in frontmatter, not prose (single source of truth)
184
-
185
- A parent's child set is owned by its frontmatter array — `epics:` on a milestone, `children:` on an epic — which the filing tool maintains automatically and the console renders live. **The prose body must not re-list the full child set or state a derived count.** Concretely, do not write a "## The N epics" roster table, a "the six child epics" / "all six child epics" phrasing, or any list framed as the complete inventory of children.
186
-
187
- Prose may name specific children by `[[wikilink]]` where it serves the narrative — rationale, dependencies, and the suggested landing order are all fine, because they reference children, not enumerate the roster. But they must never claim to be exhaustive and must never hard-code a count. The reason is mechanical: when a child is attached, only the frontmatter array is updated — nothing re-syncs the body — so any prose roster or count silently goes stale (the HS-4PTZ-MVMD-MIL-mvp-mode-relax-review-commit-pre-launch body said "six" while its array held nine). Full diagnosis: HS-FMRH-SVCZ-DIAG-parent-spec-prose-body-drifts-from-its-f.
188
-
189
- ## Explain Like I'm 12 (required body section — milestone + epic)
190
-
191
- > **The premise:** every other section on a milestone or epic is written for someone who already knows this project. `summary:` compresses the WORK; it does not explain what the work is FOR. So a reader arriving cold — a fresh agent on an empty context window, a customer opening their own vault, the owner returning months later — has nowhere to land. This section is that landing place, and it sits **first in the body** so it is read before scope, criteria, or ids.
192
-
193
- Owner ruling 2026-08-15 (HS-KJFZ-Z12C-CAP-every-milestone-and-every-epic-carries-a → HS-W5O3-K8AG-RES-explain-like-i-m-12-as-a-required), from a request to explain the project "like I am 12, no metaphors". The ban on metaphor is the active ingredient: metaphor is the escape hatch a writer takes when they do not want to commit to what a thing literally is, and removing it forces every sentence to name the real object.
194
-
195
- **Where it applies:** `type: milestone` and `type: epic`. **Not** tasks — a task is already a single concrete piece of work and its `## Builder brief` serves the same reader.
196
-
197
- **Auto-scaffolded at creation.** `spex-console/src/lib/vault.ts` `createSpec` injects the heading, an altitude-appropriate prompt, and the ten properties below as an authoring comment — env-gated by `HEYSPEX_AUTO_ELI12_EMBED`, positioned first in the body. Same dual treatment as `## Memories` and `## Expected Breakage & Consequences`: creation-time embed, author fills it.
198
-
199
- **Milestone links, epic does not.** A milestone's section links to heyspex-north-star §Explain Like I'm 12 for what the whole product is, and covers only the parts of the product that bear on *this* milestone. Owner instruction: "we don't need HeySpex to be re-explained every time. But the important points of HeySpex that are relevant can be helpful." An epic explains its own chunk and carries **no** such link — the parent milestone already covers the wider picture.
200
-
201
- **Never a paraphrase of `summary:`.** The two do different jobs ([[spec-summary-authoring]]). If the summary can be pasted in here, the section is wrong.
202
-
203
- ### Rule 0 — grounding outranks style (read this before the ten)
204
-
205
- **Every sentence must be a rewrite of something the source already says.** The source is this spec's own body, its parent, and heyspex-north-star. Nothing else — not the writer's background knowledge, and not what is generally true of products like this one. If you cannot point at the sentence a claim came from, delete the claim.
206
-
207
- **This rule outranks all ten below.** When plain language and accuracy pull against each other, accuracy wins and the sentence gets longer.
208
-
209
- The ten style rules push toward invention. Read them knowing that:
210
-
211
- - **"Name the thing it replaces" demands a replaced thing.** If the source does not name one, there is no such sentence to write. Do not supply one.
212
- - **"Plain words over precise ones" holds only when the plain word means the same thing.** Simplifying a fact into a false one is the exact failure this rule exists to stop.
213
- - **"End on the consequence" rewards a punchy closing claim**, and cost and speed are the punchiest. They are also the easiest to get wrong.
214
- - **Stripping ids, tool names, and file paths removes the words a reader would use to check you.** Plain language is lossy on purpose, so the grounding has to be deliberate.
215
-
216
- **Three kinds of fact are never inferred, only copied:**
217
-
218
- 1. **Money** — who pays, what it costs, what is free, what is bundled.
219
- 2. **Ownership** — who runs it, who built it, whether it is open source, whose hardware it is on.
220
- 3. **Scale** — how many, how fast, how big, how often.
221
-
222
- Naming an outside product carries **the relationship the source states** — uses it, replaces it, self-hosts it, pays for it. Never read the relationship off the product's name.
223
-
224
- **Worked failure — 2026-08-15.** A draft section for the SpexCrawl milestone opened: *"Today HeySpex pays a service called Firecrawl to do that."* Every one of the ten style rules is satisfied by that sentence. It is false. The thing being reproduced is open-source Firecrawl, self-hosted on the owner's own hardware at no cost, and a self-hosted deployment never calls the company at all (spexcrawl-parity-target-is-self-hosted). The word "paying" did appear in the source, in a different sentence about a different thing, and the pull toward plain words moved it onto the wrong subject. A style rule with no grounding rule is a machine for producing well-written falsehoods, at whatever rate you run it.
225
-
226
- ### Written by a machine? Then money, ownership, and scale are off the table
227
-
228
- A human author writing this section gets Rule 0 and is trusted with it. **Anything writing the section unattended does not get that trust**, because the failure is not decidable by a checker.
229
-
230
- This was tested, not assumed. A grounding critic was built for exactly this job: it reads the source and each claim, and must quote the sentence that supports it. On 2026-08-15 it was run against both DeepSeek V4 Flash and DeepSeek V4 Pro drafting the SpexCrawl milestone. **Both models wrote that HeySpex pays for page fetching, and the critic passed both** — because "paying to fetch pages in the cloud" does appear in the source, in a sentence about what the new design *avoids*. The critic matched the words and missed the subject, which is the same mistake it was hired to catch.
231
-
232
- So for unattended writing the whole class is refused rather than checked. `lintEli12(..., { strict: true })` in `scripts/eli12-lint.mjs` makes any sentence touching money, ownership, or scale a hard error, and the generator is told the class is out of scope even when the source states it. A spec whose explanation genuinely needs such a claim fails the lint and goes to a human. That is the correct direction to fail.
233
-
234
- The deterministic checks that back this up: spec ids, file paths, code formatting, em dashes, semicolons, exclamation points, figurative language, walls of text, the milestone/epic North Star link rule, **any product name absent from the source**, and **any run of six or more words lifted verbatim from the source**. That last one exists because hardening the grounding rule pushes a writer toward the safe failure — paraphrasing the spec sentence by sentence, which passes every truth check and teaches a reader nothing, since a reader who could follow the spec would not need this section.
235
-
236
- ### The ten properties (write to these, not to "write it simply")
237
-
238
- Observed from the approved example in heyspex-north-star, not invented from taste. Each one can be read off a draft and answered yes or no — which is the whole point. "Write it simply" is unfalsifiable, so it produces nothing and no reviewer can reject a violation. Same method as communication-style-protocol. **All ten are subordinate to Rule 0 above.**
239
-
240
- 1. **No metaphors and no figurative language at all.** Say what the thing literally is.
241
- 2. **One idea per paragraph**, two to four sentences. Never a wall of text.
242
- 3. **Comparisons are to real named products, not images.** "a website like Jira or Linear" is useful. "like a train station" is not.
243
- 4. **Plain words over precise ones** where they carry the same meaning.
244
- 5. **Say what it is FOR before what it IS.** Open with the purpose.
245
- 6. **Name the thing it replaces**, when it replaces something.
246
- 7. **Spaced hyphens.** No em dashes, no semicolons, no exclamation points, no hype.
247
- 8. **No spec ids, no tool names, no file paths, no jargon.** Not one appears in the approved text.
248
- 9. **End on the consequence**, not a summary of what was just said.
249
- 10. **Assume the reader knows nothing about this project** — but do not re-teach the whole product. Only the parts that bear on THIS work.
250
- 11. **Name the role, never use a pronoun for it.** No "you", "your", "me", "my", "we", or "our". Owner ruling 2026-08-15: a spec outlives the conversation it was written in, so "me" ends up pointing at nobody and "you" changes meaning with whoever opens the file. Use the roles from glossary §Roles, written in plain words rather than the capitalized term — **the developer** (glossary: Builder), **the reviewer**, **the agent**, and where the two kinds matter, the agent working in a window versus the agent running overnight. Where a role has no stated pronoun, use they/them.
251
-
252
- The approved example itself is held **verbatim** in heyspex-north-star and marked do-not-paraphrase. Quote it where it is reused; never re-voice it.
253
-
254
- ### Backfill — open and in-progress only
255
-
256
- **Owner ruling 2026-08-15 (revised the same day).** The first ruling was no backfill at all. It was then widened: **milestones and epics at `open` or `in_progress` are backfilled; everything else is not.** Closed, superseded, cancelled, parked, and review-stage specs stay as they are — an explanation added to work nobody will pick up again is cost with no reader.
257
-
258
- A backfilled section is **not** author-written, and the file does not pretend otherwise. Every one carries an HTML-comment marker naming it as generated and asking the next person who works the spec to rewrite it. The original objection still stands — the author who understood the work is the one who should explain it — so the marker keeps that visible rather than papering over it.
259
-
260
- Sweep: `node scripts/eli12-backfill.mjs --run`. It refuses to write on a guess. Every draft must clear the strict lint and, when a spec cannot, it is **skipped and listed** rather than written. Nothing is written silently. The ledger at `.heyspex/eli12-backfill.jsonl` makes a re-run resume instead of redoing work.
261
-
262
- ### Unfilled scaffold at breakdown
263
-
264
- Same handling as `## Expected Breakage & Consequences` below: an unfilled scaffold is acceptable at hand-off but is a signal the reviewer should question. At **task breakdown** the epic's section is the plain-language grounding for each task's `summary:` — the ten properties above govern that field too. When the section is still the scaffold, the judge is told so explicitly and must **not** read the authoring comment as epic content (`dev-factory/engine/breakdown.mjs` `eli12GroundingBlock`). Filing is **not** gated on the section being filled.
265
-
266
- **Classification:** template-shape (heyspex-platform-vault-doubles-as-customer-template-source) — ships to every customer vault, content differs per vault, and each vault links to its own North Star.
267
-
268
- ## Expected Breakage & Consequences (required body section)
269
-
270
- > **The premise:** to make something better you usually have to break it first. Implementing real work *raises* the error/failure count before it settles — adding one `surface id` legitimately took a tree from 10 → 17 tsc errors because `layout-panes.ts` needed a resolution entry and a test fixture was now missing a field. Those were **expected consequences**, not regressions. This section makes that anticipation explicit so the executing agent and the reviewer read a transient rise as normal, and so the cascade is enumerated *before* it is hit.
271
-
272
- Every milestone, epic, and task carries a `## Expected Breakage & Consequences` section. It is **auto-scaffolded at creation** (`spex-console/src/lib/vault.ts` `createSpec`, env-gated by `HEYSPEX_AUTO_BREAKAGE_EMBED`, placed below `## Memories`), **surfaced in the `/hs-next` execution brief** (above NEXT STEP, via `enrichOnDeckWithExpectedBreakage` in `vendor/heyspex-mcp/cli.mjs`), and **reconciled at close** (the HS-REV `### Breakage reconciliation` table — see [[session-close-protocol]]). It mirrors the dual-layer (creation-time embed + runtime injection) treatment of `## Memories` per HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio.
273
-
274
- The section holds **two distinct halves** — keep them separate, because a non-breaking side-effect is not the same as breakage:
275
-
276
- 1. **Breakage** — things that will *error or fail* until fixed in-scope (type/contract consumers, test fixtures/snapshots, call sites). Pair this with a **Baseline → Exit** contract: the tsc/test error count before work starts, and the exit condition (every net-new error resolved before flipping to `in_review`, back to ≤ baseline).
277
- 2. **Consequences / side-effects** — intended ripples that change behavior but do **not** break (a removed field, a changed default, a downstream behavior shift). These are real and worth recording even though nothing "fails."
278
-
279
- **Altitude-appropriate content:**
280
-
281
- - **Task** — the concrete cascade: name the specific files/fixtures/call-sites, plus the baseline→exit error contract.
282
- - **Epic** — higher-altitude blast radius: which subsystems the epic's changes ripple into, so the task breakdown can pre-allocate fix-up scope. Exit = no net-new errors at epic DoD.
283
- - **Milestone** — which cross-epic seams may break; lighter, prose. Exit = no net-new errors across the milestone's epics at the acceptance gate.
284
-
285
- The scaffold ships with placeholder prompts; the author replaces them (it is **not** auto-derived). A future codebase-graph blast-radius (HS-AR82-8BAA-TASK-blast-radius-k-hops-codebase-graph, HS-M8GC-9U4U-TASK-pr-impact-blast-radius-backlog) can later pre-fill the Breakage half. An unfilled scaffold is acceptable at hand-off but is a signal the reviewer should question.
286
-
287
- **Expected Breakage cascade (planning → breakdown → execution).** The section **cascades by allocation, not copy-down** — same re-derivation principle as :
288
-
289
- | Phase | When authored | Altitude | Allocates from |
290
- |---|---|---|---|
291
- | **Milestone** | Plan ingest / research promotion (plan-ingest-agent-workflow) | Cross-epic seams and integration points that may break as the milestone lands | Research scope + milestone thesis |
292
- | **Epic** | Epic filing under the milestone (planning phase) | Subsystems/files this epic's changes ripple into | Parent milestone's breakage (where relevant) |
293
- | **Task** | `/hs-next` breakdown only (pre-execution-alignment-gate) | Concrete files/fixtures/call sites + baseline→exit contract | Parent epic's blast radius **and** milestone seams surfaced in the decomposition brief |
294
-
295
- At **task breakdown**, read the parent epic's and milestone's `## Expected Breakage & Consequences` sections first (the `/hs-next` decomposition brief surfaces them verbatim under **Expected Breakage — parent context**). Each task **pre-allocates fix-up scope** from those parent sections — drill down to specifics; do not paste parent prose or invent breakage in isolation. If a parent section is still the unfilled scaffold, fill it before decomposing (or note explicitly in each task why breakage is none).
296
-
297
- Task-level shape:
298
-
299
- ```markdown
300
- ## Expected Breakage & Consequences
301
-
302
- Implementing this will transiently raise the tsc/test error count — that's expected.
303
- Enumerate the cascade so a rise reads as normal, not a regression.
304
-
305
- **Breakage (will error/fail until fixed in-scope):**
306
- - Type/contract consumers: `<files referencing the changed symbol>`
307
- - Test fixtures/snapshots: `<state literals / snapshots needing the new field>`
308
- - Call sites: `<…>`
309
-
310
- **Consequences / side-effects (intended, non-breaking):**
311
- - `<changed default / removed field / downstream behavior shift>`
312
-
313
- **Baseline → Exit:** tsc = `<N>` errors / tests `<green | N failing>` before work.
314
- Exit: every net-new error resolved before flipping to `in_review` (back to ≤ `<N>`).
315
- ```
316
-
317
- ## Why-this-epic-exists rationale (substantive priority, not mechanical order)
318
-
319
- The deterministic scheduler explains **how** a spec ranked — tier, dependency layer, what-matters, criticality (the mechanical "Why"). It cannot explain the **substantive why**: *why this work matters and why it belongs at the top now*. That has to be **authored**. Without it, the "Why" is factual but thin. Research + framework grounding: HS-XMSD-W8OZ-RES-authoring-substantive-priority-rationale (Cost of Delay/WSJF, Shape Up appetite, RICE, Definition-of-Ready); the mechanical layer it complements: HS-AVSG-7J97-RES-how-the-scheduler-explains-itself-determ.
320
-
321
- Every epic's `## Why this epic exists` section carries up to four **plain-language** prompts — no scores, no numeric fields (the scalar-priority anti-pattern HS-141W-PRIO-RES-priority-and-scoring-systems-2026 rejected):
322
-
323
- - **Problem right now (required at filing):** one sentence — the specific problem this solves *and* why now (not next cycle). This is the qualitative minimum Cost of Delay: it names the problem and the time-criticality in one breath.
324
- - **What this unlocks (required when `kind: infra` or `foundation`):** what future work shipping this makes possible. The authored complement to the scheduler's criticality stage (the graph counts downstream specs; the author says why they matter).
325
- - **Skip cost (write before pinning this as on-deck):** "If we skip this cycle, we lose: …" — a *specific* consequence, not "it's important."
326
- - **Confidence note (optional):** flag any part of the rationale where confidence is low (RICE's bias-brake; also keeps an agent from treating shaky rationale as ground truth).
327
-
328
- **Rationale strengthens as a spec rises (progressive elaboration / Definition-of-Ready).** A spec far down the queue needs only the lightweight Problem (+ Unlocks). As it approaches the **top / on-deck pin**, it should also carry the **Skip cost** sentence — the closer to execution, the more specific the justification. This is enforced as a **soft nudge at the pin action** (the pin UI prompts for the skip-cost sentence), **never a hard block** — keep it progressive elaboration, not a phase-gate (HS-XMSD §Definition-of-Ready caveat). The `what_matters` (★/♥) flag remains the coarse value band and always appears in the "Why".
329
-
330
- ## Linkage rules
331
-
332
- Linkage is **bidirectional and explicit** — agents MUST update both sides when filing a new spec:
333
-
334
- | Edge | Parent's field | Child's field |
335
- |---|---|---|
336
- | Milestone → Epic | `epics: ["[[HS-EPIC-…]]"]` | `milestone: "[[HS-MIL-…]]"` |
337
- | Epic → Task | `children: ["[[HS-TASK-…]]"]` | `parent: "[[HS-EPIC-…]]"` |
338
- | Task → Child task | `children: ["[[HS-TASK-…]]"]` | `parent: "[[HS-TASK-…]]"` |
339
-
340
- **`heyspex_file_spec` does NOT wire the parent's `children[]` back-reference.** It creates the task file and sets the task's own `parent:` field, but the parent epic's `children:` array is a separate write. The signal is the response: if `parent` or `milestone` come back `null`, the back-reference was not written. Always call `heyspex_register_spec(spec_id, parent_id)` immediately after every `heyspex_file_spec` call — it writes both sides atomically and is idempotent (see §Filing procedure step 5).
341
-
342
- After filing, run `heyspex_validate_linkage` (MCP) to confirm both sides are present and resolve to existing files. See plan-ingest-agent-workflow §Checklist. Manually confirm as a fallback: (a) the new file's `parent:` / `milestone:` resolves to an existing file, (b) the parent's `children:` / `epics:` array includes a wikilink to the new file, (c) no other file already claims this id.
343
-
344
- **When moving an existing spec** (re-parenting an epic, changing milestone membership), filing rules above are not enough — consult internal-linking-protocol to leave a summary + wikilink stub at the old home so inbound links and lexical retrieval signal survive.
345
-
346
- **The parent milestone must still be accepting work.** An epic may only be filed under a milestone that is **not claim-terminal** — i.e. `open` / `in_progress` / `draft` (and `parked`, which can legitimately gain a daughter epic during a split). Filing an epic under a milestone whose status is `in_review`, `closed`, or any other claim-terminal status is **hard-blocked** at `POST /api/specs` / `heyspex_file_spec` with `400 parent_milestone_not_open`, backed by a defense-in-depth throw in `createSpec` — because back-linking the child mutates that frozen parent's `epics:` array, violating closed-trackable-specs-are-historical-record-not-living-docs. The terminal set is the claim-eligibility SSOT (`CLAIM_TERMINAL_STATUSES` in `spex-console/src/lib/scheduler/claim-eligibility.ts`), so "cannot be claimed" and "cannot accept a child epic" stay one rule. To proceed, pick an open milestone (the error lists them) or reopen the target. Per HS-05ND-2DMM-DIAG-epic-can-be-filed-under-a-non-open-in-re.
347
-
348
- ## Filing procedure
349
-
350
- 1. **Decide the level** using the table in §What each level is for. If you're unsure, ask the user before filing.
351
- 2. **Allocate the ID** per vault-id-assignment-protocol §Assignment procedure:
352
- - **Planning (milestone/epic):** `allocateRandomVaultId()` or `POST /api/specs` / `heyspex_file_spec` (console auto-allocates).
353
- - **Execution (task):** same tools, but only at the on-deck `/hs-next` breakdown — not during plan ingest.
354
- - Milestones: hand-pick a 4-character uppercase alphanumeric mnemonic per vault-id-assignment-protocol; verify uniqueness by scanning `specs/milestones/` for existing `HS-MIL-*` IDs. File via `POST /api/specs` with `type: "milestone"` (shipped in HS-Y855-1AX3-TASK-recognize-milestone-in-console-type-regi / HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing — see `TYPE_TO_FOLDER` and milestone seed in `spex-console/src/lib/vault.ts`).
355
- - Research / Captures: random ID per vault-id-assignment-protocol.
356
- - Protocols: `HS-PROTO-<slug>` (deterministic from slug, not random).
357
- 3. **Create the file** at the correct folder per the table in §The four levels.
358
- 4. **Fill the frontmatter** per §Frontmatter requirements; in particular, set the **parent linkage** (`milestone:` for epic, `parent:` for task) and **author the `summary:`** as part of this step (see §Frontmatter requirements → "Author `summary:` at file time").
359
- 5. **Register the spec into the parent** via `heyspex_register_spec(spec_id, parent_id)` — this writes both the task's `parent:` field AND the parent's `children:` (or `epics:`) array atomically and is idempotent. **Do NOT rely on `heyspex_file_spec` alone** — it creates the file but does not update `children[]` on the parent. Call `heyspex_register_spec` in the same turn as filing; when batching multiple tasks, run all registrations in parallel after the filing calls complete. The signal that a registration was missed: the `heyspex_file_spec` response returns `parent: null`.
360
- 6. **Run `heyspex_validate_linkage`** to confirm no orphans (planned MCP tool — until it ships, follow the manual fallback in §Linkage rules).
361
- 7. **Run `npm run validate:vault-ids`** (only required when hand-authoring task/epic files; console-created specs are validated by the API).
362
- 8. **Commit** spec + manifest updates together. The pre-commit hook (`npm run hooks:install` once per clone) blocks duplicate IDs.
363
-
364
- ## Agent autonomy boundaries
365
-
366
- This is the part agents most often get wrong. The default is **ask first** for structural changes, **decide on your own** for execution-level fanout.
367
-
368
- | Action | Agent autonomy |
369
- |---|---|
370
- | **File a new milestone** | **Ask the user.** Milestones are strategic decisions; do not file unilaterally. |
371
- | **File a new epic under an existing milestone** | **Ask the user**, unless the epic clearly maps to a single audit/finding/incident already named by the user. When in doubt, ask. **File the epic with `children: []`** — do not fan out tasks in the same pass (see the next row). **The parent milestone must be open** (non-claim-terminal — `open`/`in_progress`/`draft`/`parked`); filing under an `in_review`/`closed` milestone is hard-blocked with `parent_milestone_not_open` (see §Linkage rules). |
372
- | **Fan out tasks under a new epic** | **Never at epic-creation / planning time.** Tasks are created **just-in-time at execution**, when the epic becomes the on-deck `ClaimTarget` (via `/hs-next`) — that breakdown is the *first step of executing the epic*, not a planning activity. At doc-creation and during clarification, offer **milestone + epics only**. See the §empty-state grain-split note below and deterministic-priority-scheduler-supersedes-scored-priority. |
373
- | **Add a child task during execution of a parent task** | **Decide autonomously.** This is the canonical place agents have authority — when executing a task uncovers sub-work, fan out into child tasks without asking. Link via `parent:` to the task you're executing. |
374
- | **Reorder child tasks within a task** | **Decide autonomously.** Sequence and decomposition of child tasks is the agent's call. |
375
- | **Add a `related:` link to another spec** | **Decide autonomously.** Cross-linking is low-risk. |
376
- | **Change a spec's `parent:` or `milestone:` field** | **Ask the user.** Restructuring linkage changes the roadmap rendering and other agents' context. |
377
- | **Change a spec's `status:` from anything to `in_review`** | **Decide autonomously** when acceptance criteria are met (per [[session-close-protocol]]). |
378
- | **Change a spec's `status:` to `closed`** | **Never.** Only the reviewer (human) flips to `closed`. Agent terminal action is `in_review`. |
379
- | **Create a memory doc** | **Decide autonomously** when a lesson/decision/UI pattern emerges (per CLAUDE.md §Automatic knowledge capture). |
380
- | **Modify a protocol doc (this kind of file)** | **Ask the user.** Protocols are stable contracts; treat changes the same as ADR changes. |
381
- | **File a new diagnose (standalone bug fix)** | **Decide autonomously** when a bug is reported and the symptom/scope is clear — standalone (no parent) is the default. **Ask the user** before giving it a `parent:` (epic/milestone) or a `blocks:` edge, since both change roadmap/on-deck rendering. |
382
-
383
- **How the empty-state grain-split is surfaced (2026-06-01).** The two "no children yet" states are NOT the same severity, and the console signals them differently (HS-6A4P On Deck split, pre-execution-alignment-gate):
384
-
385
- - An **epic with `children: []`** is the **normal first step of executing it** — tasks are created just-in-time, at the last possible moment, so they stay relevant and don't drift (deterministic-priority-scheduler-supersedes-scored-priority). Surfaced GREEN as "Next step: break into tasks" (a happy-path step, not a defect).
386
- - A **milestone with `epics: []`** is **abnormal** in the agent-driven workflow (milestones + epics are normally filed together in one planning session) — surfaced as a YELLOW/RED "Needs breakdown" warning.
387
-
388
- **Before either grain-split breakdown starts, resolve inbound `amends:` first.** If the epic (or its parent milestone) being broken down is an amends target, decompose to the amending doc's scope, not the stale original — see pre-execution-alignment-gate §"Resolve inbound amendments before decomposing" and supersession-protocol §Amends.
389
-
390
- ## Claim eligibility & `ClaimTarget`
391
-
392
- **The single eligibility rule:** *a spec is claimable iff it is its own current `ClaimTarget`*. The console claim endpoint (`POST /api/specs/:id/claim`) enforces this — any POST whose target is not the current `ClaimTarget` returns `409 claim_blocked_by_descendants` with the actual current target's id in the response payload (HS-B0Z7 of HS-VLB8). This is the **vault invariant** that backs the legibility surface in `/hs-next`: the brief always names what is actually claimable, and the claim never silently lands on the wrong level.
393
-
394
- ### `ClaimTarget` type
395
-
396
- `computeClaimTarget(spec, vaultRoot, alignmentState)` (`vendor/heyspex-mcp/blocker-resolver.mjs`; TypeScript mirror in `spex-console/src/lib/scheduler/claim-eligibility.ts`) returns a 7-field record:
397
-
398
- | Field | Shape | Notes |
399
- |---|---|---|
400
- | `level` | `"task" \| "epic" \| "milestone"` | What grain the user is actually authorizing. |
401
- | `spec_id` | `string` | The id the user will claim — **not necessarily the spec id `/hs-next` is currently looking at**. |
402
- | `title` | `string` | Display title (frontmatter `name:`). |
403
- | `intent_verb` | plain English | "implement it" / "break it into Tasks" / "break it into Epics" — see §Intent verbs below. |
404
- | `tone_token` | `--status-neutral \| --status-ready \| --status-pending` | UI tone per HS-6A4P-MNBD-EPIC-milestone-needs-breakdown-awareness. |
405
- | `hierarchy_breadcrumb` | `string[]` | Root → leaf id chain (e.g. `["HS-AGEX-MIL", "HS-VQZ8-EPIC", "HS-NJPH-TASK"]`). |
406
- | `alignment_state` | `"fresh" \| "drift_detected" \| "unknown"` | Transparency only — the alignment gate is upstream; this field reports the gate's verdict on the brief, it does not re-decide it. |
407
-
408
- ### The three corollary rules
409
-
410
- | Spec kind | Resolution |
411
- |---|---|
412
- | **Task** | Always its own `ClaimTarget` (level `"task"`). |
413
- | **Diagnose (not broken down)** | Its own `ClaimTarget` (a leaf, level `"task"`-equivalent, intent "implement it"). No parent to recurse from; directly claimable. The one-fix case, and the common one. |
414
- | **Diagnose that spawned work** | NOT claimable — `rollup_state: "tracked_by_work"`, carrying `tracked_by` with the specs it spawned. Claim one of those instead. It is not terminal: the bug is still live, and it closes only when its reproduction stops reproducing (HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose). |
415
- | **Epic with open children** | Recurses through `children:` in manifest order; returns the first non-terminal-status descendant. The Epic itself is NOT claimable while it has any open Task. |
416
- | **Epic with no open children** (drained or fresh) | Its own `ClaimTarget` — intent depends on child rollup state (see §Rollup states below). |
417
- | **Milestone with open epics** | Recurses through `epics:`; returns the first non-terminal descendant (which itself may recurse one more level). |
418
- | **Milestone with no open epics** | Its own `ClaimTarget` — intent depends on child rollup state (see §Rollup states below). |
419
-
420
- Terminal statuses that block recursion into a descendant: `closed`, `shipped`, `in_review`, `review_pending`, `archived`, `cancelled`, `duplicate`, `hibernated`, `superseded`.
421
-
422
- > **A review backlog is never a claim blocker, and never needs owner permission to work past.** `in_review` / `review_pending` are *terminal for recursion* precisely because they are build-complete — the code is already in the tree. Specs sitting unreviewed are the expected steady state under a batched review grain (`mvp_mode.review_backlog_never_blocks: true`, owner ruling 2026-08-14), so the `ClaimTarget` is claimed on the normal handshake no matter how deep the review queue is. Never tell the owner to Pass/commit first, never offer to redirect the claim, never ask permission to proceed past pending reviews. Only a **real** dependency blocks — the new work needs code or a decision the other spec has not produced yet; name that dependency specifically and keep going. Full rule: [[session-close-protocol]] §MVP Mode variant item 5. Enforced in `blocker-resolver.mjs` (`STATUS_SEVERITY` → `soft_review`, never `hard`).
423
-
424
- ### Rollup states (HS-Q7IG)
425
-
426
- When every child is terminal (no `open` / `in_progress` descendant), `computeClaimTarget` classifies the parent instead of defaulting to "break it into Tasks/Epics":
427
-
428
- | Child rollup | `rollup_state` | Intent verb | Meaning |
429
- |---|---|---|---|
430
- | No `children:` / `epics:` entries | *(absent)* or `no_children` | *break it into Tasks* / *break it into Epics* | Normal just-in-time breakdown — the happy path. |
431
- | ≥1 child `in_review` / `review_pending`, rest terminal | `reviewer_blocked` | *wait for reviewer* | **Strict (task grain) only:** breakdown/implementation is done but each child still needs its own review before the parent can close. `/hs-next` must **not** offer to claim for breakdown. |
432
- | Every child build-complete below the active review grain (`in_review` / `review_pending` / `closed`, no `open`/`in_progress`) | `ready_for_close` | *flip to in_review* | **MVP (milestone grain) and Standard (epic grain):** children at `in_review` below the grain are build-complete, not blockers — parent close-out (flip + HS-REV) is claimable. |
433
- | Every child `closed` / cancelled / etc., none awaiting review | `ready_for_close` | *flip to in_review* | Children are fully terminal; agent close-out is the next write. |
434
-
435
- ### Intent verbs (plain English, per user preference)
436
-
437
- The on-deck brief uses **plain action verbs** rather than vault jargon ("decompose"). Per feedback_avoid_decompose_in_ux_copy:
438
-
439
- | Level | Verb |
440
- |---|---|
441
- | Task | *implement it* |
442
- | Epic with no children (fresh) | *break it into Tasks* |
443
- | Epic with all children awaiting review | *wait for reviewer* | Strict (task grain): children must close before parent. |
444
- | Epic with all children build-complete below review grain | *flip to in_review* | MVP / Standard: claim for parent close-out even when children are `in_review`. |
445
- | Epic with all children closed (none in review) | *flip to in_review* |
446
- | Milestone with no epics (fresh) | *break it into Epics* |
447
- | Milestone with all epics awaiting review | *wait for reviewer* |
448
- | Milestone with all epics closed (none in review) | *flip to in_review* |
449
-
450
- System slugs (`uc-decompose-intent`, `extractDecompositionContext`, etc.) keep their identifiers — only the human-facing copy switches.
451
-
452
- ### SSOT invariants
453
-
454
- `computeClaimTarget` and `resolveHierarchy` read **only from disk** via the injected loader (`loadTask(vaultRoot, ref)` in production). Per HS-QA02 (the forensic-reconstruction incident), the breakdown decision must not consult a derived index — stale index data has previously triggered phantom `claim_blocked_by_descendants` rejects. Two invariants follow:
455
-
456
- - **Structural truth from disk.** Children, parent, milestone, and status are read from the spec's frontmatter on the SSOT path (`specs/.../<basename>.md`) every call. No cohort, no scheduler cache, no Neon row.
457
- - **Time truth from git.** Where temporal decisions are required (binary skip rule's `older_children` check, world-state delta), the timestamp comes from `git log -1 --format=%ct`, not from frontmatter `updated:`. See pre-execution-alignment-gate §Signature.
458
-
459
- ### Relationship to the alignment gate
460
-
461
- The alignment gate (upstream, `/hs-next` step 1) decides whether the response is `fresh` or `drift_detected` and renders the brief sections + 4-option menu when drift fires. `computeClaimTarget` (downstream, T2 of HS-VLB8) then renders the claim banner that names the target spec, the breadcrumb, and the intent verb. The two surfaces are independent:
462
-
463
- - The alignment gate decides **whether to re-ground**.
464
- - `ClaimTarget` decides **what to actually claim**.
465
-
466
- A drift-detected brief still names a single claimable target; the user picks `[1] proceed` to record an override and claim, or `[4] defer` to close the brief.
467
-
468
- ## Related procedures
469
-
470
- | Procedure | What it covers |
471
- |---|---|
472
- | [[entity-model]] | Frontmatter schema, valid status values, valid types, wikilink resolution rules |
473
- | vault-id-assignment-protocol | Random suffix allocation, ID collision handling |
474
- | plan-ingest-agent-workflow | Creating specs from implementation plans, manifest registration, linkage validation |
475
- | plan-ingest-review-roadmap-workflow | Same flow with a human review gate |
476
- | roadmap-protocol | How the roadmap manifest is structured, what columns specs render in |
477
- | [[session-close-protocol]] | The `in_review` → `closed` handoff between agent and reviewer |
478
- | [[memory-protocol]] | When to file memories (lessons / decisions / references / UI patterns) |
479
- | internal-linking-protocol | Preserve wikilinks + retrieval signal when re-parenting or restructuring trackable specs |
480
- | mcp-slash-surface-protocol | MCP tools the agent uses to interact with the vault |
481
-
482
- ## Discovery & enforcement
483
-
484
- This protocol is only useful if agents **find it** when they need it. Today, discovery relies on agents:
485
-
486
- 1. Reading CLAUDE.md at session start (which now links here).
487
- 2. Recalling tag-filtered memories before relevant work (e.g. `tags: ["vault", "specs"]`).
488
- 3. Pattern-matching against existing example specs — **the failure mode this protocol exists to replace**.
489
-
490
- The robust solution — a procedure registry, an MCP `heyspex_get_procedure` tool, and pre-flight reminders — is planned in HS-M4J5-3QS8-EPIC-agent-procedure-discovery-enforcement-la. Until that lands, agents should:
491
-
492
- - Read this protocol before filing any milestone, epic, or task they didn't claim via `/hs-next`.
493
- - Re-read it whenever the user says something like "follow HeySpex procedures" — that's a signal a pattern-match failure occurred.
494
-
495
- ## Open questions / in-flight
496
-
497
- - **Required `milestone:` on Epics** — HS-OFR5 makes this required with a grace window. Until then, an epic without `milestone:` is tolerated.
498
- - **Sub-milestones** — explicitly out of scope per HS-1JC8 §Out of scope. Large milestones should be split horizontally (more milestones at the same level), not nested. See §Milestone sizing — epic cap above for the soft/hard caps and the owner-override exception.
499
- - **Cross-milestone task dependencies** — `depends_on:` can point across milestone boundaries today; the scheduler treats each milestone independently. Cross-milestone scheduling is a v2 concern.
500
-
501
- ## Change log
502
-
503
- | Date | Author | Change |
504
- |---|---|---|
505
- | 2026-05-27 | pwher (with claude opus 4.7) | Initial draft. Codifies the Milestone → Epic → Task → child task hierarchy that was previously implicit. Filed alongside the agent-procedure-discovery planning epic. Status `draft` until reviewed; flip to `open` after first cross-agent use confirms it answers the questions agents actually have. |
506
- | 2026-05-28 | pwher (with claude opus 4.8 [1m]) | Conflict fix (Builder-approved protocol edit): `kind:` enum corrected from `feature\|bug\|spike\|story\|polish` to the scheduler tier vocabulary `infra\|foundation\|feature\|polish` actually read by HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler and used by every real epic (MILE=infra, M2J3/Y9OJ=foundation). Surfaced during the HS-SSOT-MIL conflict check. |
507
- | 2026-05-30 | claude opus 4.7 [1m] · session `local` | Added `### Milestone sizing — epic cap` subsection under §What each level is *for*: soft cap 8, at-cap 9–10, hard cap 10, owner-override exception with inline `## Exception — over-cap rationale` requirement, and a filing flow for over-cap proposals. Codifies the previously informal cap surfaced at HS-QOAL-JN9H-TASK-manifest-schema-v3-and-horizon-retirement close-out (PFAG=9, CMCH=11, IART=11). Filed under HS-SP9M-MCAP-TASK-over-cap-milestone-splits-pfag-cmch-iart per HS-OFR5-M2J3-EPIC-introduce-milestones-as-highest-entity; owner permitted the three current over-cap milestones to remain unsplit under the new exception clause. |
508
- | 2026-06-03 | claude opus 4.7 [1m] · session `local` (with pwher) | Added the **"Single-intent test — the deliverable, not the surface count"** clarification under §"What each level is *for*" §Anti-patterns. Per HS-UEF3-S1HX-RES-seo-style-information-architecture-for-t §Q9 findings: a migration epic with N file-tasks is single-intent; a bundled milestone whose acceptance gate names three independently-acceptable ships is multi-intent. Cross-links to doc-canonicalization-protocol + evergreen-doc-architecture-srp-and-seo-pillars so plan-side and doc-side SRP share one principle. One-sentence amendment — explicitly NOT a new plan-side protocol; SSOT for plan-side questions stays in this protocol. |
509
- | 2026-06-03 | claude opus 4.7 [1m] · session `local` (with pwher) | Added the **"Closed specs are historical record — frozen + immutable"** clarification under §"What each level is *for*" §Anti-patterns. User flagged the principle as foundational and previously undocumented: *"Epics are stale once implemented — by design. Immediately locked and frozen and never updated again."* The principle existed implicitly across four references (supersession-protocol, this protocol's prior anti-pattern about post-close follow-ups, edit-protocol-in-place-with-version-preamble, spec-literal-is-intent-not-literal-update-on-rename); now named explicitly in the foundational memory-decision closed-trackable-specs-are-historical-record-not-living-docs under HS-PILR-pillars. Cross-links to HS-ZUVY-S1A6-EPIC-shipped-surface-closure-event-archive-wi (the console surface that displays the historical record) and to [[entity-model]] §close_reason (the storage convention: archive = filter not folder). |
510
- | 2026-06-04 | claude opus 4.7 [1m] · session `local` (with pwher) | Reconciled stale "milestones cannot be created via `POST /api/specs`" directive in §Filing procedure (and removed the matching §Open questions / in-flight bullet) after verifying HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing and HS-Y855-1AX3-TASK-recognize-milestone-in-console-type-regi both closed 2026-05-31 — `TYPE_TO_FOLDER` now has `milestone: "milestones"` and `createSpec()` handles `type: "milestone"` (`spex-console/src/lib/vault.ts:1263, 1605-1614`). The prior line cited a now-nonexistent `vault.ts:897`, a tell that no agent re-grepped after the implementation moved. Surfaced by the user flagging the directive as a memory candidate for review; rejected the memory because the corrected truth is derivable from current code. |
511
- | 2026-06-05 | claude opus 4.7 [1m] · session `local` | **T5 added §Claim eligibility & `ClaimTarget`** (HS-VLB8 / HS-IYD8). Codifies the vault invariant *a spec is claimable iff it is its own current `ClaimTarget`*, the 7-field type, three corollary rules (Task / Epic-with-open-children / Epic-empty / Milestone-with-open-epics / Milestone-empty), plain-English intent verbs ("implement it" / "break it into Tasks" / "break it into Epics" — per feedback_avoid_decompose_in_ux_copy), tone tokens linking HS-6A4P, the two SSOT invariants (structural truth from disk; time truth from git), and the upstream/downstream relationship with pre-execution-alignment-gate. Implementation references: `vendor/heyspex-mcp/blocker-resolver.mjs` (HS-USOP), `spex-console/src/lib/scheduler/claim-eligibility.ts` (HS-B0Z7). Also upgraded `heyspex_preflight({ intent: "claim_spec" })` to return this protocol as `required`. |
512
- | 2026-06-07 | claude opus 4.7 [1m] · session `local` (with pwher) | **Added §"Sequencing language — disambiguate 'after X'"** under §What each level is *for*. Bans bare "after X" wording in task bodies; requires one of three explicit phrasings ("after X's code lands" / "after X closes" / a behavioral marker). Sets the default-read rule for legacy bare-"after-X" specs to "after X's code lands" (cheapest read in MVP mode where close-out is a reviewer formality). Companion enforcement on the close side (the `### Deferred items audit` subsection in the HS-REV template): [[session-close-protocol]]. Driven by the feedback_mvp_mode_after_x_closes_means_after_code_lands incident during HS-7FOD where bare "after DV1T" in the task body led an agent to defer six in-tree edits to a future session for no real gate. |
513
- | 2026-06-18 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Child rosters live in frontmatter, not prose (single source of truth)"** and amended the milestone/epic §Frontmatter-requirements body lines: a parent's prose body must **not** re-list the child set or hard-code a count (no "## The N epics" roster table, no "all six child epics" wording); the `epics:`/`children:` frontmatter array is the single source of truth, rendered live by the console. Landing order stays allowed as sequencing guidance, not a roster. Owner-approved (option A) fix for HS-FMRH-SVCZ-DIAG-parent-spec-prose-body-drifts-from-its-f — the write path patches the parent's frontmatter array but never re-syncs its body, so any prose roster/count silently drifts (HS-4PTZ-MVMD-MIL-mvp-mode-relax-review-commit-pre-launch body said "six" while its array held nine). |
514
- | 2026-06-07 | claude opus 4.7 [1m] · session `local` (with pwher) | **Added `## Memories` body-convention note** to §Frontmatter requirements per level → Task-specific. Tasks should include a `## Memories` section as the first body section (auto-populated by `POST /api/specs` when filed under an epic parent; hand-authored manually otherwise). Companion of the new step 1c in plan-ingest-agent-workflow and the runtime-injection enricher in HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio. The two surfaces together (creation-time embedding + runtime injection) cover each other's failure modes — see feedback_creation_time_memory_embedding_complements_runtime_recall. |
515
- | 2026-06-13 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Diagnose — the bug-fix lane"** plus a "What each level is for" row, an Agent-autonomy row ("File a new diagnose"), and a Claim-eligibility corollary row. A `diagnose` (`specs/diagnoses/`, `HS-DIAG-*`) is a user-reported bug fix as a sibling executable entity *outside* the milestone→epic→task chain: standalone-by-default (exempt from the epic→milestone parent rule), optional epic/milestone `parent:`, always its own leaf `ClaimTarget`, advising the scheduler via `severity:` + `blocks:` (resolution-advises-scheduler-via-severity-and-gating). Ratified by introduction lineage HS-OXBS. Per HS-AO2F-1KZK-RES-bug-fix-resolution-task-entity-model-and. |
516
- | 2026-06-20 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added the "parent milestone must still be accepting work" rule** to §Linkage rules (new paragraph after the bidirectional table) and a clause to the §Agent-autonomy "File a new epic under an existing milestone" row. An epic may only be filed under a milestone that is **not claim-terminal** (`open`/`in_progress`/`draft`/`parked`); filing under an `in_review`/`closed`/… milestone is hard-blocked at `POST /api/specs` / `heyspex_file_spec` with `400 parent_milestone_not_open`, plus a defense-in-depth throw in `createSpec` — back-linking the child would otherwise mutate a frozen parent's `epics:` array (closed-trackable-specs-are-historical-record-not-living-docs). Reuses `CLAIM_TERMINAL_STATUSES` (now exported from `spex-console/src/lib/scheduler/claim-eligibility.ts`) so claim-terminality and child-acceptance are one rule. Owner-approved fix for HS-05ND-2DMM-DIAG-epic-can-be-filed-under-a-non-open-in-re (from capture HS-R22Q-OBO9-CAP-filing-an-epic-under-a-milestone-that-is); scope is prevention-only (existing in-review milestones left as-is) and epic→milestone only. |
517
- | 2026-06-20 | claude opus 4.8 [1m] · session `local` (with pwher) | **Made `summary:` an authoring step of filing** — added "Author `summary:` at file time" to §Frontmatter requirements (after the base-shape line) and a clause to §Filing procedure step 4. A plain-language `summary:` is now written as part of creating every milestone/epic/task, not deferred to claim time (the `/hs-next` claim flow was the only checkpoint, so specs created-but-not-claimed — e.g. the HS-QYXP-PI91-MIL-capture-workflow-v3-fast-brain-offload-h family — slipped through bare). Authoring expectation only, **not** a blocking gate: refuse-to-commit-without-one stays rejected per HS-G7OW-Q3HK-EPIC-spec-frontmatter-summary-field-policy-an. Owner-requested. |
518
- | 2026-06-21 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Why-this-epic-exists rationale"** + an §Epic-specific body bullet. Every epic's `## Why this epic exists` section now carries four plain-language prompts (Problem-right-now, What-this-unlocks, Skip-cost, Confidence) so the scheduler "Why" can surface the *substantive* priority rationale, not just the mechanical tier/layer order. Rationale **strengthens as a spec rises** (progressive elaboration / Definition-of-Ready): Problem(+Unlocks) at filing, Skip-cost before the on-deck pin — enforced as a **soft pin nudge, never a hard gate**. No numeric scores / no `value`/`time_criticality` frontmatter (the scalar anti-pattern HS-141W-PRIO-RES-priority-and-scoring-systems-2026 rejected); `what_matters` (★/♥) stays the coarse value band and always shows in the Why (`buildWhyNow`). Mirrored in `canonical-frontmatter-templates` §epic + `BODY_SKELETONS.epic` (`spex-console/src/lib/vault.ts`). Research: HS-XMSD-W8OZ-RES-authoring-substantive-priority-rationale; mechanical layer it complements: HS-AVSG-7J97-RES-how-the-scheduler-explains-itself-determ. Owner-approved (soft-nudge option). |
519
- | 2026-06-22 | Cursor · Composer (with pwher) | §Filing procedure step 2: split ID allocation into planning (milestone/epic) vs execution (task at `/hs-next` only). Authoring-phase parity sweep with plan-ingest, skills, and agent contracts. |
520
- | 2026-06-24 | Claude Code · session `2a8cade3` (with pwher) | **Hardened §Linkage rules + §Filing procedure step 5** to make `heyspex_register_spec` a mandatory post-file step. `heyspex_file_spec` does NOT auto-wire `children[]` on the parent; the filed task is invisible in the UI until `heyspex_register_spec(spec_id, parent_id)` writes both sides. Removed the stale "planned, not shipped" note (both tools now ship). Incident: HS-8XZA-LJUP-DIAG-heyspex-file-spec-does-not-auto-wire-chi. |
521
- | 2026-06-24 | Cursor · local | **Added shared-suffix family note** under §The four levels (inherits epic suffix; bare `HS-TASK-<suffix>` is a family token; basename required). Cross-links vault-id-assignment-protocol §Shared-suffix families and bare-hs-task-suffix-is-a-family-token-af. (HS-M96C). |
522
- | 2026-06-24 | Cursor · local | Cross-linked internal-linking-protocol in §Related procedures + §Linkage rules (re-parent / restructure moves). Implements HS-CGJ6-XPGV-RES-internal-linking-and-wikilink-best-pract. |
523
- | 2026-06-25 | claude opus 4.8 [1m] · session `0b396d62` (with pwher) | **(A)** Collapsed §"Milestone sizing — epic cap" from the soft-8 / at-cap-9–10 / hard-10 ladder to a **flat hard cap of 9 epics**, owner-override only; rewrote the rationale to lead with the console Milestone-card **3×3 grid** (scheduler + chip-budget now secondary). **(B)** Added §"Research → milestone/epic filing — default to a new milestone": on the `heyspex_promote_research` path the default is a NEW milestone; attaching to an existing one is a high-bar exception (clear best-fit only). Cross-referenced from research-capture-protocol and plan-ingest-agent-workflow. Both implemented directly off research HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t (prose-only, owner-approved). |
524
- | 2026-07-07 | Cursor | **Mandatory NEW-milestone default on research promotion** — expanded §"Research → milestone/epic filing" with decision table + `promote-research.mjs` enforcement (open-only attach; in_progress owner-gated via `attach_to_in_progress_milestone` + `attach_override_reason`). Cross-linked from research-capture-protocol. | Every level carries an intended-outcome section that **cascades by re-derivation, not copy-down**: state the outcome at that altitude, draw an explicit ↑ link to the parent outcome, and name the **mechanism of contribution** (a paste of the parent is "outcome theater"). The **task** outcome must be stated as **monitorable + controllable conditions that seed the `## Acceptance criteria`** — the KAOS leaf-operationalization rule, so acceptance criteria are *derived from* the outcome, not invented separately. Materializers: `promote-capture.mjs` / `promote-research.mjs` / `BODY_SKELETONS` (epic/task/milestone) in `spex-console/src/lib/vault.ts`. Grounded in HS-K4D4-2I29-RES-outcome-cascade-through-a-requirements-h (GORE/KAOS HOW-down/WHY-up + OKR translate-not-cascade). Companion edits: `research-capture-protocol`, `plan-ingest-agent-workflow`, `canonical-frontmatter-templates`. Owner-directed. |
525
- | 2026-07-04 | Claude Code (HS-I949-TASK) | Added a one-line pointer next to the grain-split empty-state note ("How the empty-state grain-split is surfaced"): breakdown must resolve inbound `amends:` first before decomposing an epic or milestone. See pre-execution-alignment-gate §"Resolve inbound amendments before decomposing" and supersession-protocol §Amends. |
526
- | 2026-07-07 | Cursor · Composer (with pwher) | **Added §Expected Breakage cascade** under §Expected Breakage & Consequences: milestone (plan ingest) → epic (planning) → task (`/hs-next` breakdown) allocates by altitude; task breakdown must read parent epic + milestone sections (plan-ingest-agent-workflow, pre-execution-alignment-gate; `alignment-gate.mjs` **Expected Breakage — parent context** block). Owner-directed. |
527
- | 2026-08-14 | Claude Code (Opus 5) · with pwher | **§Claim eligibility: added the "review backlog is never a claim blocker" callout.** `in_review` / `review_pending` are terminal-for-recursion because they are build-complete; an unreviewed queue never gates the next claim and never needs owner permission to work past. Mirrors [[session-close-protocol]] §MVP Mode variant item 5 and `mvp_mode.review_backlog_never_blocks`. Owner ruling. |
528
- | 2026-08-15 | Claude Code (Opus 5) · with pwher | **Added §Explain Like I'm 12 (required body section — milestone + epic)**, and added the section to the milestone + epic "Body must include" rows as the FIRST body section. Ten checkable writing properties observed from the approved example held verbatim in heyspex-north-star; milestone links there, epic does not; creation-time only (owner ruled **no backfill** of the ~3,400 existing specs); unfilled scaffold handled at breakdown like `## Expected Breakage & Consequences`, filing is not gated. Classification: template-shape. Owner ruling via HS-KJFZ-Z12C-CAP-every-milestone-and-every-epic-carries-a → HS-W5O3-K8AG-RES-explain-like-i-m-12-as-a-required. |
529
- | 2026-08-15 | Claude Code (Opus 5) · with pwher | **Added §Rule 0 — grounding outranks style**, after the owner caught a drafted section claiming "HeySpex pays a service called Firecrawl". Every one of the ten style properties passed that sentence and it is false. Several style rules actively push toward invention, so Rule 0 now outranks them: every sentence is a rewrite of something the source says, and money / ownership / scale are never inferred. Added §"Written by a machine?" — a grounding critic was built for this and DOES NOT WORK (both DeepSeek V4 Flash and Pro reproduced the same false claim through it), so unattended writing refuses the whole class instead. Revised §Backfill: the no-backfill ruling was widened the same day to open + in_progress milestones and epics, generated sections carry a "not written by the author" marker, and a spec that cannot clear the strict lint is skipped rather than written. |
530
- | 2026-08-22 | Claude Code · session `45051d11` (with pwher) | **§Diagnose rewritten: the leaf claim is the ONE-FIX case, not the only case.** The old bullet said a diagnose is *always* its own `ClaimTarget`, which left a bug needing several separate fixes with nowhere to put them. It now says a one-fix write-up is the claimable leaf and a multi-fix one goes through the breakdown stage (`heyspex_promote_diagnose` — the same stage as `heyspex_promote_research`, second input kind), with grounding before sizing, placement descending (epic gets tasks, milestone gets one epic, milestone minted last and owner-confirmed), and a resolvable milestone on everything spawned with no exemption. Two bullets added: **links, not parenting** (`spawned_epics:`/`spawned_tasks:` <-> `derived_from:`; the diagnose's own `parent:` keeps its bug-grouping job and is never touched, because an epic parented to a bug report does not render on the roadmap) and **closes on its reproduction, not on its children**. The claim-eligibility corollary row split in two: a not-broken-down diagnose is the claimable leaf; one that spawned work returns `rollup_state: "tracked_by_work"` and is not claimable. Per HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose. |
1
+ ---
2
+ id: HS-PROTO-spec-hierarchy
3
+ name: Spec hierarchy protocol — Milestone → Epic → Task → child tasks
4
+ type: protocol
5
+ status: draft
6
+ priority: 0
7
+ scope: vault
8
+ owner: TODO — your name/handle
9
+ created: '2026-05-27'
10
+ updated: '2026-07-04'
11
+ tags:
12
+ - vault
13
+ - specs
14
+ - hierarchy
15
+ - milestone
16
+ - epic
17
+ - task
18
+ - agent-autonomy
19
+ - foundational
20
+ triggers:
21
+ - file_milestone
22
+ - file_epic
23
+ - file_task
24
+ - protocol_edit
25
+ related:
26
+ - "[[entity-model]]"
27
+ - "[[session-close-protocol]]"
28
+ - "[[memory-protocol]]"
29
+ summary: |
30
+ Governs the Milestone to Epic to Task hierarchy: filing rules, how acceptance criteria propagate and freeze, the Expected Breakage section, sequencing language constraints, and the change-log review seed close-out steps.
31
+
32
+ ---
33
+
34
+ # Spec hierarchy protocol — Milestone → Epic → Task → child tasks
35
+
36
+ The **canonical hierarchy** of trackable work in HeySpex. Every piece of trackable work belongs at exactly one level of this hierarchy. Agents filing new work use this protocol to decide *which level* a new spec belongs at, *what frontmatter* it needs, and *how much autonomy* they have at each level.
37
+
38
+ This protocol exists because the hierarchy was previously implicit — codified in example specs (`HS-HMNJ-MIL`, `HS-84HT-EPIC`) and partially in `entity-model.md`, but never written as a single authoritative procedure an agent can read. Pattern-matching off example files leads to drift; this doc is what agents read **before** filing a milestone, epic, or task.
39
+
40
+ The Milestone entity type itself is **in-flight**: HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing (under HS-RM22-IART-MIL-console-ia-restructure) is formalizing the type/folder/validator. Until MILE lands, `type: milestone` and `HS-MIL-*` are recognized by the vault walker but not enforced by validators. The required-parent rule on Epics lands in HS-OFR5-M2J3-EPIC-introduce-milestones-as-highest-entity within HS-1JC8-HMNJ-MIL-status-and-roadmap-surface-refactor with a grace window. This protocol describes the **target** state.
41
+
42
+ ## The four levels
43
+
44
+ | Level | Folder | ID format | What it is | Status today |
45
+ |---|---|---|---|---|
46
+ | **Milestone** | `specs/milestones/` | `HS-MIL-<suffix>` | A coherent multi-epic ship. Closes when every child epic closes and the milestone's own acceptance gate is met. | Entity exists (4 filed); type formalization in HS-VK7C |
47
+ | **Epic** | `specs/epics/` | `HS-EPIC-<suffix>` | A bounded body of work, typically 1–10 tasks. Has one parent milestone (required after HS-OFR5) and N children tasks. | Stable |
48
+ | **Task** | `specs/tasks/` | `HS-TASK-<suffix>` | An atomic unit of agent-executable work. Has one parent epic. May or may not have child tasks (agent's call — see §Agent autonomy). | Stable |
49
+ | **Child task** | `specs/tasks/` | `HS-TASK-<suffix>` | A task with `parent:` pointing at another task (not an epic). Used when an agent fans out a task during execution. Same shape as a task; differentiated only by parent. | Stable |
50
+
51
+ Suffix format for all four: 4+ uppercase alphanumeric per vault-id-assignment-protocol. Legacy zero-padded epic IDs (`HS-0029-EPIC`) and 4-character mnemonic milestone IDs (`HS-HMNJ-MIL`) both remain valid.
52
+
53
+ **Shared-suffix families:** when a task is filed under an epic, it inherits the epic's 4-char suffix (e.g. epic `HS-D5YF-EPIC-…` → tasks `HS-TASK-D5YF-<slug>.md`). The slug keeps each filename unique; a bare `HS-TASK-<suffix>` alone is a **family token**, not a unique handle — wikilinks must use the full basename. Retroactive re-suffixing of existing tasks is out of scope. Full rules: vault-id-assignment-protocol §Shared-suffix families; decision: bare-hs-task-suffix-is-a-family-token-af.
54
+
55
+ ## What each level is *for*
56
+
57
+ | If the work… | …file it as |
58
+ |---|---|
59
+ | Spans multiple epics, has its own acceptance gate, and represents a coherent ship | Milestone |
60
+ | Is a single bounded ship — one outcome, 1–10 tasks, one DoD | Epic |
61
+ | Is one atomic unit of agent execution (under one context window, usually) | Task |
62
+ | Is sub-work discovered *during* a task that wasn't in the original task's scope but must complete before the parent task can close | Child task |
63
+ | Is a **user-reported bug fix** — executable, but not planned roadmap work that belongs under an epic | Diagnose (see §Diagnose below) |
64
+
65
+ **Anti-patterns** — these are not separate levels and should not be filed as such:
66
+ - A "phase" or "sprint" inside an epic → those are sections in the epic body or sub-tasks, not a new level.
67
+ - A "follow-up" that's discovered after a task closes → file as a new task under the same epic (not a child of the closed task).
68
+ - A "research" finding that needs implementation → research lives in `specs/research/`; the implementation work is filed as task(s) under an epic, with `discovered_from: [[<research>]]` linking back.
69
+
70
+ ### Diagnose — the bug-fix lane (a sibling executable entity, outside the milestone→epic→task chain)
71
+
72
+ A **`diagnose`** (`specs/diagnoses/`, `HS-DIAG-<suffix>`) is a user-reported bug fix as first-class executable work. It is **not** a fifth level of the planning hierarchy — it sits *beside* it:
73
+
74
+ - **Standalone by default.** A diagnose needs **no** parent. It is explicitly **exempt** from the epic→milestone mandatory-parent rule. An optional `parent:` (an `HS-EPIC-*` **or** `HS-MIL-*`) groups related fixes when more organization helps — but absence is the normal, valid state (the same "top-level" allowance [[entity-model]] grants tasks, made intentional and typed).
75
+ - **Claimable like a task — while it is a ONE-FIX bug.** A one-fix diagnose is its own `ClaimTarget` (level `"task"`-equivalent — a leaf), and that is the common path. It is not the only one: a write-up whose own `## Fix` section lists **several separately-verifiable fixes** goes through the **breakdown stage** instead (`heyspex_promote_diagnose` — the same stage `heyspex_promote_research` uses, taking a bug write-up as its second input kind, HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose). Grounding runs first (what open milestone/epic already covers this area?), then placement descends: a matching epic gets **tasks**, a matching milestone with no fitting epic gets **one new epic**, and only when neither exists is a milestone minted — that one asks the owner first. Every epic spawned this way carries a milestone that resolves on disk, with no exemption.
76
+ - **It LINKS to what it spawns; it never parents it.** The write-up gains `spawned_epics:` / `spawned_tasks:` and every spawned spec gains `derived_from:` pointing back — the same shape a research doc uses. An epic parented to a bug report would not render on the roadmap at all, so the `parent:` above keeps its own separate job of grouping several small bugs under one epic and the breakdown never touches it. Once a write-up has spawned work it leaves the on-deck queue and its Resolution row stops offering a claim, so one agent cannot claim the write-up while another works its tasks.
77
+ - **It closes on its reproduction, not on its children.** A broken-down write-up reaches `closed` only after its `## Reproduction` is re-run and no longer reproduces (`heyspex_verify_reproduction`); a `closed` flip without that verdict is refused, for a human as much as for an agent. The write-up is the record of the investigation, and the investigation is over when the bug is gone.
78
+ - **Surfaces on Resolution, advises the scheduler.** An open diagnose appears on the Resolution surface as a derived `kind: "bug"` row (the row is derived; the diagnose spec is the work). It advises the scheduler via `severity:` (a candidate competing for on-deck — channel B) and optional `blocks:` edges (gating a named epic — channel A), never via a rival queue (resolution-advises-scheduler-via-severity-and-gating). Full design: HS-AO2F-1KZK-RES-bug-fix-resolution-task-entity-model-and, milestone HS-OXBS.
79
+ - **Required body sections:** Symptom, Reproduction, Root cause, Fix, Acceptance criteria.
80
+
81
+ **Single-intent test — the deliverable, not the surface count.** When deciding whether work belongs at one level or splits horizontally, the "intent" for SRP-for-plans is the **deliverable** (the milestone's acceptance gate / the epic's DoD / the task's PR), **not** the surface count. A migration epic with N file-tasks is single-intent — the deliverable is "the migration is done." A bundled "platform refresh" milestone whose acceptance gate names three independently-acceptable ships is multi-intent — split it horizontally per §"Milestone sizing — epic cap" below. This is the plan-side counterpart of the doc-side rule in doc-canonicalization-protocol (per evergreen-doc-architecture-srp-and-seo-pillars); both protocols share one principle, applied at different surfaces.
82
+
83
+ **Closed specs are historical record — frozen + immutable.** Trackable plan-side specs (epics / tasks / milestones / reviews) are *living documents* while their status is `open` / `in_progress` / `in_review`. The moment they close (`status: closed`), the body is **frozen**: no further edits to scope, acceptance criteria, narrative, or `related:` links. The closed spec captures the plan exactly as it was at the moment of close — and that's its job from then on. The actual delivered behavior is the git history of the shipped change; the closed spec is the *plan that closed*, preserved as historical record. Full rule + the four implicit references it generalizes + what you CAN still touch on a closed spec (the close-out change-log row; `status:`; `close_reason:`; `superseded_by:`) + where new information goes instead (new task / new memory / supersession): closed-trackable-specs-are-historical-record-not-living-docs. The Shipped console surface that displays the archive: HS-ZUVY-S1A6-EPIC-shipped-surface-closure-event-archive-wi. The storage convention (archive = filter not folder; closed specs stay in their normal folders): [[entity-model]] §close_reason.
84
+
85
+ ### Sequencing language — disambiguate "after X"
86
+
87
+ When a task body or scope item references another spec as a precondition for some piece of work ("land after X," "after X closes," "this happens after X"), the wording must name *which signal* the agent should wait for. Pick exactly one:
88
+
89
+ - **"after X's code lands"** — wait until X's implementation is in the working tree, regardless of X's status. Use when the dependent work describes X's final code state (typically: comment rewrites, doc updates, edits that reference X's predicate signature / env var / new column).
90
+ - **"after X closes"** — wait until X's `status: closed` (the reviewer's commit). Use **only** when the trigger is genuinely the close-out event itself — e.g., audit-only timing, archive sweeps, "do this after the reviewer signs off because the next step depends on the sign-off."
91
+ - **Specific behavioral marker** — e.g., "after `isXyz()` returns Y," "after migration 0010 is applied," "after the cron has run once." Use when the trigger is concrete and observable from the code or operational state.
92
+
93
+ The bare **"after X"** is banned: in MVP mode where the reviewer is the owner and close-out is a formality, agents have read it as both "code lands" (and updated the dependent comments in the same session) and "status flips" (and over-deferred to a future session that wasn't needed). The two readings are not equivalent; the spec author must pick one.
94
+
95
+ **Reading legacy specs that use bare "after X":** default to "after X's code lands" — it's the cheaper read in MVP mode and matches the spec author's typical intent (they wanted the dependent work to describe X's final semantics, not to wait on the reviewer's clock). Treat the close-out reading as the exception, required only when the surrounding context makes the close-out signal explicit.
96
+
97
+ This rule generalizes the feedback_mvp_mode_after_x_closes_means_after_code_lands incident pattern. The matching enforcement at session close (the **Deferred items audit** subsection in the HS-REV template) catches over-deferral that slipped past this rule — see [[session-close-protocol]] §HS-REV companion template §`## Reviewer notes` §`### Deferred items audit`.
98
+
99
+ ### Milestone sizing — epic cap
100
+
101
+ A milestone groups epics into a single coherent ship. **A milestone holds at most 9 epics (hard cap).** Exceeding 9 requires the owner's explicit permission — there is no soft/at-cap band; 9 is the line.
102
+
103
+ - **Hard cap: 9 epics.** At or below 9 is fine. To add a 10th, either file a horizontal split (peer milestones at the same level — sub-milestones are explicitly out of scope, see §Open questions / in-flight below) or obtain the owner-override.
104
+ - **Owner-override exception.** A milestone with a single product thesis and shared infrastructure that genuinely cannot be cleaved without breaking the gate may exceed 9 with the owner's explicit permission. The exception is documented inline in the milestone body as a `## Exception — over-cap rationale` section naming the shared thesis, the shared infrastructure, and what would be lost on splitting. The owner's permission is recorded in the milestone's change log row.
105
+
106
+ **Cap rationale.** The primary reason is the console Milestone-card UI: epics render as a **3×3 grid (three rows of three)**, so 9 fills the card exactly and reads cleaner than 8 or 10. Two reinforcing reasons: the deterministic scheduler scopes its priority pass to the active milestone's epics (HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler), and the card's chip budget overflows beyond a full grid. Capacity lost by capping at 9 rather than 10 is one epic — negligible against a clean grid. Basis: HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t.
107
+
108
+ **Filing flow when a new epic would push a milestone over cap:**
109
+
110
+ 1. Check whether the new epic belongs to this milestone at all — often it's a separate concern that should land under a different (or new) milestone.
111
+ 2. If yes, check whether the milestone qualifies for the owner-override exception (single thesis + shared infra that splits would break). If unsure, surface to the owner.
112
+ 3. Otherwise, propose a horizontal split — daughter milestones at the same level. The parent flips to `status: parked` with `parked_reason: split-into-peers` and gains a `Split into [[daughter-A]] + [[daughter-B]] on YYYY-MM-DD` header in its §Status section.
113
+
114
+ ### Research → milestone/epic filing — default to a new milestone (mandatory)
115
+
116
+ When filing a milestone/epic set from a completed research doc (the `heyspex_promote_research` path), **the default is always to create a NEW milestone.** Omit `target_milestone` or pass `target_milestone: "NEW"`. Attaching spawned epics to an existing milestone is the **high-bar exception**, not the default — and agents must **not** recommend an existing milestone unless the research clearly extends that milestone's single coherent thesis and shared infrastructure.
117
+
118
+ **Decision rule (agents + tools):**
119
+
120
+ | Situation | Action |
121
+ |-----------|--------|
122
+ | Research promotion (default) | `target_milestone: "NEW"` + `new_milestone_name` |
123
+ | Research clearly extends one open milestone's thesis | May attach — target must be `status: open` only |
124
+ | Agent would cram into nearest plausible milestone | **Stop** — file NEW instead |
125
+ | Research spans multiple milestone theses | Split first (HS-TQQS-GTH9-TASK-hs-split-research-mcp-tool), then promote each child to NEW or its one open best-fit |
126
+
127
+ **Existing-milestone attach gate (enforced in `promote-research.mjs`):**
128
+
129
+ | Target milestone `status:` | Spawn epics allowed? |
130
+ |----------------------------|----------------------|
131
+ | `open` | Yes (still requires clear thesis fit — agent judgment) |
132
+ | `in_progress` | **Blocked by default.** Rare owner exception only: `attach_to_in_progress_milestone: true` + `attach_override_reason` (≥40 chars, audit trail) |
133
+ | `in_review`, `review_pending`, `closed`, `parked`, `deferred`, `hibernated`, … | **Blocked** — use NEW |
134
+
135
+ Still check existing milestones first — but as a high-bar gate, not an eager search. Attach to an existing milestone **only** when the research clearly extends that milestone's single thesis and shared infrastructure (best-fit is obvious, not merely that *a* fit exists). Absent that clear best-fit, file a new milestone. Filing a new milestone is a strategic decision — confirm it with the owner per the autonomy table below. Rationale: the common case is one research doc → one new milestone; cramming epics into the nearest plausible existing milestone is a helpful-but-wrong habit that over-stuffs milestones and blurs their thesis. Basis: HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t.
136
+
137
+ ## Frontmatter requirements per level
138
+
139
+ All four levels share the base shape from [[entity-model]]: `id`, `name`, `type`, `status`, `owner`, `created`, `updated`, `tags`, `related`.
140
+
141
+ **Author `summary:` at file time.** A plain-language `summary:` is written as part of the act of filing — the same step as giving the spec a name. **Planning phase (milestone + epic):** summary at plan ingest / research promotion / milestone breakdown — milestones and epics are filed in this wave only, never with tasks. **Execution phase (task):** summary only when filing from What's Next → Launch → `/hs-next` breakdown. Pass `summary` to `heyspex_file_spec` / include it in the `POST /api/specs` body. Full contract: [[spec-summary-authoring]].
142
+
143
+ **Milestone-specific** (`type: milestone`):
144
+ - `epics:` — array of `[[wikilinks]]` to every child epic (mirror of each child epic's `milestone:` pointer)
145
+ - `target_date:` — optional ISO date or empty string
146
+ - Body must include: `## Explain Like I'm 12` **as the first body section** (see below), a `## Intended outcome` re-derived from the source research's outcome (see §Intended outcome — re-derived at every level), rationale ("why this is a milestone, not loose epics"), acceptance gate (numbered closure conditions), suggested landing order (sequencing guidance, not a complete roster or a count), out-of-scope, and `## Expected Breakage & Consequences` (see below). The body must **not** re-list the child epics as a roster or state their count — see §Child rosters live in frontmatter, not prose.
147
+
148
+ **Epic-specific** (`type: epic`):
149
+ - `milestone:` — `[[wikilink]]` to parent milestone (will be **required** post-HS-OFR5; today recommended)
150
+ - `pillar:` — **required** `[[wikilink]]` to the perpetual `HS-PILR` theme this epic's learning belongs to. **Hard-blocked at filing**: `POST /api/specs` / `heyspex_file_spec` refuse an epic with no resolvable pillar (`400 pillar_required`). When none fits, author a net-new pillar first. Full rules: pillar-assignment-protocol.
151
+ - `children:` — array of `[[wikilinks]]` to every child task
152
+ - `kind:` — the scheduler **tier** vocabulary: `infra` | `foundation` | `feature` | `polish` (lower tiers rank first; read by the deterministic scheduler's tier stage per HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler). Unrecognized values sort last.
153
+ - `effort:` — Fibonacci 1/2/3/5/8/13
154
+ - `requires_user_testing:` — boolean
155
+ - `builder_profile:` — optional at plan ingest: `mechanical` | `architectural` | `mixed` — signals anticipated local builder class for breakdown (builder-fleet-capability-reference).
156
+ - Body must include: `## Explain Like I'm 12` **as the first body section** (see below), a `## Intended outcome` re-derived from the parent milestone's outcome (see §Intended outcome — re-derived at every level), scope, acceptance criteria, out-of-scope, and `## Expected Breakage & Consequences` (see below). Optional at plan ingest: **`## Builder fleet notes`** (cross-file blast radius, expected model/machine class, breakdown constraints — see builder-fleet-capability-reference). The body must **not** enumerate the child tasks as a roster or state their count — see §Child rosters live in frontmatter, not prose.
157
+ - Body must include `## Why this epic exists` — the **substantive priority rationale** the scheduler "Why" surfaces (the mechanical tier/layer order is computed; *why the work matters* must be authored). See §Why-this-epic-exists rationale below. Auto-scaffolded with prompts (`canonical-frontmatter-templates` §epic / `BODY_SKELETONS.epic`).
158
+
159
+ **Task-specific** (`type: task`):
160
+ - `parent:` — `[[wikilink]]` to parent epic (or to a parent task, for child-task case)
161
+ - `kind:` — same enum as epic
162
+ - `effort:` — same scale as epic
163
+ - `requires_user_testing:` — boolean
164
+ - `target:` — **required for dev-factory dispatch** — repo-relative path to the primary file the builder edits
165
+ - `depends_on:` — optional array of `[[wikilinks]]` to other tasks that must close first. Canonical blocker field per depends-on-canonical-blocker-field; `blocked_by` is deprecated but still tolerated by the resolver. For **phased epic children**, declare `depends_on: ['[[previous_phase_id]]']` on each non-first phase — the `/hs-next` resolver classifies blockers by predecessor status (`hard_blocked` for open/draft predecessors; `soft_review` with `start_batched` hint for in_review predecessors so epic-batch workflows aren't gated mid-flight).
166
+ - **Builder dispatch (optional frontmatter — recommended when filing from breakdown):** `suggested_model:`, `suggested_machine:`, `recommended_num_ctx:` (16384 | 32768 | 65536 | 131072), `estimated_context_tokens:`, `context_files:` (YAML list → builder `--extra`), `fit_assessment:` (`safe` | `tight` | `overflow`). Controller and `pick-num-ctx` honor these as preferences; routing-table fallback remains when a machine is busy. Full contract: builder-fleet-capability-reference.
167
+ - Body **should** include a `## Memories` section as the first body section (immediately under the H1, before `## Scope`) listing 3–5 wikilinks to memories relevant to the work. MCP-side `POST /api/specs` auto-populates this when the task's parent is an `epic` (scoring by `parent.tags ∪ task.tags`); hand-authored tasks add it manually per plan-ingest-agent-workflow §1c. Rationale + cross-link with the runtime injection on `/hs-next`: HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio, feedback_creation_time_memory_embedding_complements_runtime_recall.
168
+ - Body must include: **`## Builder brief`** (required at `/hs-next` task filing) — target path, explicit context/`--extra` file list sized to estimated tokens, `recommended_num_ctx`, suggested model/machine, and what **not** to include (defer to sibling tasks). Monitorable acceptance criteria the gate can check (tsc + vitest or embedded `--self-test`). See builder-fleet-capability-reference for the template.
169
+ - Body must include: a `## Intended outcome` re-derived from the parent epic's outcome and stated as **monitorable + controllable conditions that seed the acceptance criteria** (see §Intended outcome — re-derived at every level), scope, acceptance criteria, and `## Expected Breakage & Consequences` (see below).
170
+
171
+ **Child task** — identical to task, except `parent:` points at another task (not an epic). The grandparent epic is reachable by walking `parent:` twice. **Console roadmap rendering treats child tasks as belonging to the grandparent epic for column placement** (until HS-OFR5 adds grain-aware drilldown).
172
+
173
+ ## Intended outcome — re-derived at every level (not copied)
174
+
175
+ Every level carries an intended-outcome section — `## Conclusion & intended outcome` on the research doc, `## Intended outcome` on milestone/epic/task. It **cascades by re-derivation, never copy-down** (grounded in HS-K4D4-2I29-RES-outcome-cascade-through-a-requirements-h: GORE/KAOS refine DOWN by asking HOW and justify UP by asking WHY; the OKR rule that copying a parent's wording down the hierarchy is the core failure mode — "the appearance of order while stripping out ownership" — whose cure is *translation*).
176
+
177
+ At each level the section must (a) state the outcome **at that altitude** (the milestone/epic/task's own contribution, not the parent's words), (b) draw an explicit **↑ link** to the parent outcome it moves, and (c) name the **mechanism of contribution** — *how* it moves the parent. A child outcome that reads like a paste of its parent is **outcome theater**; rewrite it.
178
+
179
+ **The task level is the verification bridge.** A leaf (task) outcome must be expressed as conditions an agent can **monitor and control** — and those conditions are exactly what the task's `## Acceptance criteria` are, each traceable ↑ to the epic outcome. This is the KAOS leaf-operationalization rule: refinement terminates only when a goal is realizable and testable. So the acceptance criteria are *derived from* the intended outcome, not invented separately.
180
+
181
+ The materializers inject these sections automatically (`promote-capture.mjs` research stub; `promote-research.mjs` milestone + epic; `BODY_SKELETONS` in `spex-console/src/lib/vault.ts` for epic/task/milestone). Hand-authored specs add them per [[canonical-frontmatter-templates]].
182
+
183
+ ## Child rosters live in frontmatter, not prose (single source of truth)
184
+
185
+ A parent's child set is owned by its frontmatter array — `epics:` on a milestone, `children:` on an epic — which the filing tool maintains automatically and the console renders live. **The prose body must not re-list the full child set or state a derived count.** Concretely, do not write a "## The N epics" roster table, a "the six child epics" / "all six child epics" phrasing, or any list framed as the complete inventory of children.
186
+
187
+ Prose may name specific children by `[[wikilink]]` where it serves the narrative — rationale, dependencies, and the suggested landing order are all fine, because they reference children, not enumerate the roster. But they must never claim to be exhaustive and must never hard-code a count. The reason is mechanical: when a child is attached, only the frontmatter array is updated — nothing re-syncs the body — so any prose roster or count silently goes stale (the HS-4PTZ-MVMD-MIL-mvp-mode-relax-review-commit-pre-launch body said "six" while its array held nine). Full diagnosis: HS-FMRH-SVCZ-DIAG-parent-spec-prose-body-drifts-from-its-f.
188
+
189
+ ## Explain Like I'm 12 (required body section — milestone + epic)
190
+
191
+ > **The premise:** every other section on a milestone or epic is written for someone who already knows this project. `summary:` compresses the WORK; it does not explain what the work is FOR. So a reader arriving cold — a fresh agent on an empty context window, a customer opening their own vault, the owner returning months later — has nowhere to land. This section is that landing place, and it sits **first in the body** so it is read before scope, criteria, or ids.
192
+
193
+ Owner ruling 2026-08-15 (HS-KJFZ-Z12C-CAP-every-milestone-and-every-epic-carries-a → HS-W5O3-K8AG-RES-explain-like-i-m-12-as-a-required), from a request to explain the project "like I am 12, no metaphors". The ban on metaphor is the active ingredient: metaphor is the escape hatch a writer takes when they do not want to commit to what a thing literally is, and removing it forces every sentence to name the real object.
194
+
195
+ **Where it applies:** `type: milestone` and `type: epic`. **Not** tasks — a task is already a single concrete piece of work and its `## Builder brief` serves the same reader.
196
+
197
+ **Auto-scaffolded at creation.** `spex-console/src/lib/vault.ts` `createSpec` injects the heading, an altitude-appropriate prompt, and the ten properties below as an authoring comment — env-gated by `HEYSPEX_AUTO_ELI12_EMBED`, positioned first in the body. Same dual treatment as `## Memories` and `## Expected Breakage & Consequences`: creation-time embed, author fills it.
198
+
199
+ **Milestone links, epic does not.** A milestone's section links to heyspex-north-star §Explain Like I'm 12 for what the whole product is, and covers only the parts of the product that bear on *this* milestone. Owner instruction: "we don't need HeySpex to be re-explained every time. But the important points of HeySpex that are relevant can be helpful." An epic explains its own chunk and carries **no** such link — the parent milestone already covers the wider picture.
200
+
201
+ **Never a paraphrase of `summary:`.** The two do different jobs ([[spec-summary-authoring]]). If the summary can be pasted in here, the section is wrong.
202
+
203
+ ### Rule 0 — grounding outranks style (read this before the ten)
204
+
205
+ **Every sentence must be a rewrite of something the source already says.** The source is this spec's own body, its parent, and heyspex-north-star. Nothing else — not the writer's background knowledge, and not what is generally true of products like this one. If you cannot point at the sentence a claim came from, delete the claim.
206
+
207
+ **This rule outranks all ten below.** When plain language and accuracy pull against each other, accuracy wins and the sentence gets longer.
208
+
209
+ The ten style rules push toward invention. Read them knowing that:
210
+
211
+ - **"Name the thing it replaces" demands a replaced thing.** If the source does not name one, there is no such sentence to write. Do not supply one.
212
+ - **"Plain words over precise ones" holds only when the plain word means the same thing.** Simplifying a fact into a false one is the exact failure this rule exists to stop.
213
+ - **"End on the consequence" rewards a punchy closing claim**, and cost and speed are the punchiest. They are also the easiest to get wrong.
214
+ - **Stripping ids, tool names, and file paths removes the words a reader would use to check you.** Plain language is lossy on purpose, so the grounding has to be deliberate.
215
+
216
+ **Three kinds of fact are never inferred, only copied:**
217
+
218
+ 1. **Money** — who pays, what it costs, what is free, what is bundled.
219
+ 2. **Ownership** — who runs it, who built it, whether it is open source, whose hardware it is on.
220
+ 3. **Scale** — how many, how fast, how big, how often.
221
+
222
+ Naming an outside product carries **the relationship the source states** — uses it, replaces it, self-hosts it, pays for it. Never read the relationship off the product's name.
223
+
224
+ **Worked failure — 2026-08-15.** A draft section for the SpexCrawl milestone opened: *"Today HeySpex pays a service called Firecrawl to do that."* Every one of the ten style rules is satisfied by that sentence. It is false. The thing being reproduced is open-source Firecrawl, self-hosted on the owner's own hardware at no cost, and a self-hosted deployment never calls the company at all (spexcrawl-parity-target-is-self-hosted). The word "paying" did appear in the source, in a different sentence about a different thing, and the pull toward plain words moved it onto the wrong subject. A style rule with no grounding rule is a machine for producing well-written falsehoods, at whatever rate you run it.
225
+
226
+ ### Written by a machine? Then money, ownership, and scale are off the table
227
+
228
+ A human author writing this section gets Rule 0 and is trusted with it. **Anything writing the section unattended does not get that trust**, because the failure is not decidable by a checker.
229
+
230
+ This was tested, not assumed. A grounding critic was built for exactly this job: it reads the source and each claim, and must quote the sentence that supports it. On 2026-08-15 it was run against both DeepSeek V4 Flash and DeepSeek V4 Pro drafting the SpexCrawl milestone. **Both models wrote that HeySpex pays for page fetching, and the critic passed both** — because "paying to fetch pages in the cloud" does appear in the source, in a sentence about what the new design *avoids*. The critic matched the words and missed the subject, which is the same mistake it was hired to catch.
231
+
232
+ So for unattended writing the whole class is refused rather than checked. `lintEli12(..., { strict: true })` in `scripts/eli12-lint.mjs` makes any sentence touching money, ownership, or scale a hard error, and the generator is told the class is out of scope even when the source states it. A spec whose explanation genuinely needs such a claim fails the lint and goes to a human. That is the correct direction to fail.
233
+
234
+ The deterministic checks that back this up: spec ids, file paths, code formatting, em dashes, semicolons, exclamation points, figurative language, walls of text, the milestone/epic North Star link rule, **any product name absent from the source**, and **any run of six or more words lifted verbatim from the source**. That last one exists because hardening the grounding rule pushes a writer toward the safe failure — paraphrasing the spec sentence by sentence, which passes every truth check and teaches a reader nothing, since a reader who could follow the spec would not need this section.
235
+
236
+ ### The ten properties (write to these, not to "write it simply")
237
+
238
+ Observed from the approved example in heyspex-north-star, not invented from taste. Each one can be read off a draft and answered yes or no — which is the whole point. "Write it simply" is unfalsifiable, so it produces nothing and no reviewer can reject a violation. Same method as communication-style-protocol. **All ten are subordinate to Rule 0 above.**
239
+
240
+ 1. **No metaphors and no figurative language at all.** Say what the thing literally is.
241
+ 2. **One idea per paragraph**, two to four sentences. Never a wall of text.
242
+ 3. **Comparisons are to real named products, not images.** "a website like Jira or Linear" is useful. "like a train station" is not.
243
+ 4. **Plain words over precise ones** where they carry the same meaning.
244
+ 5. **Say what it is FOR before what it IS.** Open with the purpose.
245
+ 6. **Name the thing it replaces**, when it replaces something.
246
+ 7. **Spaced hyphens.** No em dashes, no semicolons, no exclamation points, no hype.
247
+ 8. **No spec ids, no tool names, no file paths, no jargon.** Not one appears in the approved text.
248
+ 9. **End on the consequence**, not a summary of what was just said.
249
+ 10. **Assume the reader knows nothing about this project** — but do not re-teach the whole product. Only the parts that bear on THIS work.
250
+ 11. **Name the role, never use a pronoun for it.** No "you", "your", "me", "my", "we", or "our". Owner ruling 2026-08-15: a spec outlives the conversation it was written in, so "me" ends up pointing at nobody and "you" changes meaning with whoever opens the file. Use the roles from glossary §Roles, written in plain words rather than the capitalized term — **the developer** (glossary: Builder), **the reviewer**, **the agent**, and where the two kinds matter, the agent working in a window versus the agent running overnight. Where a role has no stated pronoun, use they/them.
251
+
252
+ The approved example itself is held **verbatim** in heyspex-north-star and marked do-not-paraphrase. Quote it where it is reused; never re-voice it.
253
+
254
+ ### Backfill — open and in-progress only
255
+
256
+ **Owner ruling 2026-08-15 (revised the same day).** The first ruling was no backfill at all. It was then widened: **milestones and epics at `open` or `in_progress` are backfilled; everything else is not.** Closed, superseded, cancelled, parked, and review-stage specs stay as they are — an explanation added to work nobody will pick up again is cost with no reader.
257
+
258
+ A backfilled section is **not** author-written, and the file does not pretend otherwise. Every one carries an HTML-comment marker naming it as generated and asking the next person who works the spec to rewrite it. The original objection still stands — the author who understood the work is the one who should explain it — so the marker keeps that visible rather than papering over it.
259
+
260
+ Sweep: `node scripts/eli12-backfill.mjs --run`. It refuses to write on a guess. Every draft must clear the strict lint and, when a spec cannot, it is **skipped and listed** rather than written. Nothing is written silently. The ledger at `.heyspex/eli12-backfill.jsonl` makes a re-run resume instead of redoing work.
261
+
262
+ ### Unfilled scaffold at breakdown
263
+
264
+ Same handling as `## Expected Breakage & Consequences` below: an unfilled scaffold is acceptable at hand-off but is a signal the reviewer should question. At **task breakdown** the epic's section is the plain-language grounding for each task's `summary:` — the ten properties above govern that field too. When the section is still the scaffold, the judge is told so explicitly and must **not** read the authoring comment as epic content (`dev-factory/engine/breakdown.mjs` `eli12GroundingBlock`). Filing is **not** gated on the section being filled.
265
+
266
+ **Classification:** template-shape (heyspex-platform-vault-doubles-as-customer-template-source) — ships to every customer vault, content differs per vault, and each vault links to its own North Star.
267
+
268
+ ## Expected Breakage & Consequences (required body section)
269
+
270
+ > **The premise:** to make something better you usually have to break it first. Implementing real work *raises* the error/failure count before it settles — adding one `surface id` legitimately took a tree from 10 → 17 tsc errors because `layout-panes.ts` needed a resolution entry and a test fixture was now missing a field. Those were **expected consequences**, not regressions. This section makes that anticipation explicit so the executing agent and the reviewer read a transient rise as normal, and so the cascade is enumerated *before* it is hit.
271
+
272
+ Every milestone, epic, and task carries a `## Expected Breakage & Consequences` section. It is **auto-scaffolded at creation** (`spex-console/src/lib/vault.ts` `createSpec`, env-gated by `HEYSPEX_AUTO_BREAKAGE_EMBED`, placed below `## Memories`), **surfaced in the `/hs-next` execution brief** (above NEXT STEP, via `enrichOnDeckWithExpectedBreakage` in `vendor/heyspex-mcp/cli.mjs`), and **reconciled at close** (the HS-REV `### Breakage reconciliation` table — see [[session-close-protocol]]). It mirrors the dual-layer (creation-time embed + runtime injection) treatment of `## Memories` per HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio.
273
+
274
+ The section holds **two distinct halves** — keep them separate, because a non-breaking side-effect is not the same as breakage:
275
+
276
+ 1. **Breakage** — things that will *error or fail* until fixed in-scope (type/contract consumers, test fixtures/snapshots, call sites). Pair this with a **Baseline → Exit** contract: the tsc/test error count before work starts, and the exit condition (every net-new error resolved before flipping to `in_review`, back to ≤ baseline).
277
+ 2. **Consequences / side-effects** — intended ripples that change behavior but do **not** break (a removed field, a changed default, a downstream behavior shift). These are real and worth recording even though nothing "fails."
278
+
279
+ **Altitude-appropriate content:**
280
+
281
+ - **Task** — the concrete cascade: name the specific files/fixtures/call-sites, plus the baseline→exit error contract.
282
+ - **Epic** — higher-altitude blast radius: which subsystems the epic's changes ripple into, so the task breakdown can pre-allocate fix-up scope. Exit = no net-new errors at epic DoD.
283
+ - **Milestone** — which cross-epic seams may break; lighter, prose. Exit = no net-new errors across the milestone's epics at the acceptance gate.
284
+
285
+ The scaffold ships with placeholder prompts; the author replaces them (it is **not** auto-derived). A future codebase-graph blast-radius (HS-AR82-8BAA-TASK-blast-radius-k-hops-codebase-graph, HS-M8GC-9U4U-TASK-pr-impact-blast-radius-backlog) can later pre-fill the Breakage half. An unfilled scaffold is acceptable at hand-off but is a signal the reviewer should question.
286
+
287
+ **Expected Breakage cascade (planning → breakdown → execution).** The section **cascades by allocation, not copy-down** — same re-derivation principle as :
288
+
289
+ | Phase | When authored | Altitude | Allocates from |
290
+ |---|---|---|---|
291
+ | **Milestone** | Plan ingest / research promotion (plan-ingest-agent-workflow) | Cross-epic seams and integration points that may break as the milestone lands | Research scope + milestone thesis |
292
+ | **Epic** | Epic filing under the milestone (planning phase) | Subsystems/files this epic's changes ripple into | Parent milestone's breakage (where relevant) |
293
+ | **Task** | `/hs-next` breakdown only (pre-execution-alignment-gate) | Concrete files/fixtures/call sites + baseline→exit contract | Parent epic's blast radius **and** milestone seams surfaced in the decomposition brief |
294
+
295
+ At **task breakdown**, read the parent epic's and milestone's `## Expected Breakage & Consequences` sections first (the `/hs-next` decomposition brief surfaces them verbatim under **Expected Breakage — parent context**). Each task **pre-allocates fix-up scope** from those parent sections — drill down to specifics; do not paste parent prose or invent breakage in isolation. If a parent section is still the unfilled scaffold, fill it before decomposing (or note explicitly in each task why breakage is none).
296
+
297
+ Task-level shape:
298
+
299
+ ```markdown
300
+ ## Expected Breakage & Consequences
301
+
302
+ Implementing this will transiently raise the tsc/test error count — that's expected.
303
+ Enumerate the cascade so a rise reads as normal, not a regression.
304
+
305
+ **Breakage (will error/fail until fixed in-scope):**
306
+ - Type/contract consumers: `<files referencing the changed symbol>`
307
+ - Test fixtures/snapshots: `<state literals / snapshots needing the new field>`
308
+ - Call sites: `<…>`
309
+
310
+ **Consequences / side-effects (intended, non-breaking):**
311
+ - `<changed default / removed field / downstream behavior shift>`
312
+
313
+ **Baseline → Exit:** tsc = `<N>` errors / tests `<green | N failing>` before work.
314
+ Exit: every net-new error resolved before flipping to `in_review` (back to ≤ `<N>`).
315
+ ```
316
+
317
+ ## Why-this-epic-exists rationale (substantive priority, not mechanical order)
318
+
319
+ The deterministic scheduler explains **how** a spec ranked — tier, dependency layer, what-matters, criticality (the mechanical "Why"). It cannot explain the **substantive why**: *why this work matters and why it belongs at the top now*. That has to be **authored**. Without it, the "Why" is factual but thin. Research + framework grounding: HS-XMSD-W8OZ-RES-authoring-substantive-priority-rationale (Cost of Delay/WSJF, Shape Up appetite, RICE, Definition-of-Ready); the mechanical layer it complements: HS-AVSG-7J97-RES-how-the-scheduler-explains-itself-determ.
320
+
321
+ Every epic's `## Why this epic exists` section carries up to four **plain-language** prompts — no scores, no numeric fields (the scalar-priority anti-pattern HS-141W-PRIO-RES-priority-and-scoring-systems-2026 rejected):
322
+
323
+ - **Problem right now (required at filing):** one sentence — the specific problem this solves *and* why now (not next cycle). This is the qualitative minimum Cost of Delay: it names the problem and the time-criticality in one breath.
324
+ - **What this unlocks (required when `kind: infra` or `foundation`):** what future work shipping this makes possible. The authored complement to the scheduler's criticality stage (the graph counts downstream specs; the author says why they matter).
325
+ - **Skip cost (write before pinning this as on-deck):** "If we skip this cycle, we lose: …" — a *specific* consequence, not "it's important."
326
+ - **Confidence note (optional):** flag any part of the rationale where confidence is low (RICE's bias-brake; also keeps an agent from treating shaky rationale as ground truth).
327
+
328
+ **Rationale strengthens as a spec rises (progressive elaboration / Definition-of-Ready).** A spec far down the queue needs only the lightweight Problem (+ Unlocks). As it approaches the **top / on-deck pin**, it should also carry the **Skip cost** sentence — the closer to execution, the more specific the justification. This is enforced as a **soft nudge at the pin action** (the pin UI prompts for the skip-cost sentence), **never a hard block** — keep it progressive elaboration, not a phase-gate (HS-XMSD §Definition-of-Ready caveat). The `what_matters` (★/♥) flag remains the coarse value band and always appears in the "Why".
329
+
330
+ ## Linkage rules
331
+
332
+ Linkage is **bidirectional and explicit** — agents MUST update both sides when filing a new spec:
333
+
334
+ | Edge | Parent's field | Child's field |
335
+ |---|---|---|
336
+ | Milestone → Epic | `epics: ["[[HS-EPIC-…]]"]` | `milestone: "[[HS-MIL-…]]"` |
337
+ | Epic → Task | `children: ["[[HS-TASK-…]]"]` | `parent: "[[HS-EPIC-…]]"` |
338
+ | Task → Child task | `children: ["[[HS-TASK-…]]"]` | `parent: "[[HS-TASK-…]]"` |
339
+
340
+ **`heyspex_file_spec` does NOT wire the parent's `children[]` back-reference.** It creates the task file and sets the task's own `parent:` field, but the parent epic's `children:` array is a separate write. The signal is the response: if `parent` or `milestone` come back `null`, the back-reference was not written. Always call `heyspex_register_spec(spec_id, parent_id)` immediately after every `heyspex_file_spec` call — it writes both sides atomically and is idempotent (see §Filing procedure step 5).
341
+
342
+ After filing, run `heyspex_validate_linkage` (MCP) to confirm both sides are present and resolve to existing files. See plan-ingest-agent-workflow §Checklist. Manually confirm as a fallback: (a) the new file's `parent:` / `milestone:` resolves to an existing file, (b) the parent's `children:` / `epics:` array includes a wikilink to the new file, (c) no other file already claims this id.
343
+
344
+ **When moving an existing spec** (re-parenting an epic, changing milestone membership), filing rules above are not enough — consult internal-linking-protocol to leave a summary + wikilink stub at the old home so inbound links and lexical retrieval signal survive.
345
+
346
+ **The parent milestone must still be accepting work.** An epic may only be filed under a milestone that is **not claim-terminal** — i.e. `open` / `in_progress` / `draft` (and `parked`, which can legitimately gain a daughter epic during a split). Filing an epic under a milestone whose status is `in_review`, `closed`, or any other claim-terminal status is **hard-blocked** at `POST /api/specs` / `heyspex_file_spec` with `400 parent_milestone_not_open`, backed by a defense-in-depth throw in `createSpec` — because back-linking the child mutates that frozen parent's `epics:` array, violating closed-trackable-specs-are-historical-record-not-living-docs. The terminal set is the claim-eligibility SSOT (`CLAIM_TERMINAL_STATUSES` in `spex-console/src/lib/scheduler/claim-eligibility.ts`), so "cannot be claimed" and "cannot accept a child epic" stay one rule. To proceed, pick an open milestone (the error lists them) or reopen the target. Per HS-05ND-2DMM-DIAG-epic-can-be-filed-under-a-non-open-in-re.
347
+
348
+ ## Filing procedure
349
+
350
+ 1. **Decide the level** using the table in §What each level is for. If you're unsure, ask the user before filing.
351
+ 2. **Allocate the ID** per vault-id-assignment-protocol §Assignment procedure:
352
+ - **Planning (milestone/epic):** `allocateRandomVaultId()` or `POST /api/specs` / `heyspex_file_spec` (console auto-allocates).
353
+ - **Execution (task):** same tools, but only at the on-deck `/hs-next` breakdown — not during plan ingest.
354
+ - Milestones: hand-pick a 4-character uppercase alphanumeric mnemonic per vault-id-assignment-protocol; verify uniqueness by scanning `specs/milestones/` for existing `HS-MIL-*` IDs. File via `POST /api/specs` with `type: "milestone"` (shipped in HS-Y855-1AX3-TASK-recognize-milestone-in-console-type-regi / HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing — see `TYPE_TO_FOLDER` and milestone seed in `spex-console/src/lib/vault.ts`).
355
+ - Research / Captures: random ID per vault-id-assignment-protocol.
356
+ - Protocols: `HS-PROTO-<slug>` (deterministic from slug, not random).
357
+ 3. **Create the file** at the correct folder per the table in §The four levels.
358
+ 4. **Fill the frontmatter** per §Frontmatter requirements; in particular, set the **parent linkage** (`milestone:` for epic, `parent:` for task) and **author the `summary:`** as part of this step (see §Frontmatter requirements → "Author `summary:` at file time").
359
+ 5. **Register the spec into the parent** via `heyspex_register_spec(spec_id, parent_id)` — this writes both the task's `parent:` field AND the parent's `children:` (or `epics:`) array atomically and is idempotent. **Do NOT rely on `heyspex_file_spec` alone** — it creates the file but does not update `children[]` on the parent. Call `heyspex_register_spec` in the same turn as filing; when batching multiple tasks, run all registrations in parallel after the filing calls complete. The signal that a registration was missed: the `heyspex_file_spec` response returns `parent: null`.
360
+ 6. **Run `heyspex_validate_linkage`** to confirm no orphans (planned MCP tool — until it ships, follow the manual fallback in §Linkage rules).
361
+ 7. **Run `npm run validate:vault-ids`** (only required when hand-authoring task/epic files; console-created specs are validated by the API).
362
+ 8. **Commit** spec + manifest updates together. The pre-commit hook (`npm run hooks:install` once per clone) blocks duplicate IDs.
363
+
364
+ ## Agent autonomy boundaries
365
+
366
+ This is the part agents most often get wrong. The default is **ask first** for structural changes, **decide on your own** for execution-level fanout.
367
+
368
+ | Action | Agent autonomy |
369
+ |---|---|
370
+ | **File a new milestone** | **Ask the user.** Milestones are strategic decisions; do not file unilaterally. |
371
+ | **File a new epic under an existing milestone** | **Ask the user**, unless the epic clearly maps to a single audit/finding/incident already named by the user. When in doubt, ask. **File the epic with `children: []`** — do not fan out tasks in the same pass (see the next row). **The parent milestone must be open** (non-claim-terminal — `open`/`in_progress`/`draft`/`parked`); filing under an `in_review`/`closed` milestone is hard-blocked with `parent_milestone_not_open` (see §Linkage rules). |
372
+ | **Fan out tasks under a new epic** | **Never at epic-creation / planning time.** Tasks are created **just-in-time at execution**, when the epic becomes the on-deck `ClaimTarget` (via `/hs-next`) — that breakdown is the *first step of executing the epic*, not a planning activity. At doc-creation and during clarification, offer **milestone + epics only**. See the §empty-state grain-split note below and deterministic-priority-scheduler-supersedes-scored-priority. |
373
+ | **Add a child task during execution of a parent task** | **Decide autonomously.** This is the canonical place agents have authority — when executing a task uncovers sub-work, fan out into child tasks without asking. Link via `parent:` to the task you're executing. |
374
+ | **Reorder child tasks within a task** | **Decide autonomously.** Sequence and decomposition of child tasks is the agent's call. |
375
+ | **Add a `related:` link to another spec** | **Decide autonomously.** Cross-linking is low-risk. |
376
+ | **Change a spec's `parent:` or `milestone:` field** | **Ask the user.** Restructuring linkage changes the roadmap rendering and other agents' context. |
377
+ | **Change a spec's `status:` from anything to `in_review`** | **Decide autonomously** when acceptance criteria are met (per [[session-close-protocol]]). |
378
+ | **Change a spec's `status:` to `closed`** | **Never.** Only the reviewer (human) flips to `closed`. Agent terminal action is `in_review`. |
379
+ | **Create a memory doc** | **Decide autonomously** when a lesson/decision/UI pattern emerges (per CLAUDE.md §Automatic knowledge capture). |
380
+ | **Modify a protocol doc (this kind of file)** | **Ask the user.** Protocols are stable contracts; treat changes the same as ADR changes. |
381
+ | **File a new diagnose (standalone bug fix)** | **Decide autonomously** when a bug is reported and the symptom/scope is clear — standalone (no parent) is the default. **Ask the user** before giving it a `parent:` (epic/milestone) or a `blocks:` edge, since both change roadmap/on-deck rendering. |
382
+
383
+ **How the empty-state grain-split is surfaced (2026-06-01).** The two "no children yet" states are NOT the same severity, and the console signals them differently (HS-6A4P On Deck split, pre-execution-alignment-gate):
384
+
385
+ - An **epic with `children: []`** is the **normal first step of executing it** — tasks are created just-in-time, at the last possible moment, so they stay relevant and don't drift (deterministic-priority-scheduler-supersedes-scored-priority). Surfaced GREEN as "Next step: break into tasks" (a happy-path step, not a defect).
386
+ - A **milestone with `epics: []`** is **abnormal** in the agent-driven workflow (milestones + epics are normally filed together in one planning session) — surfaced as a YELLOW/RED "Needs breakdown" warning.
387
+
388
+ **Before either grain-split breakdown starts, resolve inbound `amends:` first.** If the epic (or its parent milestone) being broken down is an amends target, decompose to the amending doc's scope, not the stale original — see pre-execution-alignment-gate §"Resolve inbound amendments before decomposing" and supersession-protocol §Amends.
389
+
390
+ ## Claim eligibility & `ClaimTarget`
391
+
392
+ **The single eligibility rule:** *a spec is claimable iff it is its own current `ClaimTarget`*. The console claim endpoint (`POST /api/specs/:id/claim`) enforces this — any POST whose target is not the current `ClaimTarget` returns `409 claim_blocked_by_descendants` with the actual current target's id in the response payload (HS-B0Z7 of HS-VLB8). This is the **vault invariant** that backs the legibility surface in `/hs-next`: the brief always names what is actually claimable, and the claim never silently lands on the wrong level.
393
+
394
+ ### `ClaimTarget` type
395
+
396
+ `computeClaimTarget(spec, vaultRoot, alignmentState)` (`vendor/heyspex-mcp/blocker-resolver.mjs`; TypeScript mirror in `spex-console/src/lib/scheduler/claim-eligibility.ts`) returns a 7-field record:
397
+
398
+ | Field | Shape | Notes |
399
+ |---|---|---|
400
+ | `level` | `"task" \| "epic" \| "milestone"` | What grain the user is actually authorizing. |
401
+ | `spec_id` | `string` | The id the user will claim — **not necessarily the spec id `/hs-next` is currently looking at**. |
402
+ | `title` | `string` | Display title (frontmatter `name:`). |
403
+ | `intent_verb` | plain English | "implement it" / "break it into Tasks" / "break it into Epics" — see §Intent verbs below. |
404
+ | `tone_token` | `--status-neutral \| --status-ready \| --status-pending` | UI tone per HS-6A4P-MNBD-EPIC-milestone-needs-breakdown-awareness. |
405
+ | `hierarchy_breadcrumb` | `string[]` | Root → leaf id chain (e.g. `["HS-AGEX-MIL", "HS-VQZ8-EPIC", "HS-NJPH-TASK"]`). |
406
+ | `alignment_state` | `"fresh" \| "drift_detected" \| "unknown"` | Transparency only — the alignment gate is upstream; this field reports the gate's verdict on the brief, it does not re-decide it. |
407
+
408
+ ### The three corollary rules
409
+
410
+ | Spec kind | Resolution |
411
+ |---|---|
412
+ | **Task** | Always its own `ClaimTarget` (level `"task"`). |
413
+ | **Diagnose (not broken down)** | Its own `ClaimTarget` (a leaf, level `"task"`-equivalent, intent "implement it"). No parent to recurse from; directly claimable. The one-fix case, and the common one. |
414
+ | **Diagnose that spawned work** | NOT claimable — `rollup_state: "tracked_by_work"`, carrying `tracked_by` with the specs it spawned. Claim one of those instead. It is not terminal: the bug is still live, and it closes only when its reproduction stops reproducing (HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose). |
415
+ | **Epic with open children** | Recurses through `children:` in manifest order; returns the first non-terminal-status descendant. The Epic itself is NOT claimable while it has any open Task. |
416
+ | **Epic with no open children** (drained or fresh) | Its own `ClaimTarget` — intent depends on child rollup state (see §Rollup states below). |
417
+ | **Milestone with open epics** | Recurses through `epics:`; returns the first non-terminal descendant (which itself may recurse one more level). |
418
+ | **Milestone with no open epics** | Its own `ClaimTarget` — intent depends on child rollup state (see §Rollup states below). |
419
+
420
+ Terminal statuses that block recursion into a descendant: `closed`, `shipped`, `in_review`, `review_pending`, `archived`, `cancelled`, `duplicate`, `hibernated`, `superseded`.
421
+
422
+ > **A review backlog is never a claim blocker, and never needs owner permission to work past.** `in_review` / `review_pending` are *terminal for recursion* precisely because they are build-complete — the code is already in the tree. Specs sitting unreviewed are the expected steady state under a batched review grain (`mvp_mode.review_backlog_never_blocks: true`, owner ruling 2026-08-14), so the `ClaimTarget` is claimed on the normal handshake no matter how deep the review queue is. Never tell the owner to Pass/commit first, never offer to redirect the claim, never ask permission to proceed past pending reviews. Only a **real** dependency blocks — the new work needs code or a decision the other spec has not produced yet; name that dependency specifically and keep going. Full rule: [[session-close-protocol]] §MVP Mode variant item 5. Enforced in `blocker-resolver.mjs` (`STATUS_SEVERITY` → `soft_review`, never `hard`).
423
+
424
+ ### Rollup states (HS-Q7IG)
425
+
426
+ When every child is terminal (no `open` / `in_progress` descendant), `computeClaimTarget` classifies the parent instead of defaulting to "break it into Tasks/Epics":
427
+
428
+ | Child rollup | `rollup_state` | Intent verb | Meaning |
429
+ |---|---|---|---|
430
+ | No `children:` / `epics:` entries | *(absent)* or `no_children` | *break it into Tasks* / *break it into Epics* | Normal just-in-time breakdown — the happy path. |
431
+ | ≥1 child `in_review` / `review_pending`, rest terminal | `reviewer_blocked` | *wait for reviewer* | **Strict (task grain) only:** breakdown/implementation is done but each child still needs its own review before the parent can close. `/hs-next` must **not** offer to claim for breakdown. |
432
+ | Every child build-complete below the active review grain (`in_review` / `review_pending` / `closed`, no `open`/`in_progress`) | `ready_for_close` | *flip to in_review* | **MVP (milestone grain) and Standard (epic grain):** children at `in_review` below the grain are build-complete, not blockers — parent close-out (flip + HS-REV) is claimable. |
433
+ | Every child `closed` / cancelled / etc., none awaiting review | `ready_for_close` | *flip to in_review* | Children are fully terminal; agent close-out is the next write. |
434
+
435
+ ### Intent verbs (plain English, per user preference)
436
+
437
+ The on-deck brief uses **plain action verbs** rather than vault jargon ("decompose"). Per feedback_avoid_decompose_in_ux_copy:
438
+
439
+ | Level | Verb |
440
+ |---|---|
441
+ | Task | *implement it* |
442
+ | Epic with no children (fresh) | *break it into Tasks* |
443
+ | Epic with all children awaiting review | *wait for reviewer* | Strict (task grain): children must close before parent. |
444
+ | Epic with all children build-complete below review grain | *flip to in_review* | MVP / Standard: claim for parent close-out even when children are `in_review`. |
445
+ | Epic with all children closed (none in review) | *flip to in_review* |
446
+ | Milestone with no epics (fresh) | *break it into Epics* |
447
+ | Milestone with all epics awaiting review | *wait for reviewer* |
448
+ | Milestone with all epics closed (none in review) | *flip to in_review* |
449
+
450
+ System slugs (`uc-decompose-intent`, `extractDecompositionContext`, etc.) keep their identifiers — only the human-facing copy switches.
451
+
452
+ ### SSOT invariants
453
+
454
+ `computeClaimTarget` and `resolveHierarchy` read **only from disk** via the injected loader (`loadTask(vaultRoot, ref)` in production). Per HS-QA02 (the forensic-reconstruction incident), the breakdown decision must not consult a derived index — stale index data has previously triggered phantom `claim_blocked_by_descendants` rejects. Two invariants follow:
455
+
456
+ - **Structural truth from disk.** Children, parent, milestone, and status are read from the spec's frontmatter on the SSOT path (`specs/.../<basename>.md`) every call. No cohort, no scheduler cache, no Neon row.
457
+ - **Time truth from git.** Where temporal decisions are required (binary skip rule's `older_children` check, world-state delta), the timestamp comes from `git log -1 --format=%ct`, not from frontmatter `updated:`. See pre-execution-alignment-gate §Signature.
458
+
459
+ ### Relationship to the alignment gate
460
+
461
+ The alignment gate (upstream, `/hs-next` step 1) decides whether the response is `fresh` or `drift_detected` and renders the brief sections + 4-option menu when drift fires. `computeClaimTarget` (downstream, T2 of HS-VLB8) then renders the claim banner that names the target spec, the breadcrumb, and the intent verb. The two surfaces are independent:
462
+
463
+ - The alignment gate decides **whether to re-ground**.
464
+ - `ClaimTarget` decides **what to actually claim**.
465
+
466
+ A drift-detected brief still names a single claimable target; the user picks `[1] proceed` to record an override and claim, or `[4] defer` to close the brief.
467
+
468
+ ## Related procedures
469
+
470
+ | Procedure | What it covers |
471
+ |---|---|
472
+ | [[entity-model]] | Frontmatter schema, valid status values, valid types, wikilink resolution rules |
473
+ | vault-id-assignment-protocol | Random suffix allocation, ID collision handling |
474
+ | plan-ingest-agent-workflow | Creating specs from implementation plans, manifest registration, linkage validation |
475
+ | plan-ingest-review-roadmap-workflow | Same flow with a human review gate |
476
+ | roadmap-protocol | How the roadmap manifest is structured, what columns specs render in |
477
+ | [[session-close-protocol]] | The `in_review` → `closed` handoff between agent and reviewer |
478
+ | [[memory-protocol]] | When to file memories (lessons / decisions / references / UI patterns) |
479
+ | internal-linking-protocol | Preserve wikilinks + retrieval signal when re-parenting or restructuring trackable specs |
480
+ | mcp-slash-surface-protocol | MCP tools the agent uses to interact with the vault |
481
+
482
+ ## Discovery & enforcement
483
+
484
+ This protocol is only useful if agents **find it** when they need it. Today, discovery relies on agents:
485
+
486
+ 1. Reading CLAUDE.md at session start (which now links here).
487
+ 2. Recalling tag-filtered memories before relevant work (e.g. `tags: ["vault", "specs"]`).
488
+ 3. Pattern-matching against existing example specs — **the failure mode this protocol exists to replace**.
489
+
490
+ The robust solution — a procedure registry, an MCP `heyspex_get_procedure` tool, and pre-flight reminders — is planned in HS-M4J5-3QS8-EPIC-agent-procedure-discovery-enforcement-la. Until that lands, agents should:
491
+
492
+ - Read this protocol before filing any milestone, epic, or task they didn't claim via `/hs-next`.
493
+ - Re-read it whenever the user says something like "follow HeySpex procedures" — that's a signal a pattern-match failure occurred.
494
+
495
+ ## Open questions / in-flight
496
+
497
+ - **Required `milestone:` on Epics** — HS-OFR5 makes this required with a grace window. Until then, an epic without `milestone:` is tolerated.
498
+ - **Sub-milestones** — explicitly out of scope per HS-1JC8 §Out of scope. Large milestones should be split horizontally (more milestones at the same level), not nested. See §Milestone sizing — epic cap above for the soft/hard caps and the owner-override exception.
499
+ - **Cross-milestone task dependencies** — `depends_on:` can point across milestone boundaries today; the scheduler treats each milestone independently. Cross-milestone scheduling is a v2 concern.
500
+
501
+ ## Change log
502
+
503
+ | Date | Author | Change |
504
+ |---|---|---|
505
+ | 2026-05-27 | pwher (with claude opus 4.7) | Initial draft. Codifies the Milestone → Epic → Task → child task hierarchy that was previously implicit. Filed alongside the agent-procedure-discovery planning epic. Status `draft` until reviewed; flip to `open` after first cross-agent use confirms it answers the questions agents actually have. |
506
+ | 2026-05-28 | pwher (with claude opus 4.8 [1m]) | Conflict fix (Builder-approved protocol edit): `kind:` enum corrected from `feature\|bug\|spike\|story\|polish` to the scheduler tier vocabulary `infra\|foundation\|feature\|polish` actually read by HS-78E7-Y9OJ-EPIC-deterministic-priority-scheduler and used by every real epic (MILE=infra, M2J3/Y9OJ=foundation). Surfaced during the HS-SSOT-MIL conflict check. |
507
+ | 2026-05-30 | claude opus 4.7 [1m] · session `local` | Added `### Milestone sizing — epic cap` subsection under §What each level is *for*: soft cap 8, at-cap 9–10, hard cap 10, owner-override exception with inline `## Exception — over-cap rationale` requirement, and a filing flow for over-cap proposals. Codifies the previously informal cap surfaced at HS-QOAL-JN9H-TASK-manifest-schema-v3-and-horizon-retirement close-out (PFAG=9, CMCH=11, IART=11). Filed under HS-SP9M-MCAP-TASK-over-cap-milestone-splits-pfag-cmch-iart per HS-OFR5-M2J3-EPIC-introduce-milestones-as-highest-entity; owner permitted the three current over-cap milestones to remain unsplit under the new exception clause. |
508
+ | 2026-06-03 | claude opus 4.7 [1m] · session `local` (with pwher) | Added the **"Single-intent test — the deliverable, not the surface count"** clarification under §"What each level is *for*" §Anti-patterns. Per HS-UEF3-S1HX-RES-seo-style-information-architecture-for-t §Q9 findings: a migration epic with N file-tasks is single-intent; a bundled milestone whose acceptance gate names three independently-acceptable ships is multi-intent. Cross-links to doc-canonicalization-protocol + evergreen-doc-architecture-srp-and-seo-pillars so plan-side and doc-side SRP share one principle. One-sentence amendment — explicitly NOT a new plan-side protocol; SSOT for plan-side questions stays in this protocol. |
509
+ | 2026-06-03 | claude opus 4.7 [1m] · session `local` (with pwher) | Added the **"Closed specs are historical record — frozen + immutable"** clarification under §"What each level is *for*" §Anti-patterns. User flagged the principle as foundational and previously undocumented: *"Epics are stale once implemented — by design. Immediately locked and frozen and never updated again."* The principle existed implicitly across four references (supersession-protocol, this protocol's prior anti-pattern about post-close follow-ups, edit-protocol-in-place-with-version-preamble, spec-literal-is-intent-not-literal-update-on-rename); now named explicitly in the foundational memory-decision closed-trackable-specs-are-historical-record-not-living-docs under HS-PILR-pillars. Cross-links to HS-ZUVY-S1A6-EPIC-shipped-surface-closure-event-archive-wi (the console surface that displays the historical record) and to [[entity-model]] §close_reason (the storage convention: archive = filter not folder). |
510
+ | 2026-06-04 | claude opus 4.7 [1m] · session `local` (with pwher) | Reconciled stale "milestones cannot be created via `POST /api/specs`" directive in §Filing procedure (and removed the matching §Open questions / in-flight bullet) after verifying HS-VK7C-MILE-EPIC-milestone-entity-type-and-surfacing and HS-Y855-1AX3-TASK-recognize-milestone-in-console-type-regi both closed 2026-05-31 — `TYPE_TO_FOLDER` now has `milestone: "milestones"` and `createSpec()` handles `type: "milestone"` (`spex-console/src/lib/vault.ts:1263, 1605-1614`). The prior line cited a now-nonexistent `vault.ts:897`, a tell that no agent re-grepped after the implementation moved. Surfaced by the user flagging the directive as a memory candidate for review; rejected the memory because the corrected truth is derivable from current code. |
511
+ | 2026-06-05 | claude opus 4.7 [1m] · session `local` | **T5 added §Claim eligibility & `ClaimTarget`** (HS-VLB8 / HS-IYD8). Codifies the vault invariant *a spec is claimable iff it is its own current `ClaimTarget`*, the 7-field type, three corollary rules (Task / Epic-with-open-children / Epic-empty / Milestone-with-open-epics / Milestone-empty), plain-English intent verbs ("implement it" / "break it into Tasks" / "break it into Epics" — per feedback_avoid_decompose_in_ux_copy), tone tokens linking HS-6A4P, the two SSOT invariants (structural truth from disk; time truth from git), and the upstream/downstream relationship with pre-execution-alignment-gate. Implementation references: `vendor/heyspex-mcp/blocker-resolver.mjs` (HS-USOP), `spex-console/src/lib/scheduler/claim-eligibility.ts` (HS-B0Z7). Also upgraded `heyspex_preflight({ intent: "claim_spec" })` to return this protocol as `required`. |
512
+ | 2026-06-07 | claude opus 4.7 [1m] · session `local` (with pwher) | **Added §"Sequencing language — disambiguate 'after X'"** under §What each level is *for*. Bans bare "after X" wording in task bodies; requires one of three explicit phrasings ("after X's code lands" / "after X closes" / a behavioral marker). Sets the default-read rule for legacy bare-"after-X" specs to "after X's code lands" (cheapest read in MVP mode where close-out is a reviewer formality). Companion enforcement on the close side (the `### Deferred items audit` subsection in the HS-REV template): [[session-close-protocol]]. Driven by the feedback_mvp_mode_after_x_closes_means_after_code_lands incident during HS-7FOD where bare "after DV1T" in the task body led an agent to defer six in-tree edits to a future session for no real gate. |
513
+ | 2026-06-18 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Child rosters live in frontmatter, not prose (single source of truth)"** and amended the milestone/epic §Frontmatter-requirements body lines: a parent's prose body must **not** re-list the child set or hard-code a count (no "## The N epics" roster table, no "all six child epics" wording); the `epics:`/`children:` frontmatter array is the single source of truth, rendered live by the console. Landing order stays allowed as sequencing guidance, not a roster. Owner-approved (option A) fix for HS-FMRH-SVCZ-DIAG-parent-spec-prose-body-drifts-from-its-f — the write path patches the parent's frontmatter array but never re-syncs its body, so any prose roster/count silently drifts (HS-4PTZ-MVMD-MIL-mvp-mode-relax-review-commit-pre-launch body said "six" while its array held nine). |
514
+ | 2026-06-07 | claude opus 4.7 [1m] · session `local` (with pwher) | **Added `## Memories` body-convention note** to §Frontmatter requirements per level → Task-specific. Tasks should include a `## Memories` section as the first body section (auto-populated by `POST /api/specs` when filed under an epic parent; hand-authored manually otherwise). Companion of the new step 1c in plan-ingest-agent-workflow and the runtime-injection enricher in HS-U2M6-IMMF-TASK-memory-recall-hardening-runtime-injectio. The two surfaces together (creation-time embedding + runtime injection) cover each other's failure modes — see feedback_creation_time_memory_embedding_complements_runtime_recall. |
515
+ | 2026-06-13 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Diagnose — the bug-fix lane"** plus a "What each level is for" row, an Agent-autonomy row ("File a new diagnose"), and a Claim-eligibility corollary row. A `diagnose` (`specs/diagnoses/`, `HS-DIAG-*`) is a user-reported bug fix as a sibling executable entity *outside* the milestone→epic→task chain: standalone-by-default (exempt from the epic→milestone parent rule), optional epic/milestone `parent:`, always its own leaf `ClaimTarget`, advising the scheduler via `severity:` + `blocks:` (resolution-advises-scheduler-via-severity-and-gating). Ratified by introduction lineage HS-OXBS. Per HS-AO2F-1KZK-RES-bug-fix-resolution-task-entity-model-and. |
516
+ | 2026-06-20 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added the "parent milestone must still be accepting work" rule** to §Linkage rules (new paragraph after the bidirectional table) and a clause to the §Agent-autonomy "File a new epic under an existing milestone" row. An epic may only be filed under a milestone that is **not claim-terminal** (`open`/`in_progress`/`draft`/`parked`); filing under an `in_review`/`closed`/… milestone is hard-blocked at `POST /api/specs` / `heyspex_file_spec` with `400 parent_milestone_not_open`, plus a defense-in-depth throw in `createSpec` — back-linking the child would otherwise mutate a frozen parent's `epics:` array (closed-trackable-specs-are-historical-record-not-living-docs). Reuses `CLAIM_TERMINAL_STATUSES` (now exported from `spex-console/src/lib/scheduler/claim-eligibility.ts`) so claim-terminality and child-acceptance are one rule. Owner-approved fix for HS-05ND-2DMM-DIAG-epic-can-be-filed-under-a-non-open-in-re (from capture HS-R22Q-OBO9-CAP-filing-an-epic-under-a-milestone-that-is); scope is prevention-only (existing in-review milestones left as-is) and epic→milestone only. |
517
+ | 2026-06-20 | claude opus 4.8 [1m] · session `local` (with pwher) | **Made `summary:` an authoring step of filing** — added "Author `summary:` at file time" to §Frontmatter requirements (after the base-shape line) and a clause to §Filing procedure step 4. A plain-language `summary:` is now written as part of creating every milestone/epic/task, not deferred to claim time (the `/hs-next` claim flow was the only checkpoint, so specs created-but-not-claimed — e.g. the HS-QYXP-PI91-MIL-capture-workflow-v3-fast-brain-offload-h family — slipped through bare). Authoring expectation only, **not** a blocking gate: refuse-to-commit-without-one stays rejected per HS-G7OW-Q3HK-EPIC-spec-frontmatter-summary-field-policy-an. Owner-requested. |
518
+ | 2026-06-21 | claude opus 4.8 [1m] · session `local` (with pwher) | **Added §"Why-this-epic-exists rationale"** + an §Epic-specific body bullet. Every epic's `## Why this epic exists` section now carries four plain-language prompts (Problem-right-now, What-this-unlocks, Skip-cost, Confidence) so the scheduler "Why" can surface the *substantive* priority rationale, not just the mechanical tier/layer order. Rationale **strengthens as a spec rises** (progressive elaboration / Definition-of-Ready): Problem(+Unlocks) at filing, Skip-cost before the on-deck pin — enforced as a **soft pin nudge, never a hard gate**. No numeric scores / no `value`/`time_criticality` frontmatter (the scalar anti-pattern HS-141W-PRIO-RES-priority-and-scoring-systems-2026 rejected); `what_matters` (★/♥) stays the coarse value band and always shows in the Why (`buildWhyNow`). Mirrored in `canonical-frontmatter-templates` §epic + `BODY_SKELETONS.epic` (`spex-console/src/lib/vault.ts`). Research: HS-XMSD-W8OZ-RES-authoring-substantive-priority-rationale; mechanical layer it complements: HS-AVSG-7J97-RES-how-the-scheduler-explains-itself-determ. Owner-approved (soft-nudge option). |
519
+ | 2026-06-22 | Cursor · Composer (with pwher) | §Filing procedure step 2: split ID allocation into planning (milestone/epic) vs execution (task at `/hs-next` only). Authoring-phase parity sweep with plan-ingest, skills, and agent contracts. |
520
+ | 2026-06-24 | Claude Code · session `2a8cade3` (with pwher) | **Hardened §Linkage rules + §Filing procedure step 5** to make `heyspex_register_spec` a mandatory post-file step. `heyspex_file_spec` does NOT auto-wire `children[]` on the parent; the filed task is invisible in the UI until `heyspex_register_spec(spec_id, parent_id)` writes both sides. Removed the stale "planned, not shipped" note (both tools now ship). Incident: HS-8XZA-LJUP-DIAG-heyspex-file-spec-does-not-auto-wire-chi. |
521
+ | 2026-06-24 | Cursor · local | **Added shared-suffix family note** under §The four levels (inherits epic suffix; bare `HS-TASK-<suffix>` is a family token; basename required). Cross-links vault-id-assignment-protocol §Shared-suffix families and bare-hs-task-suffix-is-a-family-token-af. (HS-M96C). |
522
+ | 2026-06-24 | Cursor · local | Cross-linked internal-linking-protocol in §Related procedures + §Linkage rules (re-parent / restructure moves). Implements HS-CGJ6-XPGV-RES-internal-linking-and-wikilink-best-pract. |
523
+ | 2026-06-25 | claude opus 4.8 [1m] · session `0b396d62` (with pwher) | **(A)** Collapsed §"Milestone sizing — epic cap" from the soft-8 / at-cap-9–10 / hard-10 ladder to a **flat hard cap of 9 epics**, owner-override only; rewrote the rationale to lead with the console Milestone-card **3×3 grid** (scheduler + chip-budget now secondary). **(B)** Added §"Research → milestone/epic filing — default to a new milestone": on the `heyspex_promote_research` path the default is a NEW milestone; attaching to an existing one is a high-bar exception (clear best-fit only). Cross-referenced from research-capture-protocol and plan-ingest-agent-workflow. Both implemented directly off research HS-Q7X7-MDHQ-RES-flat-9-epic-milestone-cap-and-research-t (prose-only, owner-approved). |
524
+ | 2026-07-07 | Cursor | **Mandatory NEW-milestone default on research promotion** — expanded §"Research → milestone/epic filing" with decision table + `promote-research.mjs` enforcement (open-only attach; in_progress owner-gated via `attach_to_in_progress_milestone` + `attach_override_reason`). Cross-linked from research-capture-protocol. | Every level carries an intended-outcome section that **cascades by re-derivation, not copy-down**: state the outcome at that altitude, draw an explicit ↑ link to the parent outcome, and name the **mechanism of contribution** (a paste of the parent is "outcome theater"). The **task** outcome must be stated as **monitorable + controllable conditions that seed the `## Acceptance criteria`** — the KAOS leaf-operationalization rule, so acceptance criteria are *derived from* the outcome, not invented separately. Materializers: `promote-capture.mjs` / `promote-research.mjs` / `BODY_SKELETONS` (epic/task/milestone) in `spex-console/src/lib/vault.ts`. Grounded in HS-K4D4-2I29-RES-outcome-cascade-through-a-requirements-h (GORE/KAOS HOW-down/WHY-up + OKR translate-not-cascade). Companion edits: `research-capture-protocol`, `plan-ingest-agent-workflow`, `canonical-frontmatter-templates`. Owner-directed. |
525
+ | 2026-07-04 | Claude Code (HS-I949-TASK) | Added a one-line pointer next to the grain-split empty-state note ("How the empty-state grain-split is surfaced"): breakdown must resolve inbound `amends:` first before decomposing an epic or milestone. See pre-execution-alignment-gate §"Resolve inbound amendments before decomposing" and supersession-protocol §Amends. |
526
+ | 2026-07-07 | Cursor · Composer (with pwher) | **Added §Expected Breakage cascade** under §Expected Breakage & Consequences: milestone (plan ingest) → epic (planning) → task (`/hs-next` breakdown) allocates by altitude; task breakdown must read parent epic + milestone sections (plan-ingest-agent-workflow, pre-execution-alignment-gate; `alignment-gate.mjs` **Expected Breakage — parent context** block). Owner-directed. |
527
+ | 2026-08-14 | Claude Code (Opus 5) · with pwher | **§Claim eligibility: added the "review backlog is never a claim blocker" callout.** `in_review` / `review_pending` are terminal-for-recursion because they are build-complete; an unreviewed queue never gates the next claim and never needs owner permission to work past. Mirrors [[session-close-protocol]] §MVP Mode variant item 5 and `mvp_mode.review_backlog_never_blocks`. Owner ruling. |
528
+ | 2026-08-15 | Claude Code (Opus 5) · with pwher | **Added §Explain Like I'm 12 (required body section — milestone + epic)**, and added the section to the milestone + epic "Body must include" rows as the FIRST body section. Ten checkable writing properties observed from the approved example held verbatim in heyspex-north-star; milestone links there, epic does not; creation-time only (owner ruled **no backfill** of the ~3,400 existing specs); unfilled scaffold handled at breakdown like `## Expected Breakage & Consequences`, filing is not gated. Classification: template-shape. Owner ruling via HS-KJFZ-Z12C-CAP-every-milestone-and-every-epic-carries-a → HS-W5O3-K8AG-RES-explain-like-i-m-12-as-a-required. |
529
+ | 2026-08-15 | Claude Code (Opus 5) · with pwher | **Added §Rule 0 — grounding outranks style**, after the owner caught a drafted section claiming "HeySpex pays a service called Firecrawl". Every one of the ten style properties passed that sentence and it is false. Several style rules actively push toward invention, so Rule 0 now outranks them: every sentence is a rewrite of something the source says, and money / ownership / scale are never inferred. Added §"Written by a machine?" — a grounding critic was built for this and DOES NOT WORK (both DeepSeek V4 Flash and Pro reproduced the same false claim through it), so unattended writing refuses the whole class instead. Revised §Backfill: the no-backfill ruling was widened the same day to open + in_progress milestones and epics, generated sections carry a "not written by the author" marker, and a spec that cannot clear the strict lint is skipped rather than written. |
530
+ | 2026-08-22 | Claude Code · session `45051d11` (with pwher) | **§Diagnose rewritten: the leaf claim is the ONE-FIX case, not the only case.** The old bullet said a diagnose is *always* its own `ClaimTarget`, which left a bug needing several separate fixes with nowhere to put them. It now says a one-fix write-up is the claimable leaf and a multi-fix one goes through the breakdown stage (`heyspex_promote_diagnose` — the same stage as `heyspex_promote_research`, second input kind), with grounding before sizing, placement descending (epic gets tasks, milestone gets one epic, milestone minted last and owner-confirmed), and a resolvable milestone on everything spawned with no exemption. Two bullets added: **links, not parenting** (`spawned_epics:`/`spawned_tasks:` <-> `derived_from:`; the diagnose's own `parent:` keeps its bug-grouping job and is never touched, because an epic parented to a bug report does not render on the roadmap) and **closes on its reproduction, not on its children**. The claim-eligibility corollary row split in two: a not-broken-down diagnose is the claimable leaf; one that spawned work returns `rollup_state: "tracked_by_work"` and is not claimable. Per HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose. |