@codyswann/lisa 2.34.0 → 2.35.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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/skills/jira-build-intake/SKILL.md +52 -5
  5. package/plugins/lisa/skills/linear-build-intake/SKILL.md +50 -5
  6. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +19 -1
  7. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  23. package/plugins/src/base/skills/jira-build-intake/SKILL.md +52 -5
  24. package/plugins/src/base/skills/linear-build-intake/SKILL.md +50 -5
  25. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +19 -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.34.0",
85
+ "version": "2.35.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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jira-build-intake
3
- description: "Symmetric counterpart to notion-prd-intake on the JIRA side. Scans a JIRA project (or JQL filter) for tickets in the configured `ready` status, claims each by transitioning to the configured `claimed` status, runs the implementation/build flow via jira-agent, and transitions to the configured `done` status on completion. The `ready` status is the human-flipped signal that a TODO ticket is truly ready for development — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
3
+ description: "Symmetric counterpart to notion-prd-intake on the JIRA side. Scans a JIRA project (or JQL filter) for tickets in the configured `ready` status, claims each by transitioning to the configured `claimed` status, runs the implementation/build flow via jira-agent, and transitions to the configured `done` status on completion. Enforces the claim-time arm of the `leaf-only-lifecycle` rule: a parent/container with open child work (or a childless Epic/Story/Spike) that still carries a stale build-ready status is skipped or safe-blocked with a lifecycle-repair comment, never claimed. The `ready` status is the human-flipped signal that a TODO ticket is truly ready for development — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash"]
5
5
  ---
6
6
 
@@ -118,7 +118,50 @@ If empty, report `"No tickets with Status=$READY. Nothing to do."` and exit. Thi
118
118
 
119
119
  ### Phase 3 — Process each ready ticket (serial)
120
120
 
121
- #### 3a. Claim
121
+ #### 3a. Leaf-only claim gate (skip / safe-block containers)
122
+
123
+ Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready status (e.g. `Ready` applied before this rule existed, or hand-applied to an Epic/Story) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:jira-write-ticket` and the validate-time S15 gate in `lisa:jira-validate-ticket`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
124
+
125
+ Run this gate **before** the claim transition, for every candidate ticket. Do NOT transition, comment "Claimed", or invoke `lisa:jira-agent` for a ticket that fails the gate.
126
+
127
+ **Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: a ticket is a **container** if it has **open** child work, whatever its declared type; otherwise the **issue type** decides. Resolve child work using the same hierarchy `lisa:jira-read-ticket` uses — JIRA's native Epic → Story → Sub-task parentage (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 and are **not** parentage — do not count them as children.
128
+
129
+ Fetch the ticket's children via `lisa:atlassian-access` `operation: search-issues` with a JQL that resolves both subtasks and Epic-linked Stories, then count those still open (not in a resolved/Done status):
130
+
131
+ ```bash
132
+ # Children of <TICKET>: native subtasks plus, for an Epic, its linked Stories.
133
+ # (parent = <TICKET>) covers Sub-tasks and child issues; ("Epic Link" = <TICKET>)
134
+ # covers Stories under an Epic on JIRA instances that expose the Epic Link field.
135
+ CHILDREN_JQL='(parent = "<TICKET>" OR "Epic Link" = "<TICKET>")'
136
+ # Count children whose status is NOT a resolved/terminal one. A parent whose
137
+ # children are all Done is no longer holding open work and rolls up via
138
+ # leaf-only-lifecycle's rollup, not here.
139
+ OPEN_CHILDREN_JQL="${CHILDREN_JQL} AND statusCategory != Done"
140
+ ```
141
+
142
+ Invoke `lisa:atlassian-access` `operation: search-issues jql: "<OPEN_CHILDREN_JQL>"` and let `OPEN_CHILDREN` be the count of returned issues (0 if none). If the JQL cannot resolve the `Epic Link` field on this instance (older JIRA / team-managed projects expose parentage differently), fall back to the parentage `lisa:jira-read-ticket` derives and treat the ticket as a container if any derived child is open. Note "Epic Link unavailable — parentage derived" so the operator knows how children were resolved.
143
+
144
+ Classify and act (first match wins). The issue type comes from the ticket's `issuetype` field (`Epic`, `Story`, `Spike`, `Bug`, `Task`, `Sub-task`, `Improvement`):
145
+
146
+ | Condition | Class | Action |
147
+ |---|---|---|
148
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
149
+ | no open children AND type ∈ {Epic, Story, Spike} | **Childless container-type** | **Skip / safe-block — do NOT claim** |
150
+ | no open children AND type ∈ {Bug, Task, Sub-task, Improvement} (or no recognized type) | **Leaf work unit** | **Proceed to 3b claim** |
151
+
152
+ The childless-parent exception is narrow: childlessness enables a claim **only** for types that are leaf work units to begin with. A childless Epic/Story/Spike is an incomplete decomposition, not an implementable unit — it is never claimed.
153
+
154
+ **Safe-block (default action for a flagged container).** Leave the build-ready status in place (don't silently transition it away — that hides the lifecycle error), post a single lifecycle-repair comment, and record the ticket under "Skipped (container)" in the summary. Do NOT transition to `$CLAIMED`. Keep the comment idempotent — skip posting if an identical `[claude-build-intake]` lifecycle-repair comment already exists on the ticket, so a re-entrant cycle doesn't spam it.
155
+
156
+ Post via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "<message>"` with:
157
+
158
+ ```text
159
+ [claude-build-intake] Not claimed: this ticket carries the build-ready status ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready (status:ready) is leaf-only per leaf-only-lifecycle — an agent claims and implements leaves, never a container. Repair: move $READY off this parent onto its leaf children (or, for a childless Epic/Story/Spike, decompose it into leaf children or reclassify it to a leaf type). A parent's lifecycle state rolls up from its children and is never set to ready directly.
160
+ ```
161
+
162
+ This gate never blocks a legitimate flat Task/Bug: those have no open children and a leaf type, so they fall straight through to the claim in 3b.
163
+
164
+ #### 3b. Claim
122
165
 
