@codyswann/lisa 2.26.3 → 2.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/rules/leaf-only-lifecycle.md +94 -0
- package/plugins/lisa/skills/github-to-tracker/SKILL.md +6 -0
- package/plugins/lisa/skills/github-write-issue/SKILL.md +18 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/leaf-only-lifecycle.md +94 -0
- package/plugins/src/base/skills/github-to-tracker/SKILL.md +6 -0
- package/plugins/src/base/skills/github-write-issue/SKILL.md +18 -1
package/package.json
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"lodash": ">=4.18.1"
|
|
83
83
|
},
|
|
84
84
|
"name": "@codyswann/lisa",
|
|
85
|
-
"version": "2.
|
|
85
|
+
"version": "2.28.0",
|
|
86
86
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
87
87
|
"main": "dist/index.js",
|
|
88
88
|
"exports": {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Leaf-Only Build-Ready Invariant & Parent Status Rollup
|
|
2
|
+
|
|
3
|
+
This is the single vendor-neutral source of truth for two coupled lifecycle rules. Every `*-to-tracker`, `*-write-*`, `*-validate-*`, and `*-build-intake` skill cites this rule rather than restating it, so per-vendor logic does not drift.
|
|
4
|
+
|
|
5
|
+
1. **Leaf-only invariant** — only an independently implementable **leaf work unit** may carry the build-ready role. A parent/container with child work is never directly build-ready.
|
|
6
|
+
2. **Parent status rollup** — a parent/container's lifecycle state is *derived* from its children, never set independently.
|
|
7
|
+
|
|
8
|
+
The two are the same idea seen from opposite ends: a parent never enters the build queue as work; it only ever *reflects* the state of the leaves underneath it.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Build intake claims and implements whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent will try to implement the container itself — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
|
|
13
|
+
|
|
14
|
+
The fix is not vendor-specific. It belongs here, in a cross-vendor rule, and every writer / validator / intake path enforces it.
|
|
15
|
+
|
|
16
|
+
## Container vs. leaf taxonomy
|
|
17
|
+
|
|
18
|
+
A **leaf work unit** is an individually implementable item with **no child work** — issue types **Bug, Task, Sub-task, Improvement**. These are what an agent claims and implements. This is the same definition used by the `repo-scope-split` rule (a leaf work unit is also single-repo).
|
|
19
|
+
|
|
20
|
+
A **container** organizes other work and is never directly implemented:
|
|
21
|
+
|
|
22
|
+
| Class | Examples by type | May carry build-ready? |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **Leaf work unit** | Bug, Task, Sub-task, Improvement — with no children | **Yes** |
|
|
25
|
+
| **Container** | Epic, Story, Spike, or *any* item that has child work | **No** — state rolls up from children |
|
|
26
|
+
|
|
27
|
+
The classification is **structural, not nominal**: an item is a container if it has child work, regardless of its declared type. A "Task" that has acquired sub-tasks is a container for rollup purposes. The type label is a strong default (Epic/Story/Spike are containers by design), but the presence of children is decisive. See the childless-parent exception below for the converse case.
|
|
28
|
+
|
|
29
|
+
### How each vendor encodes hierarchy
|
|
30
|
+
|
|
31
|
+
The invariant is vendor-neutral; the mechanics of "has child work" differ. A skill resolves child membership using the native hierarchy first, falling back to text/metadata links where the vendor has no native parent/child:
|
|
32
|
+
|
|
33
|
+
- **GitHub Issues** — native **sub-issues** (parent ↔ child issue graph), plus task-list checkboxes and `Blocked by #<n>` / parent references in the body. Epic and Story are modeled as parent issues; their Sub-tasks are sub-issues.
|
|
34
|
+
- **JIRA** — native **Epic → Story → Sub-task** hierarchy: Epic link / parent field for Stories under an Epic, and the subtask relationship for Sub-tasks under a Story/Task. Issue links (`blocks` / `is blocked by`) express cross-item dependencies but are not parentage.
|
|
35
|
+
- **Linear** — **Project** (the Epic equivalent) groups **Issues** via `projectId`; an Issue groups **sub-issues** via `parentId`. Project state and Issue state are native. Relations (`save_issue_relation`) express dependencies, not parentage. (Initiatives are not used — see `config-resolution`.)
|
|
36
|
+
|
|
37
|
+
Where a vendor lacks native hierarchy for a given pair, a text link or metadata marker establishes the relationship (per PRD #522 non-goals: vendors need not expose identical native hierarchy features).
|
|
38
|
+
|
|
39
|
+
## Leaf-only invariant (the rule)
|
|
40
|
+
|
|
41
|
+
**Build-ready means a directly implementable leaf work unit.** Therefore:
|
|
42
|
+
|
|
43
|
+
- **At decomposition / write time** — when a PRD decomposes into a hierarchy, only the leaf work units receive the `ready` role (status/label). Parent containers (Epic, Story, Project, and any parent issue that has child work) are created in their normal non-ready state and never receive the build-ready role directly. The leaves are what downstream build intake will claim.
|
|
44
|
+
- **At validate time** — the `*-validate-*` gate FAILs any container carrying the build-ready role. This is the symmetric write-side guard: a stale or hand-applied build-ready role on a parent is a lifecycle error.
|
|
45
|
+
- **At claim time** — build intake scans for the `ready` role but claims **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not claimed**: intake either skips it or safely blocks it with a clear lifecycle-repair message (move the role to the leaves; roll the parent up). Intake never silently implements a container.
|
|
46
|
+
|
|
47
|
+
The permission boundary is the maintainer-applied build-ready role, not authorship — do not add author-based guards (PRD #522 non-goal). This rule narrows *what* may carry that role, not *who* may apply it.
|
|
48
|
+
|
|
49
|
+
## Childless-parent exception
|
|
50
|
+
|
|
51
|
+
A container *type* with **no child work** is, structurally, a leaf — and may be build-ready **iff its issue type is itself a valid leaf work unit**.
|
|
52
|
+
|
|
53
|
+
- A **Task** or **Bug** with no children → leaf → may be build-ready. (Many real tickets are flat Tasks with no sub-tasks; this rule must not strand them.)
|
|
54
|
+
- An **Epic, Story, or Spike** with no children → still **not** build-ready. These types are coordination containers by design; an empty one is an incomplete decomposition, not an implementable unit. The correct repair is to decompose it (add leaf children) or reclassify it to a leaf type — not to claim it.
|
|
55
|
+
|
|
56
|
+
So the exception is narrow: childlessness *enables* build-ready only for types that are leaf work units to begin with. It never promotes an Epic/Story/Spike to directly implementable.
|
|
57
|
+
|
|
58
|
+
## Parent status rollup (the state machine)
|
|
59
|
+
|
|
60
|
+
A parent/container never sets its own lifecycle state; it **derives** it from the roll-up of its children's states. Rollup is evaluated whenever a child transitions (or when intake observes the child set). Using the canonical build-lifecycle roles from `config-resolution` (`ready`, `claimed`, `review`, `blocked`, `done`):
|
|
61
|
+
|
|
62
|
+
Evaluate the children in priority order and take the **first** match:
|
|
63
|
+
|
|
64
|
+
| If among the required leaves… | …the parent rolls up to | Role |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| any leaf is **blocked** | blocked / attention-needed | `blocked` |
|
|
67
|
+
| else any leaf is **in progress** (claimed or in review) | active / in-progress | `claimed` |
|
|
68
|
+
| else **all** required leaves are **terminal** (`done`) | the configured rollup terminal state | `done` (or `review` where supported — see below) |
|
|
69
|
+
| else (leaves exist but none started) | unchanged (parent stays in its non-ready container state) | — |
|
|
70
|
+
|
|
71
|
+
Notes:
|
|
72
|
+
|
|
73
|
+
- **Blocked dominates.** A single blocked leaf surfaces blocked/attention on the parent even if other leaves are progressing, so a human sees the parent needs attention.
|
|
74
|
+
- **"Required" leaves.** Optional or won't-do children do not hold a parent open; only the leaves that must ship for the parent to be complete are counted toward the all-terminal check.
|
|
75
|
+
- **Rollup is recursive.** An Epic rolls up from its Stories, each of which rolls up from its own leaves. Evaluate bottom-up: a Story reaches `done` only when its leaves are all terminal; an Epic reaches `done` only when its Stories are all `done`.
|
|
76
|
+
- **Vendor support varies.** Apply the rollup state the vendor can express. Where a vendor has no native intermediate state, use the nearest configured role or a metadata/comment signal rather than forcing a non-existent status (PRD #522 non-goal: vendors need not expose identical states).
|
|
77
|
+
- **The parent never carries `ready`.** `ready` is a *human* "this is buildable, claim it" signal and only ever lives on leaves. Rollup moves a parent between non-ready container states (in-progress / blocked / terminal); it never sets the parent to `ready`.
|
|
78
|
+
|
|
79
|
+
### The rollup terminal state is the configured "done" — multi-env capable
|
|
80
|
+
|
|
81
|
+
The terminal rollup state is whatever the project configures for `done` — which is **env-keyed** (`config-resolution` "Env-keyed `done`"): a `done` map keyed by environment (`dev`, `staging`, `production`), resolved from the merged PR's base branch. This rule does **not** hardcode a `dev → staging → prod` promotion chain as required — that is a project-specific deploy topology. A downstream project with dev/staging/prod environments rolls a parent up to whichever terminal `done` value matches the environment its leaves shipped to. The rule stays generic and multi-env capable.
|
|
82
|
+
|
|
83
|
+
**Single-environment collapse (this repo).** Lisa's own deploy has only `main`/`production` (no dev/staging), so `done` is a single value, not a map, and the build lifecycle collapses to one chain: `ready → claimed (in-progress) → review (code-review) → done`. The rollup terminal state is simply `done`. This is the *collapsed* case of the generic rule, not a different rule — projects with more environments keep the env-keyed map.
|
|
84
|
+
|
|
85
|
+
## Citation
|
|
86
|
+
|
|
87
|
+
Skills that enforce this invariant or perform rollup cite this rule by slug (the `leaf-only-lifecycle` rule) instead of restating it:
|
|
88
|
+
|
|
89
|
+
- **Decomposition / write** (`*-to-tracker`, `*-write-*`) — apply the `ready` role to leaves only; never to containers.
|
|
90
|
+
- **Validate** (`*-validate-*`) — FAIL a container carrying the build-ready role; FAIL a childless Epic/Story/Spike marked build-ready.
|
|
91
|
+
- **Build intake** (`*-build-intake`, `tracker-build-intake`) — claim leaves only; skip or safe-block containers with stale build-ready roles.
|
|
92
|
+
- **Rollup** — derive parent state from children per the state machine above.
|
|
93
|
+
|
|
94
|
+
This is the inverse-direction companion to `repo-scope-split` (which governs a leaf's *repo* scope); together they define what a build-ready leaf work unit is: directly implementable, single-repo, childless-or-leaf-typed.
|
|
@@ -198,6 +198,8 @@ For each epic identified in Phase 1, **invoke the `lisa:tracker-write` shim** (d
|
|
|
198
198
|
- `artifacts`: the full Phase 1.5 artifact list — every artifact, regardless of domain. The Epic is the canonical hub.
|
|
199
199
|
- `priority`, `labels`, `components`, `fix_version`: as appropriate
|
|
200
200
|
|
|
201
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: an Epic is a container, not a leaf work unit. Do NOT mark it build-ready — `lisa:tracker-write` must not be passed `status:ready` for an Epic, and the Epic's lifecycle state rolls up from its children. The build-ready label is applied only in Phase 5.
|
|
202
|
+
|
|
201
203
|
Capture the returned epic ref — Phase 4 needs it as the parent for Stories.
|
|
202
204
|
|
|
203
205
|
### Phase 4: Create Stories
|
|
@@ -225,6 +227,8 @@ For each Story, **invoke `lisa:tracker-write`** with:
|
|
|
225
227
|
| Infrastructure | `ops`, `reference` |
|
|
226
228
|
| Mixed / setup ("X.0") | All domains |
|
|
227
229
|
|
|
230
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: a Story is a container (it has child Sub-tasks), not a leaf work unit. Do NOT mark it build-ready — never pass `status:ready` to `lisa:tracker-write` for a Story. Its lifecycle state rolls up from its Sub-tasks. The build-ready label is applied only in Phase 5.
|
|
231
|
+
|
|
228
232
|
Capture each returned story ref — Phase 5 needs it.
|
|
229
233
|
|
|
230
234
|
### Phase 5: Create Sub-tasks
|
|
@@ -237,6 +241,8 @@ Each sub-task MUST:
|
|
|
237
241
|
1. **Be scoped to exactly ONE repo** — indicated in brackets in the summary: `[repo-name]`.
|
|
238
242
|
2. **Include an Empirical Verification Plan** — real user-like verification, NOT unit tests, linting, or typechecking.
|
|
239
243
|
|
|
244
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: Sub-tasks are the **leaf work units** of the decomposition — they are the ONLY items in the hierarchy that receive the build-ready label. `lisa:tracker-write` applies `status:ready` here so downstream build intake (`lisa:github-build-intake`) claims the leaves and never the Epic or Stories. Apply `status:ready` to each Sub-task; never to its parent Story or Epic (Phases 3–4). `lisa:github-write-issue` enforces the same invariant on the write side, so a Sub-task split into per-repo children (the cross-repo case above) carries build-ready on the children, not on any intermediate parent that gains child work.
|
|
245
|
+
|
|
240
246
|
Sub-tasks inherit their parent Story's artifacts by reference (the parent link). Do not pass the same artifact list to every sub-task.
|
|
241
247
|
|
|
242
248
|
### Phase 5.5: Artifact Preservation Gate (mandatory)
|
|
@@ -206,6 +206,12 @@ Milestones map to `fix-version:<value>` labels OR native GitHub Milestones — p
|
|
|
206
206
|
|
|
207
207
|
Create labels lazily — call `gh label create` if a referenced label doesn't exist. Use a stable color palette per category so the issue board reads cleanly.
|
|
208
208
|
|
|
209
|
+
### Build-ready label is leaf-only
|
|
210
|
+
|
|
211
|
+
The build-ready status label (`status:ready`) is governed by the `leaf-only-lifecycle` rule. **Apply `status:ready` only when the issue is a leaf work unit** — an individually implementable issue type (`Bug`, `Task`, `Sub-task`, `Improvement`) that has **no child work**. A container (`Epic`, `Story`, `Spike`, or *any* issue that has sub-issues) is **never** written with `status:ready`; its lifecycle state rolls up from its children. The classification is structural: an item is a container if it has child work, regardless of its declared type (see the childless-parent exception in the rule). Do not hand-apply `status:ready` to a parent.
|
|
212
|
+
|
|
213
|
+
For non-build-ready issues created fresh (Epics, Stories, and other containers), omit the status label entirely; the container's rollup state is derived, not set directly.
|
|
214
|
+
|
|
209
215
|
## Phase 5.5 — Validate (Pre-write Gate)
|
|
210
216
|
|
|
211
217
|
Before any write, invoke `lisa:github-validate-issue` with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the `lisa:github-validate-issue` schema, including `runtime_behavior_change`, `authenticated_surface`, and `artifacts_attached` flags so the right gates run.
|
|
@@ -218,8 +224,9 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
|
|
|
218
224
|
|
|
219
225
|
### CREATE
|
|
220
226
|
|
|
221
|
-
1. Compose the body markdown from Phases 2/3/4 in a temp file (avoid quoting hell):
|
|
227
|
+
1. Compose the body markdown from Phases 2/3/4 in a temp file (avoid quoting hell). Apply `status:ready` **only for a leaf work unit** per the Phase 5 leaf-only rule (`leaf-only-lifecycle`) — omit it for `Epic` / `Story` / `Spike` and any issue that has child work:
|
|
222
228
|
```bash
|
|
229
|
+
# Leaf work unit (Bug / Task / Sub-task / Improvement with no children):
|
|
223
230
|
gh issue create \
|
|
224
231
|
--repo <org>/<repo> \
|
|
225
232
|
--title "<summary>" \
|
|
@@ -227,6 +234,16 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
|
|
|
227
234
|
--label "type:<type>" --label "status:ready" --label "priority:<priority>" \
|
|
228
235
|
[--label "component:<name>" ...] [--milestone "<milestone>"] \
|
|
229
236
|
[--assignee "<login>"]
|
|
237
|
+
|
|
238
|
+
# Container (Epic / Story / Spike / any issue with child work):
|
|
239
|
+
# identical, but WITHOUT --label "status:ready" — its state rolls up from children.
|
|
240
|
+
gh issue create \
|
|
241
|
+
--repo <org>/<repo> \
|
|
242
|
+
--title "<summary>" \
|
|
243
|
+
--body-file /tmp/issue-body.md \
|
|
244
|
+
--label "type:<type>" --label "priority:<priority>" \
|
|
245
|
+
[--label "component:<name>" ...] [--milestone "<milestone>"] \
|
|
246
|
+
[--assignee "<login>"]
|
|
230
247
|
```
|
|
231
248
|
2. Capture the returned issue number.
|
|
232
249
|
3. **Link to parent sub-issue** (if non-Epic): use the GraphQL sub-issue API.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Leaf-Only Build-Ready Invariant & Parent Status Rollup
|
|
2
|
+
|
|
3
|
+
This is the single vendor-neutral source of truth for two coupled lifecycle rules. Every `*-to-tracker`, `*-write-*`, `*-validate-*`, and `*-build-intake` skill cites this rule rather than restating it, so per-vendor logic does not drift.
|
|
4
|
+
|
|
5
|
+
1. **Leaf-only invariant** — only an independently implementable **leaf work unit** may carry the build-ready role. A parent/container with child work is never directly build-ready.
|
|
6
|
+
2. **Parent status rollup** — a parent/container's lifecycle state is *derived* from its children, never set independently.
|
|
7
|
+
|
|
8
|
+
The two are the same idea seen from opposite ends: a parent never enters the build queue as work; it only ever *reflects* the state of the leaves underneath it.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Build intake claims and implements whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent will try to implement the container itself — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
|
|
13
|
+
|
|
14
|
+
The fix is not vendor-specific. It belongs here, in a cross-vendor rule, and every writer / validator / intake path enforces it.
|
|
15
|
+
|
|
16
|
+
## Container vs. leaf taxonomy
|
|
17
|
+
|
|
18
|
+
A **leaf work unit** is an individually implementable item with **no child work** — issue types **Bug, Task, Sub-task, Improvement**. These are what an agent claims and implements. This is the same definition used by the `repo-scope-split` rule (a leaf work unit is also single-repo).
|
|
19
|
+
|
|
20
|
+
A **container** organizes other work and is never directly implemented:
|
|
21
|
+
|
|
22
|
+
| Class | Examples by type | May carry build-ready? |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **Leaf work unit** | Bug, Task, Sub-task, Improvement — with no children | **Yes** |
|
|
25
|
+
| **Container** | Epic, Story, Spike, or *any* item that has child work | **No** — state rolls up from children |
|
|
26
|
+
|
|
27
|
+
The classification is **structural, not nominal**: an item is a container if it has child work, regardless of its declared type. A "Task" that has acquired sub-tasks is a container for rollup purposes. The type label is a strong default (Epic/Story/Spike are containers by design), but the presence of children is decisive. See the childless-parent exception below for the converse case.
|
|
28
|
+
|
|
29
|
+
### How each vendor encodes hierarchy
|
|
30
|
+
|
|
31
|
+
The invariant is vendor-neutral; the mechanics of "has child work" differ. A skill resolves child membership using the native hierarchy first, falling back to text/metadata links where the vendor has no native parent/child:
|
|
32
|
+
|
|
33
|
+
- **GitHub Issues** — native **sub-issues** (parent ↔ child issue graph), plus task-list checkboxes and `Blocked by #<n>` / parent references in the body. Epic and Story are modeled as parent issues; their Sub-tasks are sub-issues.
|
|
34
|
+
- **JIRA** — native **Epic → Story → Sub-task** hierarchy: Epic link / parent field for Stories under an Epic, and the subtask relationship for Sub-tasks under a Story/Task. Issue links (`blocks` / `is blocked by`) express cross-item dependencies but are not parentage.
|
|
35
|
+
- **Linear** — **Project** (the Epic equivalent) groups **Issues** via `projectId`; an Issue groups **sub-issues** via `parentId`. Project state and Issue state are native. Relations (`save_issue_relation`) express dependencies, not parentage. (Initiatives are not used — see `config-resolution`.)
|
|
36
|
+
|
|
37
|
+
Where a vendor lacks native hierarchy for a given pair, a text link or metadata marker establishes the relationship (per PRD #522 non-goals: vendors need not expose identical native hierarchy features).
|
|
38
|
+
|
|
39
|
+
## Leaf-only invariant (the rule)
|
|
40
|
+
|
|
41
|
+
**Build-ready means a directly implementable leaf work unit.** Therefore:
|
|
42
|
+
|
|
43
|
+
- **At decomposition / write time** — when a PRD decomposes into a hierarchy, only the leaf work units receive the `ready` role (status/label). Parent containers (Epic, Story, Project, and any parent issue that has child work) are created in their normal non-ready state and never receive the build-ready role directly. The leaves are what downstream build intake will claim.
|
|
44
|
+
- **At validate time** — the `*-validate-*` gate FAILs any container carrying the build-ready role. This is the symmetric write-side guard: a stale or hand-applied build-ready role on a parent is a lifecycle error.
|
|
45
|
+
- **At claim time** — build intake scans for the `ready` role but claims **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not claimed**: intake either skips it or safely blocks it with a clear lifecycle-repair message (move the role to the leaves; roll the parent up). Intake never silently implements a container.
|
|
46
|
+
|
|
47
|
+
The permission boundary is the maintainer-applied build-ready role, not authorship — do not add author-based guards (PRD #522 non-goal). This rule narrows *what* may carry that role, not *who* may apply it.
|
|
48
|
+
|
|
49
|
+
## Childless-parent exception
|
|
50
|
+
|
|
51
|
+
A container *type* with **no child work** is, structurally, a leaf — and may be build-ready **iff its issue type is itself a valid leaf work unit**.
|
|
52
|
+
|
|
53
|
+
- A **Task** or **Bug** with no children → leaf → may be build-ready. (Many real tickets are flat Tasks with no sub-tasks; this rule must not strand them.)
|
|
54
|
+
- An **Epic, Story, or Spike** with no children → still **not** build-ready. These types are coordination containers by design; an empty one is an incomplete decomposition, not an implementable unit. The correct repair is to decompose it (add leaf children) or reclassify it to a leaf type — not to claim it.
|
|
55
|
+
|
|
56
|
+
So the exception is narrow: childlessness *enables* build-ready only for types that are leaf work units to begin with. It never promotes an Epic/Story/Spike to directly implementable.
|
|
57
|
+
|
|
58
|
+
## Parent status rollup (the state machine)
|
|
59
|
+
|
|
60
|
+
A parent/container never sets its own lifecycle state; it **derives** it from the roll-up of its children's states. Rollup is evaluated whenever a child transitions (or when intake observes the child set). Using the canonical build-lifecycle roles from `config-resolution` (`ready`, `claimed`, `review`, `blocked`, `done`):
|
|
61
|
+
|
|
62
|
+
Evaluate the children in priority order and take the **first** match:
|
|
63
|
+
|
|
64
|
+
| If among the required leaves… | …the parent rolls up to | Role |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| any leaf is **blocked** | blocked / attention-needed | `blocked` |
|
|
67
|
+
| else any leaf is **in progress** (claimed or in review) | active / in-progress | `claimed` |
|
|
68
|
+
| else **all** required leaves are **terminal** (`done`) | the configured rollup terminal state | `done` (or `review` where supported — see below) |
|
|
69
|
+
| else (leaves exist but none started) | unchanged (parent stays in its non-ready container state) | — |
|
|
70
|
+
|
|
71
|
+
Notes:
|
|
72
|
+
|
|
73
|
+
- **Blocked dominates.** A single blocked leaf surfaces blocked/attention on the parent even if other leaves are progressing, so a human sees the parent needs attention.
|
|
74
|
+
- **"Required" leaves.** Optional or won't-do children do not hold a parent open; only the leaves that must ship for the parent to be complete are counted toward the all-terminal check.
|
|
75
|
+
- **Rollup is recursive.** An Epic rolls up from its Stories, each of which rolls up from its own leaves. Evaluate bottom-up: a Story reaches `done` only when its leaves are all terminal; an Epic reaches `done` only when its Stories are all `done`.
|
|
76
|
+
- **Vendor support varies.** Apply the rollup state the vendor can express. Where a vendor has no native intermediate state, use the nearest configured role or a metadata/comment signal rather than forcing a non-existent status (PRD #522 non-goal: vendors need not expose identical states).
|
|
77
|
+
- **The parent never carries `ready`.** `ready` is a *human* "this is buildable, claim it" signal and only ever lives on leaves. Rollup moves a parent between non-ready container states (in-progress / blocked / terminal); it never sets the parent to `ready`.
|
|
78
|
+
|
|
79
|
+
### The rollup terminal state is the configured "done" — multi-env capable
|
|
80
|
+
|
|
81
|
+
The terminal rollup state is whatever the project configures for `done` — which is **env-keyed** (`config-resolution` "Env-keyed `done`"): a `done` map keyed by environment (`dev`, `staging`, `production`), resolved from the merged PR's base branch. This rule does **not** hardcode a `dev → staging → prod` promotion chain as required — that is a project-specific deploy topology. A downstream project with dev/staging/prod environments rolls a parent up to whichever terminal `done` value matches the environment its leaves shipped to. The rule stays generic and multi-env capable.
|
|
82
|
+
|
|
83
|
+
**Single-environment collapse (this repo).** Lisa's own deploy has only `main`/`production` (no dev/staging), so `done` is a single value, not a map, and the build lifecycle collapses to one chain: `ready → claimed (in-progress) → review (code-review) → done`. The rollup terminal state is simply `done`. This is the *collapsed* case of the generic rule, not a different rule — projects with more environments keep the env-keyed map.
|
|
84
|
+
|
|
85
|
+
## Citation
|
|
86
|
+
|
|
87
|
+
Skills that enforce this invariant or perform rollup cite this rule by slug (the `leaf-only-lifecycle` rule) instead of restating it:
|
|
88
|
+
|
|
89
|
+
- **Decomposition / write** (`*-to-tracker`, `*-write-*`) — apply the `ready` role to leaves only; never to containers.
|
|
90
|
+
- **Validate** (`*-validate-*`) — FAIL a container carrying the build-ready role; FAIL a childless Epic/Story/Spike marked build-ready.
|
|
91
|
+
- **Build intake** (`*-build-intake`, `tracker-build-intake`) — claim leaves only; skip or safe-block containers with stale build-ready roles.
|
|
92
|
+
- **Rollup** — derive parent state from children per the state machine above.
|
|
93
|
+
|
|
94
|
+
This is the inverse-direction companion to `repo-scope-split` (which governs a leaf's *repo* scope); together they define what a build-ready leaf work unit is: directly implementable, single-repo, childless-or-leaf-typed.
|
|
@@ -198,6 +198,8 @@ For each epic identified in Phase 1, **invoke the `lisa:tracker-write` shim** (d
|
|
|
198
198
|
- `artifacts`: the full Phase 1.5 artifact list — every artifact, regardless of domain. The Epic is the canonical hub.
|
|
199
199
|
- `priority`, `labels`, `components`, `fix_version`: as appropriate
|
|
200
200
|
|
|
201
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: an Epic is a container, not a leaf work unit. Do NOT mark it build-ready — `lisa:tracker-write` must not be passed `status:ready` for an Epic, and the Epic's lifecycle state rolls up from its children. The build-ready label is applied only in Phase 5.
|
|
202
|
+
|
|
201
203
|
Capture the returned epic ref — Phase 4 needs it as the parent for Stories.
|
|
202
204
|
|
|
203
205
|
### Phase 4: Create Stories
|
|
@@ -225,6 +227,8 @@ For each Story, **invoke `lisa:tracker-write`** with:
|
|
|
225
227
|
| Infrastructure | `ops`, `reference` |
|
|
226
228
|
| Mixed / setup ("X.0") | All domains |
|
|
227
229
|
|
|
230
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: a Story is a container (it has child Sub-tasks), not a leaf work unit. Do NOT mark it build-ready — never pass `status:ready` to `lisa:tracker-write` for a Story. Its lifecycle state rolls up from its Sub-tasks. The build-ready label is applied only in Phase 5.
|
|
231
|
+
|
|
228
232
|
Capture each returned story ref — Phase 5 needs it.
|
|
229
233
|
|
|
230
234
|
### Phase 5: Create Sub-tasks
|
|
@@ -237,6 +241,8 @@ Each sub-task MUST:
|
|
|
237
241
|
1. **Be scoped to exactly ONE repo** — indicated in brackets in the summary: `[repo-name]`.
|
|
238
242
|
2. **Include an Empirical Verification Plan** — real user-like verification, NOT unit tests, linting, or typechecking.
|
|
239
243
|
|
|
244
|
+
**Leaf-only build-ready (`leaf-only-lifecycle`)**: Sub-tasks are the **leaf work units** of the decomposition — they are the ONLY items in the hierarchy that receive the build-ready label. `lisa:tracker-write` applies `status:ready` here so downstream build intake (`lisa:github-build-intake`) claims the leaves and never the Epic or Stories. Apply `status:ready` to each Sub-task; never to its parent Story or Epic (Phases 3–4). `lisa:github-write-issue` enforces the same invariant on the write side, so a Sub-task split into per-repo children (the cross-repo case above) carries build-ready on the children, not on any intermediate parent that gains child work.
|
|
245
|
+
|
|
240
246
|
Sub-tasks inherit their parent Story's artifacts by reference (the parent link). Do not pass the same artifact list to every sub-task.
|
|
241
247
|
|
|
242
248
|
### Phase 5.5: Artifact Preservation Gate (mandatory)
|
|
@@ -206,6 +206,12 @@ Milestones map to `fix-version:<value>` labels OR native GitHub Milestones — p
|
|
|
206
206
|
|
|
207
207
|
Create labels lazily — call `gh label create` if a referenced label doesn't exist. Use a stable color palette per category so the issue board reads cleanly.
|
|
208
208
|
|
|
209
|
+
### Build-ready label is leaf-only
|
|
210
|
+
|
|
211
|
+
The build-ready status label (`status:ready`) is governed by the `leaf-only-lifecycle` rule. **Apply `status:ready` only when the issue is a leaf work unit** — an individually implementable issue type (`Bug`, `Task`, `Sub-task`, `Improvement`) that has **no child work**. A container (`Epic`, `Story`, `Spike`, or *any* issue that has sub-issues) is **never** written with `status:ready`; its lifecycle state rolls up from its children. The classification is structural: an item is a container if it has child work, regardless of its declared type (see the childless-parent exception in the rule). Do not hand-apply `status:ready` to a parent.
|
|
212
|
+
|
|
213
|
+
For non-build-ready issues created fresh (Epics, Stories, and other containers), omit the status label entirely; the container's rollup state is derived, not set directly.
|
|
214
|
+
|
|
209
215
|
## Phase 5.5 — Validate (Pre-write Gate)
|
|
210
216
|
|
|
211
217
|
Before any write, invoke `lisa:github-validate-issue` with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the `lisa:github-validate-issue` schema, including `runtime_behavior_change`, `authenticated_surface`, and `artifacts_attached` flags so the right gates run.
|
|
@@ -218,8 +224,9 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
|
|
|
218
224
|
|
|
219
225
|
### CREATE
|
|
220
226
|
|
|
221
|
-
1. Compose the body markdown from Phases 2/3/4 in a temp file (avoid quoting hell):
|
|
227
|
+
1. Compose the body markdown from Phases 2/3/4 in a temp file (avoid quoting hell). Apply `status:ready` **only for a leaf work unit** per the Phase 5 leaf-only rule (`leaf-only-lifecycle`) — omit it for `Epic` / `Story` / `Spike` and any issue that has child work:
|
|
222
228
|
```bash
|
|
229
|
+
# Leaf work unit (Bug / Task / Sub-task / Improvement with no children):
|
|
223
230
|
gh issue create \
|
|
224
231
|
--repo <org>/<repo> \
|
|
225
232
|
--title "<summary>" \
|
|
@@ -227,6 +234,16 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
|
|
|
227
234
|
--label "type:<type>" --label "status:ready" --label "priority:<priority>" \
|
|
228
235
|
[--label "component:<name>" ...] [--milestone "<milestone>"] \
|
|
229
236
|
[--assignee "<login>"]
|
|
237
|
+
|
|
238
|
+
# Container (Epic / Story / Spike / any issue with child work):
|
|
239
|
+
# identical, but WITHOUT --label "status:ready" — its state rolls up from children.
|
|
240
|
+
gh issue create \
|
|
241
|
+
--repo <org>/<repo> \
|
|
242
|
+
--title "<summary>" \
|
|
243
|
+
--body-file /tmp/issue-body.md \
|
|
244
|
+
--label "type:<type>" --label "priority:<priority>" \
|
|
245
|
+
[--label "component:<name>" ...] [--milestone "<milestone>"] \
|
|
246
|
+
[--assignee "<login>"]
|
|
230
247
|
```
|
|
231
248
|
2. Capture the returned issue number.
|
|
232
249
|
3. **Link to parent sub-issue** (if non-Epic): use the GraphQL sub-issue API.
|