123
166
  Transition the ticket from `$READY` to `$CLAIMED` by invoking `lisa:atlassian-access` `operation: transition key: <TICKET> to: "$CLAIMED"`.
124
167
  - Post a `[claude-build-intake]` comment via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "Claimed by Claude. Starting build."`
@@ -126,7 +169,7 @@ Transition the ticket from `$READY` to `$CLAIMED` by invoking `lisa:atlassian-ac
126
169
 
127
170
  If the transition fails (permission, missing transition, race), log under "Errors" in the cycle summary and skip this ticket. **Do not invoke the build flow on a ticket you didn't successfully claim.**
128
171
 
129
- #### 3b. Run the build flow
172
+ #### 3c. Run the build flow
130
173
 
131
174
  Invoke the `lisa:jira-agent` (existing per-ticket lifecycle agent) with the ticket key. `lisa:jira-agent` owns:
132
175
  - Reading the full ticket graph (`lisa:jira-read-ticket`)
@@ -142,7 +185,7 @@ Wait for `lisa:jira-agent` to return. Capture its outcome:
142
185
  - **Blocked by ticket-triage ambiguities** — `lisa:jira-agent` posts findings and stops. The ticket stays in `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors" with reason `"Triage found ambiguities — see comments on <ticket-key>"`.
143
186
  - **Errored** — exception, missing config, etc. Leave the ticket in `$CLAIMED` for human investigation. Record under "Errors" with the exception summary.
144
187
 
145
- #### 3c. Transition to $DONE (only on Success)
188
+ #### 3d. Transition to $DONE (only on Success)
146
189
 
147
190
  If `lisa:jira-agent` returned Success:
148
191
  1. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
@@ -151,7 +194,7 @@ If `lisa:jira-agent` returned Success:
151
194
 
152
195
  For any non-Success outcome, do NOT transition. The ticket sits in `$CLAIMED` (or wherever `lisa:jira-agent` left it for the Blocked case) — the cycle's job is done; humans take it from there.
153
196
 
154
- #### 3d. Continue
197
+ #### 3e. Continue
155
198
 
156
199
  Move to the next ready ticket. One ticket failing does not stop others.
157
200
 
@@ -167,6 +210,8 @@ Cycle completed: <ISO timestamp>
167
210
  Tickets processed: <n>
168
211
  - $DONE (build complete, PR ready): <n>
169
212
  - <ticket-key> <summary> → PR <URL>
213
+ - Skipped (container — leaf-only-lifecycle): <n>
214
+ - <ticket-key> <summary> — build-ready on a parent with open child work; lifecycle-repair comment posted
170
215
  - Blocked (pre-flight verify failed): <n>
171
216
  - <ticket-key> <summary> — see ticket comments
172
217
  - Held (triage found ambiguities): <n>
@@ -179,6 +224,7 @@ Total PRs opened: <n>
179
224
 
180
225
  ## Idempotency & safety
181
226
 
227
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
182
228
  - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:jira-agent` invocation — no double-pickup.
183
229
  - **No writes outside the lifecycle**: this skill only transitions `$READY → $CLAIMED` and `$CLAIMED → $DONE`. Every other status change is owned by `lisa:jira-agent` (which suggests transitions but only auto-transitions on the verify-FAIL path).
184
230
  - **Failure isolation**: per-ticket exceptions caught and recorded; the cycle continues.
@@ -211,6 +257,7 @@ If a ready-equivalent status does not exist in the JIRA project's workflow, this
211
257
 
212
258
  ## Rules
213
259
 
260
+ - **Claim leaves only.** Per the `leaf-only-lifecycle` rule, never claim a container — a ticket with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready status. Skip or safe-block it (Phase 3a); never silently implement a container.
214
261
  - Never transition a ticket the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
215
262
  - Never bypass `lisa:jira-agent` to do build work directly. `lisa:jira-agent` owns the per-ticket lifecycle (read, verify, triage, route, sync, evidence). This skill is the dispatcher, not the builder.
216
263
  - Never auto-transition past `$DONE`. Downstream statuses are owned by QA / product / a future verification-intake skill — not this one.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: linear-build-intake
3
- description: "Symmetric counterpart to lisa:jira-build-intake on the Linear side. Scans a Linear team for Issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:linear-agent, and relabels to the configured `done` label on completion. The `ready` label is the human-flipped signal that an Issue is truly ready for development — mirroring how Notion PRDs work Draft → Ready → (us) In Review → Blocked|Ticketed."
3
+ description: "Symmetric counterpart to lisa:jira-build-intake on the Linear side. Scans a Linear team for Issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:linear-agent, and relabels to the configured `done` label on completion. Enforces the claim-time arm of the `leaf-only-lifecycle` rule: a parent/container with open child work (or a childless Epic/Story/Spike) that still carries a stale build-ready label is skipped or safe-blocked with a lifecycle-repair comment, never claimed. The `ready` label is the human-flipped signal that an Issue is truly ready for development — mirroring how Notion PRDs work Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash", "mcp__linear-server__list_teams", "mcp__linear-server__list_issues", "mcp__linear-server__get_issue", "mcp__linear-server__save_issue", "mcp__linear-server__save_comment", "mcp__linear-server__list_issue_labels", "mcp__linear-server__create_issue_label"]
5
5
  ---
6
6
 
@@ -128,7 +128,48 @@ If empty, report `"No Linear Issues labeled $READY. Nothing to do."` and exit. C
128
128
 
129
129
  ### Phase 3 — Process each ready Issue (serial)
130
130
 
131
- #### 3a. Claim
131
+ #### 3a. Leaf-only claim gate (skip / safe-block containers)
132
+
133
+ Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready label (e.g. `status:ready` applied before this rule existed, or hand-applied to a Project-grouped parent Issue) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:linear-write-issue` and the validate-time S15 gate in `lisa:linear-validate-issue`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
134
+
135
+ Run this gate **before** the claim relabel, for every candidate Issue. Do NOT relabel, comment "Claimed", or invoke `lisa:linear-agent` for an Issue that fails the gate.
136
+
137
+ **Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: an Issue is a **container** if it has **open** child work, whatever its declared type; otherwise the **type label** decides. Resolve child work using the same hierarchy `lisa:linear-read-issue` uses — Linear's native parentage: an Issue groups **sub-issues** via `parentId`, and a **Project** (the Epic equivalent) groups Issues via `projectId`. Relations (`save_issue_relation` — `blocks` / `is blocked by`) express dependencies and are **not** parentage — do not count them as children.
138
+
139
+ Fetch the Issue's sub-issues via `mcp__linear-server__get_issue` (which returns the children) or `mcp__linear-server__list_issues({parentId: <issueId>})`, then count those still open (Linear `state.type` not in the completed/canceled set):
140
+
141
+ ```text
142
+ # Children of <issueId>: native sub-issues via parentId.
143
+ # Count children whose Linear state.type is NOT terminal ("completed" / "canceled").
144
+ # A parent whose children are all completed is no longer holding open work and
145
+ # rolls up via leaf-only-lifecycle's rollup, not here.
146
+ OPEN_CHILDREN = count(list_issues({parentId: <issueId>})
147
+ where state.type not in {"completed", "canceled"})
148
+ ```
149
+
150
+ For a Project-level parent (an Issue that itself anchors a `projectId` grouping rather than a `parentId` tree), resolve membership the same way `lisa:linear-read-issue` does and treat the parent as a container if any grouped Issue is still open. If sub-issue resolution is unavailable, fall back to the parentage `lisa:linear-read-issue` derives and treat the Issue as a container if any derived child is open. Note "sub-issues unavailable — parentage derived" so the operator knows how children were resolved.
151
+
152
+ Classify and act (first match wins). The type comes from the Issue's `type:` label (`type:Epic`, `type:Story`, `type:Spike`, `type:Bug`, `type:Task`, `type:Sub-task`, `type:Improvement`):
153
+
154
+ | Condition | Class | Action |
155
+ |---|---|---|
156
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
157
+ | no open children AND type ∈ {Epic, Story, Spike} | **Childless container-type** | **Skip / safe-block — do NOT claim** |
158
+ | no open children AND type ∈ {Bug, Task, Sub-task, Improvement} (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
159
+
160
+ The childless-parent exception is narrow: childlessness enables a claim **only** for types that are leaf work units to begin with. A childless Epic/Story/Spike is an incomplete decomposition, not an implementable unit — it is never claimed.
161
+
162
+ **Safe-block (default action for a flagged container).** Leave the build-ready label in place (don't silently strip it — that hides the lifecycle error), post a single lifecycle-repair comment, and record the Issue under "Skipped (container)" in the summary. Do NOT relabel to `$CLAIMED`. Keep the comment idempotent — skip posting if an identical `[claude-build-intake]` lifecycle-repair comment already exists on the Issue, so a re-entrant cycle doesn't spam it.
163
+
164
+ Post via `mcp__linear-server__save_comment` with:
165
+
166
+ ```text
167
+ [claude-build-intake] Not claimed: this Issue carries the build-ready label ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready (status:ready) is leaf-only per leaf-only-lifecycle — an agent claims and implements leaves, never a container. Repair: move $READY off this parent onto its leaf children (or, for a childless Epic/Story/Spike, decompose it into leaf children or reclassify it to a leaf type). A parent's lifecycle state rolls up from its children and is never set to ready directly.
168
+ ```
169
+
170
+ This gate never blocks a legitimate flat Task/Bug: those have no open children and a leaf `type:`, so they fall straight through to the claim in 3b.
171
+
172
+ #### 3b. Claim
132
173
 
133
174
  Update labels via `mcp__linear-server__save_issue`: remove `$READY`, add `$CLAIMED`. Resolve label IDs via `list_issue_labels` (create `$CLAIMED` if missing).
134
175
 
@@ -138,7 +179,7 @@ This is the idempotency lock — a re-entrant cycle's `label: $READY` filter wil
138
179
 
139
180
  If the relabel fails (permission, race), record under "Errors" and skip. **Do not invoke the build flow on an Issue you didn't successfully claim.**
140
181
 
141
- #### 3b. Run the build flow
182
+ #### 3c. Run the build flow
142
183
 
143
184
  Invoke `lisa:linear-agent` (per-Issue lifecycle agent) with the Issue identifier. `lisa:linear-agent` owns:
144
185
  - Reading the full Issue graph (`lisa:linear-read-issue`)
@@ -154,7 +195,7 @@ Wait for the agent to return. Capture its outcome:
154
195
  - **Blocked by ticket-triage ambiguities** — agent posts findings and stops. The Issue stays at `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors".
155
196
  - **Errored** — exception, missing config, etc. Leave at `$CLAIMED`. Record with exception summary.
156
197
 
157
- #### 3c. Relabel to $DONE (only on Success)
198
+ #### 3d. Relabel to $DONE (only on Success)
158
199
 
159
200
  If `lisa:linear-agent` returned Success:
160
201
  1. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
@@ -163,7 +204,7 @@ If `lisa:linear-agent` returned Success:
163
204
 
164
205
  For any non-Success outcome, do NOT transition. The Issue sits where the agent left it — humans take it from there.
165
206
 
166
- #### 3d. Continue
207
+ #### 3e. Continue
167
208
 
168
209
  Move to the next ready Issue. One Issue failing does not stop others.
169
210
 
@@ -179,6 +220,8 @@ Cycle completed: <ISO timestamp>
179
220
  Issues processed: <n>
180
221
  - $DONE (build complete, PR ready): <n>
181
222
  - <ID> <title> → PR <URL>
223
+ - Skipped (container — leaf-only-lifecycle): <n>
224
+ - <ID> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
182
225
  - status:blocked (pre-flight verify failed): <n>
183
226
  - <ID> <title> — see Issue comments
184
227
  - Held (triage found ambiguities): <n>
@@ -191,6 +234,7 @@ Total PRs opened: <n>
191
234
 
192
235
  ## Idempotency & safety
193
236
 
237
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
194
238
  - **Claim-first ordering**: `$CLAIMED` set BEFORE agent invocation — no double-pickup.
195
239
  - **No writes outside the lifecycle**: this skill only adds/removes `$READY`, `$CLAIMED`, `$DONE`. Every other label change (and the native state) is owned by the agent or `lisa:linear-evidence`.
196
240
  - **Failure isolation**: per-Issue exceptions caught and recorded; the cycle continues.
@@ -210,6 +254,7 @@ If the team hasn't adopted these labels, the first run exits with an adoption hi
210
254
 
211
255
  ## Rules
212
256
 
257
+ - **Claim leaves only.** Per the `leaf-only-lifecycle` rule, never claim a container — an Issue with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready label. Skip or safe-block it (Phase 3a); never silently implement a container.
213
258
  - Never relabel an Issue the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
214
259
  - Never bypass `lisa:linear-agent` to do build work directly. The agent owns the per-Issue lifecycle.
215
260
  - Never auto-transition past `$DONE`. Downstream labels are owned by QA / product / a future verification-intake skill.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tracker-build-intake
3
- description: "Vendor-neutral wrapper for the build-queue batch scanner. Reads `tracker` from .lisa.config.json (default: jira) and dispatches to lisa:jira-build-intake (JQL/project-key queue), lisa:github-build-intake (GitHub repo queue keyed off the `status:ready` label), or lisa:linear-build-intake (Linear team queue keyed off the `status:ready` label). Counterpart to lisa:intake's PRD-side dispatchers."
3
+ description: "Vendor-neutral wrapper for the build-queue batch scanner. Reads `tracker` from .lisa.config.json (default: jira) and dispatches to lisa:jira-build-intake (JQL/project-key queue), lisa:github-build-intake (GitHub repo queue keyed off the `status:ready` label), or lisa:linear-build-intake (Linear team queue keyed off the `status:ready` label). Every vendor scanner enforces the claim-time arm of the `leaf-only-lifecycle` rule — claim leaf work units only; skip or safe-block a container with open child work (or a childless Epic/Story/Spike) that carries a stale build-ready role. Counterpart to lisa:intake's PRD-side dispatchers."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -20,8 +20,26 @@ See the `config-resolution` rule for configuration and dispatch table.
20
20
  - Anything else → stop and report `"Unknown tracker '<value>' in .lisa.config.json. Expected 'jira', 'github', or 'linear'."`
21
21
  3. Pass through the cycle summary verbatim.
22
22
 
23
+ ## Leaf-only claim contract (forwarded to every vendor)
24
+
25
+ This shim is dispatch only — it does not reclassify or re-gate items — but the contract it forwards is part of the build-intake API, so it is documented here once and the three vendor scanners implement it identically. Per the vendor-neutral `leaf-only-lifecycle` rule, **build intake claims only independently implementable leaf work units**:
26
+
27
+ - A **leaf work unit** (Bug, Task, Sub-task, Improvement with no open child work) is claimed and built.
28
+ - A **container** — anything with open child work, or a childless Epic/Story/Spike — that still carries a stale build-ready role is **never claimed**. The vendor scanner skips it or safe-blocks it with an idempotent lifecycle-repair comment (move the build-ready role off the parent onto its leaf children; a parent's state rolls up from its children and is never set to ready directly).
29
+
30
+ This is the claim-time arm of the rule. Its siblings are the write-time labeling (`lisa:tracker-write` → the vendor `*-write-*` skills apply build-ready to leaves only) and the validate-time S15 gate (`lisa:tracker-validate` → the vendor `*-validate-*` skills FAIL a build-ready container). All three arms cite `leaf-only-lifecycle` so no vendor drifts. Each vendor scanner implements the gate against its own hierarchy:
31
+
32
+ | Tracker | Vendor scanner | Hierarchy used to detect open child work |
33
+ |---|---|---|
34
+ | `github` | `lisa:github-build-intake` (Phase 3a) | native sub-issues (GraphQL) + body parentage |
35
+ | `jira` | `lisa:jira-build-intake` (Phase 3a) | native Epic → Story → Sub-task parentage |
36
+ | `linear` | `lisa:linear-build-intake` (Phase 3a) | native sub-issues via `parentId` + Project grouping |
37
+
38
+ The shim never needs to inspect the item itself — it forwards `$ARGUMENTS` verbatim and the resolved vendor scanner runs its Phase 3a gate before any claim.
39
+
23
40
  ## Rules
24
41
 
25
42
  - Single cycle per invocation — the vendor skill processes the current `Ready` set and exits.
26
43
  - The vendor skills run their own pre-flight checks (JIRA workflow transitions for the JIRA path; label namespace adoption for the GitHub and Linear paths) before processing items. Never bypass.
44
+ - **Leaf-only claim, every vendor.** Per the `leaf-only-lifecycle` rule, each vendor scanner claims leaf work units only and skips / safe-blocks a container (open child work, or a childless Epic/Story/Spike) carrying a stale build-ready role. This shim does not re-implement the gate — it relies on the vendor scanner's Phase 3a — but the contract is uniform across `jira`, `github`, and `linear` so behavior never drifts by tracker.
27
45
  - Never run two intake cycles concurrently against overlapping queues — the scheduling layer is responsible for serialization.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jira-build-intake
3
- description: "Symmetric counterpart to notion-prd-intake on the JIRA side. Scans a JIRA project (or JQL filter) for tickets in the configured `ready` status, claims each by transitioning to the configured `claimed` status, runs the implementation/build flow via jira-agent, and transitions to the configured `done` status on completion. The `ready` status is the human-flipped signal that a TODO ticket is truly ready for development — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
3
+ description: "Symmetric counterpart to notion-prd-intake on the JIRA side. Scans a JIRA project (or JQL filter) for tickets in the configured `ready` status, claims each by transitioning to the configured `claimed` status, runs the implementation/build flow via jira-agent, and transitions to the configured `done` status on completion. Enforces the claim-time arm of the `leaf-only-lifecycle` rule: a parent/container with open child work (or a childless Epic/Story/Spike) that still carries a stale build-ready status is skipped or safe-blocked with a lifecycle-repair comment, never claimed. The `ready` status is the human-flipped signal that a TODO ticket is truly ready for development — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash"]
5
5
  ---
6
6
 
@@ -118,7 +118,50 @@ If empty, report `"No tickets with Status=$READY. Nothing to do."` and exit. Thi
118
118
 
119
119
  ### Phase 3 — Process each ready ticket (serial)
120
120
 
121
- #### 3a. Claim
121
+ #### 3a. Leaf-only claim gate (skip / safe-block containers)
122
+
123
+ Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready status (e.g. `Ready` applied before this rule existed, or hand-applied to an Epic/Story) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:jira-write-ticket` and the validate-time S15 gate in `lisa:jira-validate-ticket`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
124
+
125
+ Run this gate **before** the claim transition, for every candidate ticket. Do NOT transition, comment "Claimed", or invoke `lisa:jira-agent` for a ticket that fails the gate.
126
+
127
+ **Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: a ticket is a **container** if it has **open** child work, whatever its declared type; otherwise the **issue type** decides. Resolve child work using the same hierarchy `lisa:jira-read-ticket` uses — JIRA's native Epic → Story → Sub-task parentage (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 and are **not** parentage — do not count them as children.
128
+
129
+ Fetch the ticket's children via `lisa:atlassian-access` `operation: search-issues` with a JQL that resolves both subtasks and Epic-linked Stories, then count those still open (not in a resolved/Done status):
130
+
131
+ ```bash
132
+ # Children of <TICKET>: native subtasks plus, for an Epic, its linked Stories.
133
+ # (parent = <TICKET>) covers Sub-tasks and child issues; ("Epic Link" = <TICKET>)
134
+ # covers Stories under an Epic on JIRA instances that expose the Epic Link field.
135
+ CHILDREN_JQL='(parent = "<TICKET>" OR "Epic Link" = "<TICKET>")'
136
+ # Count children whose status is NOT a resolved/terminal one. A parent whose
137
+ # children are all Done is no longer holding open work and rolls up via
138
+ # leaf-only-lifecycle's rollup, not here.
139
+ OPEN_CHILDREN_JQL="${CHILDREN_JQL} AND statusCategory != Done"
140
+ ```
141
+
142
+ Invoke `lisa:atlassian-access` `operation: search-issues jql: "<OPEN_CHILDREN_JQL>"` and let `OPEN_CHILDREN` be the count of returned issues (0 if none). If the JQL cannot resolve the `Epic Link` field on this instance (older JIRA / team-managed projects expose parentage differently), fall back to the parentage `lisa:jira-read-ticket` derives and treat the ticket as a container if any derived child is open. Note "Epic Link unavailable — parentage derived" so the operator knows how children were resolved.
143
+
144
+ Classify and act (first match wins). The issue type comes from the ticket's `issuetype` field (`Epic`, `Story`, `Spike`, `Bug`, `Task`, `Sub-task`, `Improvement`):
145
+
146
+ | Condition | Class | Action |
147
+ |---|---|---|
148
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
149
+ | no open children AND type ∈ {Epic, Story, Spike} | **Childless container-type** | **Skip / safe-block — do NOT claim** |
150
+ | no open children AND type ∈ {Bug, Task, Sub-task, Improvement} (or no recognized type) | **Leaf work unit** | **Proceed to 3b claim** |
151
+
152
+ The childless-parent exception is narrow: childlessness enables a claim **only** for types that are leaf work units to begin with. A childless Epic/Story/Spike is an incomplete decomposition, not an implementable unit — it is never claimed.
153
+
154
+ **Safe-block (default action for a flagged container).** Leave the build-ready status in place (don't silently transition it away — that hides the lifecycle error), post a single lifecycle-repair comment, and record the ticket under "Skipped (container)" in the summary. Do NOT transition to `$CLAIMED`. Keep the comment idempotent — skip posting if an identical `[claude-build-intake]` lifecycle-repair comment already exists on the ticket, so a re-entrant cycle doesn't spam it.
155
+
156
+ Post via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "<message>"` with:
157
+
158
+ ```text
159
+ [claude-build-intake] Not claimed: this ticket carries the build-ready status ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready (status:ready) is leaf-only per leaf-only-lifecycle — an agent claims and implements leaves, never a container. Repair: move $READY off this parent onto its leaf children (or, for a childless Epic/Story/Spike, decompose it into leaf children or reclassify it to a leaf type). A parent's lifecycle state rolls up from its children and is never set to ready directly.
160
+ ```
161
+
162
+ This gate never blocks a legitimate flat Task/Bug: those have no open children and a leaf type, so they fall straight through to the claim in 3b.
163
+
164
+ #### 3b. Claim
122
165
 
123
166
  Transition the ticket from `$READY` to `$CLAIMED` by invoking `lisa:atlassian-access` `operation: transition key: <TICKET> to: "$CLAIMED"`.
124
167
  - Post a `[claude-build-intake]` comment via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "Claimed by Claude. Starting build."`
@@ -126,7 +169,7 @@ Transition the ticket from `$READY` to `$CLAIMED` by invoking `lisa:atlassian-ac
126
169
 
127
170
  If the transition fails (permission, missing transition, race), log under "Errors" in the cycle summary and skip this ticket. **Do not invoke the build flow on a ticket you didn't successfully claim.**
128
171
 
129
- #### 3b. Run the build flow
172
+ #### 3c. Run the build flow
130
173
 
131
174
  Invoke the `lisa:jira-agent` (existing per-ticket lifecycle agent) with the ticket key. `lisa:jira-agent` owns:
132
175
  - Reading the full ticket graph (`lisa:jira-read-ticket`)
@@ -142,7 +185,7 @@ Wait for `lisa:jira-agent` to return. Capture its outcome:
142
185
  - **Blocked by ticket-triage ambiguities** — `lisa:jira-agent` posts findings and stops. The ticket stays in `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors" with reason `"Triage found ambiguities — see comments on <ticket-key>"`.
143
186
  - **Errored** — exception, missing config, etc. Leave the ticket in `$CLAIMED` for human investigation. Record under "Errors" with the exception summary.
144
187
 
145
- #### 3c. Transition to $DONE (only on Success)
188
+ #### 3d. Transition to $DONE (only on Success)
146
189
 
147
190
  If `lisa:jira-agent` returned Success:
148
191
  1. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
@@ -151,7 +194,7 @@ If `lisa:jira-agent` returned Success:
151
194
 
152
195
  For any non-Success outcome, do NOT transition. The ticket sits in `$CLAIMED` (or wherever `lisa:jira-agent` left it for the Blocked case) — the cycle's job is done; humans take it from there.
153
196
 
154
- #### 3d. Continue
197
+ #### 3e. Continue
155
198
 
156
199
  Move to the next ready ticket. One ticket failing does not stop others.
157
200
 
@@ -167,6 +210,8 @@ Cycle completed: <ISO timestamp>
167
210
  Tickets processed: <n>
168
211
  - $DONE (build complete, PR ready): <n>
169
212
  - <ticket-key> <summary> → PR <URL>
213
+ - Skipped (container — leaf-only-lifecycle): <n>
214
+ - <ticket-key> <summary> — build-ready on a parent with open child work; lifecycle-repair comment posted
170
215
  - Blocked (pre-flight verify failed): <n>
171
216
  - <ticket-key> <summary> — see ticket comments
172
217
  - Held (triage found ambiguities): <n>
@@ -179,6 +224,7 @@ Total PRs opened: <n>
179
224
 
180
225
  ## Idempotency & safety
181
226
 
227
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
182
228
  - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:jira-agent` invocation — no double-pickup.
183
229
  - **No writes outside the lifecycle**: this skill only transitions `$READY → $CLAIMED` and `$CLAIMED → $DONE`. Every other status change is owned by `lisa:jira-agent` (which suggests transitions but only auto-transitions on the verify-FAIL path).
184
230
  - **Failure isolation**: per-ticket exceptions caught and recorded; the cycle continues.
@@ -211,6 +257,7 @@ If a ready-equivalent status does not exist in the JIRA project's workflow, this
211
257
 
212
258
  ## Rules
213
259
 
260
+ - **Claim leaves only.** Per the `leaf-only-lifecycle` rule, never claim a container — a ticket with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready status. Skip or safe-block it (Phase 3a); never silently implement a container.
214
261
  - Never transition a ticket the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
215
262
  - Never bypass `lisa:jira-agent` to do build work directly. `lisa:jira-agent` owns the per-ticket lifecycle (read, verify, triage, route, sync, evidence). This skill is the dispatcher, not the builder.
216
263
  - Never auto-transition past `$DONE`. Downstream statuses are owned by QA / product / a future verification-intake skill — not this one.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: linear-build-intake
3
- description: "Symmetric counterpart to lisa:jira-build-intake on the Linear side. Scans a Linear team for Issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:linear-agent, and relabels to the configured `done` label on completion. The `ready` label is the human-flipped signal that an Issue is truly ready for development — mirroring how Notion PRDs work Draft → Ready → (us) In Review → Blocked|Ticketed."
3
+ description: "Symmetric counterpart to lisa:jira-build-intake on the Linear side. Scans a Linear team for Issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:linear-agent, and relabels to the configured `done` label on completion. Enforces the claim-time arm of the `leaf-only-lifecycle` rule: a parent/container with open child work (or a childless Epic/Story/Spike) that still carries a stale build-ready label is skipped or safe-blocked with a lifecycle-repair comment, never claimed. The `ready` label is the human-flipped signal that an Issue is truly ready for development — mirroring how Notion PRDs work Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash", "mcp__linear-server__list_teams", "mcp__linear-server__list_issues", "mcp__linear-server__get_issue", "mcp__linear-server__save_issue", "mcp__linear-server__save_comment", "mcp__linear-server__list_issue_labels", "mcp__linear-server__create_issue_label"]
5
5
  ---
6
6
 
@@ -128,7 +128,48 @@ If empty, report `"No Linear Issues labeled $READY. Nothing to do."` and exit. C
128
128
 
129
129
  ### Phase 3 — Process each ready Issue (serial)
130
130
 
131
- #### 3a. Claim
131
+ #### 3a. Leaf-only claim gate (skip / safe-block containers)
132
+
133
+ Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready label (e.g. `status:ready` applied before this rule existed, or hand-applied to a Project-grouped parent Issue) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:linear-write-issue` and the validate-time S15 gate in `lisa:linear-validate-issue`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
134
+
135
+ Run this gate **before** the claim relabel, for every candidate Issue. Do NOT relabel, comment "Claimed", or invoke `lisa:linear-agent` for an Issue that fails the gate.
136
+
137
+ **Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: an Issue is a **container** if it has **open** child work, whatever its declared type; otherwise the **type label** decides. Resolve child work using the same hierarchy `lisa:linear-read-issue` uses — Linear's native parentage: an Issue groups **sub-issues** via `parentId`, and a **Project** (the Epic equivalent) groups Issues via `projectId`. Relations (`save_issue_relation` — `blocks` / `is blocked by`) express dependencies and are **not** parentage — do not count them as children.
138
+
139
+ Fetch the Issue's sub-issues via `mcp__linear-server__get_issue` (which returns the children) or `mcp__linear-server__list_issues({parentId: <issueId>})`, then count those still open (Linear `state.type` not in the completed/canceled set):
140
+
141
+ ```text
142
+ # Children of <issueId>: native sub-issues via parentId.
143
+ # Count children whose Linear state.type is NOT terminal ("completed" / "canceled").
144
+ # A parent whose children are all completed is no longer holding open work and
145
+ # rolls up via leaf-only-lifecycle's rollup, not here.
146
+ OPEN_CHILDREN = count(list_issues({parentId: <issueId>})
147
+ where state.type not in {"completed", "canceled"})
148
+ ```
149
+
150
+ For a Project-level parent (an Issue that itself anchors a `projectId` grouping rather than a `parentId` tree), resolve membership the same way `lisa:linear-read-issue` does and treat the parent as a container if any grouped Issue is still open. If sub-issue resolution is unavailable, fall back to the parentage `lisa:linear-read-issue` derives and treat the Issue as a container if any derived child is open. Note "sub-issues unavailable — parentage derived" so the operator knows how children were resolved.
151
+
152
+ Classify and act (first match wins). The type comes from the Issue's `type:` label (`type:Epic`, `type:Story`, `type:Spike`, `type:Bug`, `type:Task`, `type:Sub-task`, `type:Improvement`):
153
+
154
+ | Condition | Class | Action |
155
+ |---|---|---|
156
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
157
+ | no open children AND type ∈ {Epic, Story, Spike} | **Childless container-type** | **Skip / safe-block — do NOT claim** |
158
+ | no open children AND type ∈ {Bug, Task, Sub-task, Improvement} (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
159
+
160
+ The childless-parent exception is narrow: childlessness enables a claim **only** for types that are leaf work units to begin with. A childless Epic/Story/Spike is an incomplete decomposition, not an implementable unit — it is never claimed.
161
+
162
+ **Safe-block (default action for a flagged container).** Leave the build-ready label in place (don't silently strip it — that hides the lifecycle error), post a single lifecycle-repair comment, and record the Issue under "Skipped (container)" in the summary. Do NOT relabel to `$CLAIMED`. Keep the comment idempotent — skip posting if an identical `[claude-build-intake]` lifecycle-repair comment already exists on the Issue, so a re-entrant cycle doesn't spam it.
163
+
164
+ Post via `mcp__linear-server__save_comment` with:
165
+
166
+ ```text
167
+ [claude-build-intake] Not claimed: this Issue carries the build-ready label ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready (status:ready) is leaf-only per leaf-only-lifecycle — an agent claims and implements leaves, never a container. Repair: move $READY off this parent onto its leaf children (or, for a childless Epic/Story/Spike, decompose it into leaf children or reclassify it to a leaf type). A parent's lifecycle state rolls up from its children and is never set to ready directly.
168
+ ```
169
+
170
+ This gate never blocks a legitimate flat Task/Bug: those have no open children and a leaf `type:`, so they fall straight through to the claim in 3b.
171
+
172
+ #### 3b. Claim
132
173
 
133
174
  Update labels via `mcp__linear-server__save_issue`: remove `$READY`, add `$CLAIMED`. Resolve label IDs via `list_issue_labels` (create `$CLAIMED` if missing).
134
175
 
@@ -138,7 +179,7 @@ This is the idempotency lock — a re-entrant cycle's `label: $READY` filter wil
138
179
 
139
180
  If the relabel fails (permission, race), record under "Errors" and skip. **Do not invoke the build flow on an Issue you didn't successfully claim.**
140
181
 
141
- #### 3b. Run the build flow
182
+ #### 3c. Run the build flow
142
183
 
143
184
  Invoke `lisa:linear-agent` (per-Issue lifecycle agent) with the Issue identifier. `lisa:linear-agent` owns:
144
185
  - Reading the full Issue graph (`lisa:linear-read-issue`)
@@ -154,7 +195,7 @@ Wait for the agent to return. Capture its outcome:
154
195
  - **Blocked by ticket-triage ambiguities** — agent posts findings and stops. The Issue stays at `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors".
155
196
  - **Errored** — exception, missing config, etc. Leave at `$CLAIMED`. Record with exception summary.
156
197
 
157
- #### 3c. Relabel to $DONE (only on Success)
198
+ #### 3d. Relabel to $DONE (only on Success)
158
199
 
159
200
  If `lisa:linear-agent` returned Success:
160
201
  1. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
@@ -163,7 +204,7 @@ If `lisa:linear-agent` returned Success:
163
204
 
164
205
  For any non-Success outcome, do NOT transition. The Issue sits where the agent left it — humans take it from there.
165
206
 
166
- #### 3d. Continue
207
+ #### 3e. Continue
167
208
 
168
209
  Move to the next ready Issue. One Issue failing does not stop others.
169
210
 
@@ -179,6 +220,8 @@ Cycle completed: <ISO timestamp>
179
220
  Issues processed: <n>
180
221
  - $DONE (build complete, PR ready): <n>
181
222
  - <ID> <title> → PR <URL>
223
+ - Skipped (container — leaf-only-lifecycle): <n>
224
+ - <ID> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
182
225
  - status:blocked (pre-flight verify failed): <n>
183
226
  - <ID> <title> — see Issue comments
184
227
  - Held (triage found ambiguities): <n>
@@ -191,6 +234,7 @@ Total PRs opened: <n>
191
234
 
192
235
  ## Idempotency & safety
193
236
 
237
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
194
238
  - **Claim-first ordering**: `$CLAIMED` set BEFORE agent invocation — no double-pickup.
195
239
  - **No writes outside the lifecycle**: this skill only adds/removes `$READY`, `$CLAIMED`, `$DONE`. Every other label change (and the native state) is owned by the agent or `lisa:linear-evidence`.
196
240
  - **Failure isolation**: per-Issue exceptions caught and recorded; the cycle continues.
@@ -210,6 +254,7 @@ If the team hasn't adopted these labels, the first run exits with an adoption hi
210
254
 
211
255
  ## Rules
212
256
 
257
+ - **Claim leaves only.** Per the `leaf-only-lifecycle` rule, never claim a container — an Issue with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready label. Skip or safe-block it (Phase 3a); never silently implement a container.
213
258
  - Never relabel an Issue the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
214
259
  - Never bypass `lisa:linear-agent` to do build work directly. The agent owns the per-Issue lifecycle.
215
260
  - Never auto-transition past `$DONE`. Downstream labels are owned by QA / product / a future verification-intake skill.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tracker-build-intake
3
- description: "Vendor-neutral wrapper for the build-queue batch scanner. Reads `tracker` from .lisa.config.json (default: jira) and dispatches to lisa:jira-build-intake (JQL/project-key queue), lisa:github-build-intake (GitHub repo queue keyed off the `status:ready` label), or lisa:linear-build-intake (Linear team queue keyed off the `status:ready` label). Counterpart to lisa:intake's PRD-side dispatchers."
3
+ description: "Vendor-neutral wrapper for the build-queue batch scanner. Reads `tracker` from .lisa.config.json (default: jira) and dispatches to lisa:jira-build-intake (JQL/project-key queue), lisa:github-build-intake (GitHub repo queue keyed off the `status:ready` label), or lisa:linear-build-intake (Linear team queue keyed off the `status:ready` label). Every vendor scanner enforces the claim-time arm of the `leaf-only-lifecycle` rule — claim leaf work units only; skip or safe-block a container with open child work (or a childless Epic/Story/Spike) that carries a stale build-ready role. Counterpart to lisa:intake's PRD-side dispatchers."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -20,8 +20,26 @@ See the `config-resolution` rule for configuration and dispatch table.
20
20
  - Anything else → stop and report `"Unknown tracker '<value>' in .lisa.config.json. Expected 'jira', 'github', or 'linear'."`
21
21
  3. Pass through the cycle summary verbatim.
22
22
 
23
+ ## Leaf-only claim contract (forwarded to every vendor)
24
+
25
+ This shim is dispatch only — it does not reclassify or re-gate items — but the contract it forwards is part of the build-intake API, so it is documented here once and the three vendor scanners implement it identically. Per the vendor-neutral `leaf-only-lifecycle` rule, **build intake claims only independently implementable leaf work units**:
26
+
27
+ - A **leaf work unit** (Bug, Task, Sub-task, Improvement with no open child work) is claimed and built.
28
+ - A **container** — anything with open child work, or a childless Epic/Story/Spike — that still carries a stale build-ready role is **never claimed**. The vendor scanner skips it or safe-blocks it with an idempotent lifecycle-repair comment (move the build-ready role off the parent onto its leaf children; a parent's state rolls up from its children and is never set to ready directly).
29
+
30
+ This is the claim-time arm of the rule. Its siblings are the write-time labeling (`lisa:tracker-write` → the vendor `*-write-*` skills apply build-ready to leaves only) and the validate-time S15 gate (`lisa:tracker-validate` → the vendor `*-validate-*` skills FAIL a build-ready container). All three arms cite `leaf-only-lifecycle` so no vendor drifts. Each vendor scanner implements the gate against its own hierarchy:
31
+
32
+ | Tracker | Vendor scanner | Hierarchy used to detect open child work |
33
+ |---|---|---|
34
+ | `github` | `lisa:github-build-intake` (Phase 3a) | native sub-issues (GraphQL) + body parentage |
35
+ | `jira` | `lisa:jira-build-intake` (Phase 3a) | native Epic → Story → Sub-task parentage |
36
+ | `linear` | `lisa:linear-build-intake` (Phase 3a) | native sub-issues via `parentId` + Project grouping |
37
+
38
+ The shim never needs to inspect the item itself — it forwards `$ARGUMENTS` verbatim and the resolved vendor scanner runs its Phase 3a gate before any claim.
39
+
23
40
  ## Rules
24
41
 
25
42
  - Single cycle per invocation — the vendor skill processes the current `Ready` set and exits.
26
43
  - The vendor skills run their own pre-flight checks (JIRA workflow transitions for the JIRA path; label namespace adoption for the GitHub and Linear paths) before processing items. Never bypass.
44
+ - **Leaf-only claim, every vendor.** Per the `leaf-only-lifecycle` rule, each vendor scanner claims leaf work units only and skips / safe-blocks a container (open child work, or a childless Epic/Story/Spike) carrying a stale build-ready role. This shim does not re-implement the gate — it relies on the vendor scanner's Phase 3a — but the contract is uniform across `jira`, `github`, and `linear` so behavior never drifts by tracker.
27
45
  - Never run two intake cycles concurrently against overlapping queues — the scheduling layer is responsible for serialization.