@codyswann/lisa 2.28.0 → 2.30.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/skills/github-build-intake/SKILL.md +56 -5
- package/plugins/lisa/skills/github-validate-issue/SKILL.md +33 -2
- 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/skills/github-build-intake/SKILL.md +56 -5
- package/plugins/src/base/skills/github-validate-issue/SKILL.md +33 -2
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.30.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: github-build-intake
|
|
3
|
-
description: "GitHub counterpart to lisa:jira-build-intake. Scans a GitHub repository for issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:github-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 product Draft → Ready → (us) In Review → Blocked|Ticketed."
|
|
3
|
+
description: "GitHub counterpart to lisa:jira-build-intake. Scans a GitHub repository for issues carrying the configured `ready` build label, claims each leaf work unit by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:github-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 product Draft → Ready → (us) In Review → Blocked|Ticketed."
|
|
4
4
|
allowed-tools: ["Skill", "Bash"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -135,7 +135,54 @@ If none of the configured role labels exist on the repo → label convention not
|
|
|
135
135
|
|
|
136
136
|
### Phase 3 — Process each ready issue (serial)
|
|
137
137
|
|
|
138
|
-
#### 3a.
|
|
138
|
+
#### 3a. Leaf-only claim gate (skip / safe-block containers)
|
|
139
|
+
|
|
140
|
+
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 role (e.g. `status: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:github-write-issue` and the validate-time S15 gate in `lisa:github-validate-issue`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
|
|
141
|
+
|
|
142
|
+
Run this gate **before** the claim relabel, for every candidate issue. Do NOT relabel, comment "Claimed", or invoke `lisa:github-agent` for an issue that fails the gate.
|
|
143
|
+
|
|
144
|
+
**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:github-read-issue` uses — native sub-issues first, then body parentage (task-list checkboxes referencing other issues, `Blocked by #<n>` / `Parent: #<n>` references):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Native sub-issues via GraphQL (same query lisa:github-read-issue uses).
|
|
148
|
+
SUBS=$(gh api graphql -f query='
|
|
149
|
+
query($org:String!,$repo:String!,$number:Int!){
|
|
150
|
+
repository(owner:$org,name:$repo){
|
|
151
|
+
issue(number:$number){
|
|
152
|
+
subIssues(first: 100) {
|
|
153
|
+
nodes { number state }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}' -F org=<org> -F repo=<repo> -F number=<number> 2>/dev/null)
|
|
158
|
+
|
|
159
|
+
# Count children still OPEN — a parent whose children are all closed is no longer
|
|
160
|
+
# holding open work and rolls up via lisa:github-read-issue's rollup, not here.
|
|
161
|
+
OPEN_CHILDREN=$(echo "$SUBS" | jq -r '[.data.repository.issue.subIssues.nodes[]? | select(.state == "OPEN")] | length' 2>/dev/null)
|
|
162
|
+
OPEN_CHILDREN=${OPEN_CHILDREN:-0}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If the GraphQL `subIssues` field is unavailable (older GHES), fall back to parsing the body for child references exactly as `lisa:github-read-issue` does, and treat the issue as a container if any referenced child issue is open. Note "GraphQL sub-issues unavailable" so the operator knows parentage was text-derived.
|
|
166
|
+
|
|
167
|
+
Classify and act (first match wins). `type:` is read from the issue's labels (`type:Epic`, `type:Story`, `type:Spike`, `type:Bug`, `type:Task`, `type:Sub-task`, `type:Improvement`):
|
|
168
|
+
|
|
169
|
+
| Condition | Class | Action |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
|
|
172
|
+
| no open children AND `type ∈ {Epic, Story, Spike}` | **Childless container-type** | **Skip / safe-block — do NOT claim** |
|
|
173
|
+
| no open children AND `type ∈ {Bug, Task, Sub-task, Improvement}` (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
|
|
174
|
+
|
|
175
|
+
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.
|
|
176
|
+
|
|
177
|
+
**Safe-block (default action for a flagged container).** Leave the build-ready role 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.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Not claimed: this issue carries the build-ready role ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready is leaf-only — 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."
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
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.
|
|
184
|
+
|
|
185
|
+
#### 3b. Claim
|
|
139
186
|
|
|
140
187
|
```bash
|
|
141
188
|
gh issue edit <number> --repo <org>/<repo> --remove-label "$READY" --add-label "$CLAIMED"
|
|
@@ -146,7 +193,7 @@ This is the idempotency lock — a re-entrant cycle's `--label $READY` filter wi
|
|
|
146
193
|
|
|
147
194
|
If the relabel fails (permission, race), log under "Errors" in the cycle summary and skip this issue. **Do not invoke the build flow on an issue you didn't successfully claim.**
|
|
148
195
|
|
|
149
|
-
####
|
|
196
|
+
#### 3c. Run the build flow
|
|
150
197
|
|
|
151
198
|
Invoke `lisa:github-agent` (the per-issue lifecycle agent) with the issue ref. `lisa:github-agent` owns:
|
|
152
199
|
- Reading the full issue graph (`lisa:github-read-issue`)
|
|
@@ -163,7 +210,7 @@ Wait for `lisa:github-agent` to return. Capture its outcome:
|
|
|
163
210
|
- **Blocked by ticket-triage ambiguities** — `lisa:github-agent` posts findings and stops. The issue stays in `$CLAIMED`. Surface to human; do not auto-relabel. Record under "Errors".
|
|
164
211
|
- **Errored** — exception, missing config, etc. Leave the issue in `$CLAIMED` for human investigation. Record under "Errors".
|
|
165
212
|
|
|
166
|
-
####
|
|
213
|
+
#### 3d. Transition to $DONE (only on Success)
|
|
167
214
|
|
|
168
215
|
If `lisa:github-agent` returned Success:
|
|
169
216
|
|
|
@@ -176,7 +223,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
|
|
|
176
223
|
|
|
177
224
|
For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever `lisa:github-agent` left it) — humans take it from there.
|
|
178
225
|
|
|
179
|
-
####
|
|
226
|
+
#### 3e. Continue
|
|
180
227
|
|
|
181
228
|
Move to the next ready issue. One issue failing does not stop others.
|
|
182
229
|
|
|
@@ -192,6 +239,8 @@ Cycle completed: <ISO timestamp>
|
|
|
192
239
|
Issues processed: <n>
|
|
193
240
|
- $DONE (build complete, PR ready): <n>
|
|
194
241
|
- <org>/<repo>#<number> <title> → PR <URL>
|
|
242
|
+
- Skipped (container — leaf-only-lifecycle): <n>
|
|
243
|
+
- <org>/<repo>#<number> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
|
|
195
244
|
- Blocked (pre-flight verify failed): <n>
|
|
196
245
|
- <org>/<repo>#<number> <title> — see issue comments
|
|
197
246
|
- Held (triage found ambiguities): <n>
|
|
@@ -204,6 +253,7 @@ Total PRs opened: <n>
|
|
|
204
253
|
|
|
205
254
|
## Idempotency & safety
|
|
206
255
|
|
|
256
|
+
- **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 safe-block comment is idempotent — a re-entrant cycle does not re-post it.
|
|
207
257
|
- **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation — no double-pickup.
|
|
208
258
|
- **No writes outside the lifecycle**: this skill only relabels `$READY → $CLAIMED` and `$CLAIMED → $DONE`. Every other label change is owned by `lisa:github-agent`.
|
|
209
259
|
- **Failure isolation**: per-issue exceptions caught and recorded; the cycle continues.
|
|
@@ -227,6 +277,7 @@ If the repo has not adopted the `status:*` label namespace, this skill cannot ru
|
|
|
227
277
|
|
|
228
278
|
## Rules
|
|
229
279
|
|
|
280
|
+
- **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 role. Skip or safe-block it (Phase 3a); never silently implement a container.
|
|
230
281
|
- Never relabel an issue the cycle didn't claim. The `$CLAIMED` label is the signature of cycle ownership.
|
|
231
282
|
- Never bypass `lisa:github-agent` to do build work directly. `lisa:github-agent` owns the per-issue lifecycle.
|
|
232
283
|
- Never auto-transition past `$DONE`. Downstream labels (terminal `status:done`, etc.) are owned by QA / PM / merge automation.
|
|
@@ -63,6 +63,8 @@ artifacts_attached: true # → requires Source Precedence section
|
|
|
63
63
|
links: [{ ref: "my-org/my-repo#99", type: "is blocked by" }] # known issue links (may be empty)
|
|
64
64
|
remote_links: [{ url: "https://github.com/.../pull/42", title: "PR #42" }]
|
|
65
65
|
journey_followup: auto # auto | none — see S11
|
|
66
|
+
build_ready: true # caller asserts the build-ready role (status:ready) is/would be applied — see S15
|
|
67
|
+
child_refs: ["my-org/my-repo#601", "my-org/my-repo#602"] # known child work (sub-issues / task-list / "Blocked by" parentage) — see S15
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
If the caller passes only an issue ref, fetch via `gh issue view <number> --repo <org>/<repo> --json number,title,body,labels,state,milestone,assignees`, parse the body sections, derive the spec fields, then run gates. The parser lives in `lisa:github-read-issue` (composition).
|
|
@@ -89,6 +91,7 @@ Each gate is tagged with a fixed `category` and a `product_relevant` boolean. Ca
|
|
|
89
91
|
| S12 Source Precedence | `design-ux` | true |
|
|
90
92
|
| S13 Relationship Search | `dependency` | true |
|
|
91
93
|
| S14 Evidence manifest binding (leaf work units) | `acceptance-criteria` | true |
|
|
94
|
+
| S15 Leaf-only build-ready | `structural` | false |
|
|
92
95
|
| F1 Issue type label exists in repo | `structural` | false |
|
|
93
96
|
| F2 Parent sub-issue exists and is the right type | `structural` | false |
|
|
94
97
|
| F3 Linked issues exist | `structural` | false |
|
|
@@ -198,6 +201,33 @@ FAIL when the Validation Journey is present but declares zero `[EVIDENCE: name]`
|
|
|
198
201
|
|
|
199
202
|
This gate depends on S11. It is `N/A` for Epic / Story / Spike (coordination containers, not work units) and for leaf units with `runtime_behavior_change = false` (doc-only / config-only / type-only). If S11 fails because the Validation Journey is absent, S14 also FAILs (there is no manifest to bind) with remediation pointing back to `lisa:github-add-journey`.
|
|
200
203
|
|
|
204
|
+
#### S15 — Leaf-only build-ready
|
|
205
|
+
|
|
206
|
+
Enforces the build-side of the vendor-neutral `leaf-only-lifecycle` rule: **only a leaf work unit may carry the build-ready role.** This is the symmetric write-side guard for the GitHub validator — a stale or hand-applied `status:ready` on a container is a lifecycle error and must FAIL here, regardless of how the issue was produced. (Mirrors the "Build-ready label is leaf-only" rule that `lisa:github-write-issue` applies at write time.)
|
|
207
|
+
|
|
208
|
+
**When the gate applies.** Run S15 whenever the issue is build-ready — i.e. `build_ready = true`, or the spec/live labels include `status:ready`. If the issue is not build-ready, S15 is `N/A` (nothing claims a non-ready issue, so the invariant is vacuous).
|
|
209
|
+
|
|
210
|
+
**Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: an item is a **container** if it has child work, whatever its declared type; otherwise the **type label** decides. Determine child work from (in order) `child_refs`, native sub-issues, body task-list checkboxes, and `Blocked by #<n>` / parent references — the same hierarchy resolution `lisa:github-read-issue` uses. When validating a live ref, query sub-issues alongside the issue fetch.
|
|
211
|
+
|
|
212
|
+
Apply this decision and FAIL the two invariant-violating cases:
|
|
213
|
+
|
|
214
|
+
1. **Container with child work + build-ready** — `issue_type ∈ {Epic, Story, Spike}` OR child work is present (any type that has children), AND build-ready. FAIL. A parent organizes work; it is never claimed and implemented directly. Its lifecycle state rolls up from its children.
|
|
215
|
+
2. **Childless container-type + build-ready** — `issue_type ∈ {Epic, Story, Spike}` with **no** child work, AND build-ready. Still FAIL: these types are coordination containers by design, and an empty one is an incomplete decomposition, not an implementable unit (the childless-parent exception in `leaf-only-lifecycle` does **not** promote an Epic/Story/Spike to build-ready).
|
|
216
|
+
|
|
217
|
+
PASS (the childless-parent exception) when the issue is build-ready and is a **leaf work unit**: `issue_type ∈ {Bug, Task, Sub-task, Improvement}` AND has **no** child work. A flat Task or Bug with no sub-issues is a valid build-ready leaf and must not be stranded.
|
|
218
|
+
|
|
219
|
+
| issue_type | has child work | build-ready | S15 |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| Bug / Task / Sub-task / Improvement | no | yes | **PASS** (leaf) |
|
|
222
|
+
| Bug / Task / Sub-task / Improvement | yes | yes | **FAIL** (structurally a container) |
|
|
223
|
+
| Epic / Story / Spike | yes | yes | **FAIL** (container with children) |
|
|
224
|
+
| Epic / Story / Spike | no | yes | **FAIL** (childless container-type, exception does not apply) |
|
|
225
|
+
| any | any | no | **N/A** (not build-ready) |
|
|
226
|
+
|
|
227
|
+
Remediation: `"Build-ready (status:ready) is leaf-only per leaf-only-lifecycle. Move status:ready off this container 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."`
|
|
228
|
+
|
|
229
|
+
`product_relevant: false` — a build-ready container is a lifecycle/decomposition error for the caller to repair, not a product question.
|
|
230
|
+
|
|
201
231
|
### Feasibility Gates (require GitHub lookups; skip in `--spec-only`)
|
|
202
232
|
|
|
203
233
|
#### F1 — Issue type label exists in repo
|
|
@@ -230,7 +260,7 @@ Per `Phase 5` of `lisa:github-write-issue`, every issue MUST carry: `type:<issue
|
|
|
230
260
|
|
|
231
261
|
## Execution
|
|
232
262
|
|
|
233
|
-
1. Parse `$ARGUMENTS`. If it's an issue ref, fetch via `gh issue view --json` and derive the spec fields. Otherwise parse the YAML spec.
|
|
263
|
+
1. Parse `$ARGUMENTS`. If it's an issue ref, fetch via `gh issue view --json` and derive the spec fields — including `build_ready` (label set contains `status:ready`) and `child_refs` (native sub-issues plus body task-list / `Blocked by #<n>` parentage, resolved as in `lisa:github-read-issue`) so S15 can classify the issue. Otherwise parse the YAML spec.
|
|
234
264
|
2. Confirm `gh auth status` succeeds before any feasibility gate runs.
|
|
235
265
|
3. Run every Specification gate in order. Collect PASS / FAIL / N/A with a one-line reason.
|
|
236
266
|
4. Unless the caller passed `--spec-only`, run every Feasibility gate.
|
|
@@ -258,6 +288,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
|
|
|
258
288
|
- [PASS|FAIL|N/A] S12 Source Precedence — <one-line reason>
|
|
259
289
|
- [PASS|FAIL|N/A] S13 Relationship Search — <one-line reason>
|
|
260
290
|
- [PASS|FAIL|N/A] S14 Evidence manifest binding — <one-line reason>
|
|
291
|
+
- [PASS|FAIL|N/A] S15 Leaf-only build-ready — <one-line reason>
|
|
261
292
|
|
|
262
293
|
### Feasibility Gates (omit this section when --spec-only)
|
|
263
294
|
- [PASS|FAIL|N/A] F1 Issue type label exists in repo — <one-line reason>
|
|
@@ -283,7 +314,7 @@ The verdict is `PASS` if every applicable gate is `PASS`. Any `FAIL` makes the v
|
|
|
283
314
|
|
|
284
315
|
Same shape and meaning as `lisa:jira-validate-ticket` so downstream PRD-intake skills (Notion, Confluence, Linear, GitHub) can format comments uniformly:
|
|
285
316
|
|
|
286
|
-
- **gate**: the gate ID (`S1`–`
|
|
317
|
+
- **gate**: the gate ID (`S1`–`S15`, `F1`–`F4`).
|
|
287
318
|
- **category**: the gate's fixed category from the table.
|
|
288
319
|
- **product_relevant**: matches the gate's table entry. `false` means the failure is an internal data-quality problem the caller should fix without bothering product.
|
|
289
320
|
- **what**: plain-language, product-readable.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-build-intake
|
|
3
|
-
description: "GitHub counterpart to lisa:jira-build-intake. Scans a GitHub repository for issues carrying the configured `ready` build label, claims each by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:github-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 product Draft → Ready → (us) In Review → Blocked|Ticketed."
|
|
3
|
+
description: "GitHub counterpart to lisa:jira-build-intake. Scans a GitHub repository for issues carrying the configured `ready` build label, claims each leaf work unit by relabeling to the configured `claimed` label, runs the implementation/build flow via lisa:github-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 product Draft → Ready → (us) In Review → Blocked|Ticketed."
|
|
4
4
|
allowed-tools: ["Skill", "Bash"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -135,7 +135,54 @@ If none of the configured role labels exist on the repo → label convention not
|
|
|
135
135
|
|
|
136
136
|
### Phase 3 — Process each ready issue (serial)
|
|
137
137
|
|
|
138
|
-
#### 3a.
|
|
138
|
+
#### 3a. Leaf-only claim gate (skip / safe-block containers)
|
|
139
|
+
|
|
140
|
+
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 role (e.g. `status: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:github-write-issue` and the validate-time S15 gate in `lisa:github-validate-issue`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
|
|
141
|
+
|
|
142
|
+
Run this gate **before** the claim relabel, for every candidate issue. Do NOT relabel, comment "Claimed", or invoke `lisa:github-agent` for an issue that fails the gate.
|
|
143
|
+
|
|
144
|
+
**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:github-read-issue` uses — native sub-issues first, then body parentage (task-list checkboxes referencing other issues, `Blocked by #<n>` / `Parent: #<n>` references):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Native sub-issues via GraphQL (same query lisa:github-read-issue uses).
|
|
148
|
+
SUBS=$(gh api graphql -f query='
|
|
149
|
+
query($org:String!,$repo:String!,$number:Int!){
|
|
150
|
+
repository(owner:$org,name:$repo){
|
|
151
|
+
issue(number:$number){
|
|
152
|
+
subIssues(first: 100) {
|
|
153
|
+
nodes { number state }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}' -F org=<org> -F repo=<repo> -F number=<number> 2>/dev/null)
|
|
158
|
+
|
|
159
|
+
# Count children still OPEN — a parent whose children are all closed is no longer
|
|
160
|
+
# holding open work and rolls up via lisa:github-read-issue's rollup, not here.
|
|
161
|
+
OPEN_CHILDREN=$(echo "$SUBS" | jq -r '[.data.repository.issue.subIssues.nodes[]? | select(.state == "OPEN")] | length' 2>/dev/null)
|
|
162
|
+
OPEN_CHILDREN=${OPEN_CHILDREN:-0}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If the GraphQL `subIssues` field is unavailable (older GHES), fall back to parsing the body for child references exactly as `lisa:github-read-issue` does, and treat the issue as a container if any referenced child issue is open. Note "GraphQL sub-issues unavailable" so the operator knows parentage was text-derived.
|
|
166
|
+
|
|
167
|
+
Classify and act (first match wins). `type:` is read from the issue's labels (`type:Epic`, `type:Story`, `type:Spike`, `type:Bug`, `type:Task`, `type:Sub-task`, `type:Improvement`):
|
|
168
|
+
|
|
169
|
+
| Condition | Class | Action |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Skip / safe-block — do NOT claim** |
|
|
172
|
+
| no open children AND `type ∈ {Epic, Story, Spike}` | **Childless container-type** | **Skip / safe-block — do NOT claim** |
|
|
173
|
+
| no open children AND `type ∈ {Bug, Task, Sub-task, Improvement}` (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
|
|
174
|
+
|
|
175
|
+
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.
|
|
176
|
+
|
|
177
|
+
**Safe-block (default action for a flagged container).** Leave the build-ready role 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.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Not claimed: this issue carries the build-ready role ($READY) but is a container with open child work (or a childless Epic/Story/Spike), which violates the leaf-only-lifecycle rule. Build-ready is leaf-only — 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."
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
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.
|
|
184
|
+
|
|
185
|
+
#### 3b. Claim
|
|
139
186
|
|
|
140
187
|
```bash
|
|
141
188
|
gh issue edit <number> --repo <org>/<repo> --remove-label "$READY" --add-label "$CLAIMED"
|
|
@@ -146,7 +193,7 @@ This is the idempotency lock — a re-entrant cycle's `--label $READY` filter wi
|
|
|
146
193
|
|
|
147
194
|
If the relabel fails (permission, race), log under "Errors" in the cycle summary and skip this issue. **Do not invoke the build flow on an issue you didn't successfully claim.**
|
|
148
195
|
|
|
149
|
-
####
|
|
196
|
+
#### 3c. Run the build flow
|
|
150
197
|
|
|
151
198
|
Invoke `lisa:github-agent` (the per-issue lifecycle agent) with the issue ref. `lisa:github-agent` owns:
|
|
152
199
|
- Reading the full issue graph (`lisa:github-read-issue`)
|
|
@@ -163,7 +210,7 @@ Wait for `lisa:github-agent` to return. Capture its outcome:
|
|
|
163
210
|
- **Blocked by ticket-triage ambiguities** — `lisa:github-agent` posts findings and stops. The issue stays in `$CLAIMED`. Surface to human; do not auto-relabel. Record under "Errors".
|
|
164
211
|
- **Errored** — exception, missing config, etc. Leave the issue in `$CLAIMED` for human investigation. Record under "Errors".
|
|
165
212
|
|
|
166
|
-
####
|
|
213
|
+
#### 3d. Transition to $DONE (only on Success)
|
|
167
214
|
|
|
168
215
|
If `lisa:github-agent` returned Success:
|
|
169
216
|
|
|
@@ -176,7 +223,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
|
|
|
176
223
|
|
|
177
224
|
For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever `lisa:github-agent` left it) — humans take it from there.
|
|
178
225
|
|
|
179
|
-
####
|
|
226
|
+
#### 3e. Continue
|
|
180
227
|
|
|
181
228
|
Move to the next ready issue. One issue failing does not stop others.
|
|
182
229
|
|
|
@@ -192,6 +239,8 @@ Cycle completed: <ISO timestamp>
|
|
|
192
239
|
Issues processed: <n>
|
|
193
240
|
- $DONE (build complete, PR ready): <n>
|
|
194
241
|
- <org>/<repo>#<number> <title> → PR <URL>
|
|
242
|
+
- Skipped (container — leaf-only-lifecycle): <n>
|
|
243
|
+
- <org>/<repo>#<number> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
|
|
195
244
|
- Blocked (pre-flight verify failed): <n>
|
|
196
245
|
- <org>/<repo>#<number> <title> — see issue comments
|
|
197
246
|
- Held (triage found ambiguities): <n>
|
|
@@ -204,6 +253,7 @@ Total PRs opened: <n>
|
|
|
204
253
|
|
|
205
254
|
## Idempotency & safety
|
|
206
255
|
|
|
256
|
+
- **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 safe-block comment is idempotent — a re-entrant cycle does not re-post it.
|
|
207
257
|
- **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation — no double-pickup.
|
|
208
258
|
- **No writes outside the lifecycle**: this skill only relabels `$READY → $CLAIMED` and `$CLAIMED → $DONE`. Every other label change is owned by `lisa:github-agent`.
|
|
209
259
|
- **Failure isolation**: per-issue exceptions caught and recorded; the cycle continues.
|
|
@@ -227,6 +277,7 @@ If the repo has not adopted the `status:*` label namespace, this skill cannot ru
|
|
|
227
277
|
|
|
228
278
|
## Rules
|
|
229
279
|
|
|
280
|
+
- **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 role. Skip or safe-block it (Phase 3a); never silently implement a container.
|
|
230
281
|
- Never relabel an issue the cycle didn't claim. The `$CLAIMED` label is the signature of cycle ownership.
|
|
231
282
|
- Never bypass `lisa:github-agent` to do build work directly. `lisa:github-agent` owns the per-issue lifecycle.
|
|
232
283
|
- Never auto-transition past `$DONE`. Downstream labels (terminal `status:done`, etc.) are owned by QA / PM / merge automation.
|
|
@@ -63,6 +63,8 @@ artifacts_attached: true # → requires Source Precedence section
|
|
|
63
63
|
links: [{ ref: "my-org/my-repo#99", type: "is blocked by" }] # known issue links (may be empty)
|
|
64
64
|
remote_links: [{ url: "https://github.com/.../pull/42", title: "PR #42" }]
|
|
65
65
|
journey_followup: auto # auto | none — see S11
|
|
66
|
+
build_ready: true # caller asserts the build-ready role (status:ready) is/would be applied — see S15
|
|
67
|
+
child_refs: ["my-org/my-repo#601", "my-org/my-repo#602"] # known child work (sub-issues / task-list / "Blocked by" parentage) — see S15
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
If the caller passes only an issue ref, fetch via `gh issue view <number> --repo <org>/<repo> --json number,title,body,labels,state,milestone,assignees`, parse the body sections, derive the spec fields, then run gates. The parser lives in `lisa:github-read-issue` (composition).
|
|
@@ -89,6 +91,7 @@ Each gate is tagged with a fixed `category` and a `product_relevant` boolean. Ca
|
|
|
89
91
|
| S12 Source Precedence | `design-ux` | true |
|
|
90
92
|
| S13 Relationship Search | `dependency` | true |
|
|
91
93
|
| S14 Evidence manifest binding (leaf work units) | `acceptance-criteria` | true |
|
|
94
|
+
| S15 Leaf-only build-ready | `structural` | false |
|
|
92
95
|
| F1 Issue type label exists in repo | `structural` | false |
|
|
93
96
|
| F2 Parent sub-issue exists and is the right type | `structural` | false |
|
|
94
97
|
| F3 Linked issues exist | `structural` | false |
|
|
@@ -198,6 +201,33 @@ FAIL when the Validation Journey is present but declares zero `[EVIDENCE: name]`
|
|
|
198
201
|
|
|
199
202
|
This gate depends on S11. It is `N/A` for Epic / Story / Spike (coordination containers, not work units) and for leaf units with `runtime_behavior_change = false` (doc-only / config-only / type-only). If S11 fails because the Validation Journey is absent, S14 also FAILs (there is no manifest to bind) with remediation pointing back to `lisa:github-add-journey`.
|
|
200
203
|
|
|
204
|
+
#### S15 — Leaf-only build-ready
|
|
205
|
+
|
|
206
|
+
Enforces the build-side of the vendor-neutral `leaf-only-lifecycle` rule: **only a leaf work unit may carry the build-ready role.** This is the symmetric write-side guard for the GitHub validator — a stale or hand-applied `status:ready` on a container is a lifecycle error and must FAIL here, regardless of how the issue was produced. (Mirrors the "Build-ready label is leaf-only" rule that `lisa:github-write-issue` applies at write time.)
|
|
207
|
+
|
|
208
|
+
**When the gate applies.** Run S15 whenever the issue is build-ready — i.e. `build_ready = true`, or the spec/live labels include `status:ready`. If the issue is not build-ready, S15 is `N/A` (nothing claims a non-ready issue, so the invariant is vacuous).
|
|
209
|
+
|
|
210
|
+
**Resolve container vs. leaf — structural first, then nominal.** Per `leaf-only-lifecycle` the classification is structural: an item is a **container** if it has child work, whatever its declared type; otherwise the **type label** decides. Determine child work from (in order) `child_refs`, native sub-issues, body task-list checkboxes, and `Blocked by #<n>` / parent references — the same hierarchy resolution `lisa:github-read-issue` uses. When validating a live ref, query sub-issues alongside the issue fetch.
|
|
211
|
+
|
|
212
|
+
Apply this decision and FAIL the two invariant-violating cases:
|
|
213
|
+
|
|
214
|
+
1. **Container with child work + build-ready** — `issue_type ∈ {Epic, Story, Spike}` OR child work is present (any type that has children), AND build-ready. FAIL. A parent organizes work; it is never claimed and implemented directly. Its lifecycle state rolls up from its children.
|
|
215
|
+
2. **Childless container-type + build-ready** — `issue_type ∈ {Epic, Story, Spike}` with **no** child work, AND build-ready. Still FAIL: these types are coordination containers by design, and an empty one is an incomplete decomposition, not an implementable unit (the childless-parent exception in `leaf-only-lifecycle` does **not** promote an Epic/Story/Spike to build-ready).
|
|
216
|
+
|
|
217
|
+
PASS (the childless-parent exception) when the issue is build-ready and is a **leaf work unit**: `issue_type ∈ {Bug, Task, Sub-task, Improvement}` AND has **no** child work. A flat Task or Bug with no sub-issues is a valid build-ready leaf and must not be stranded.
|
|
218
|
+
|
|
219
|
+
| issue_type | has child work | build-ready | S15 |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| Bug / Task / Sub-task / Improvement | no | yes | **PASS** (leaf) |
|
|
222
|
+
| Bug / Task / Sub-task / Improvement | yes | yes | **FAIL** (structurally a container) |
|
|
223
|
+
| Epic / Story / Spike | yes | yes | **FAIL** (container with children) |
|
|
224
|
+
| Epic / Story / Spike | no | yes | **FAIL** (childless container-type, exception does not apply) |
|
|
225
|
+
| any | any | no | **N/A** (not build-ready) |
|
|
226
|
+
|
|
227
|
+
Remediation: `"Build-ready (status:ready) is leaf-only per leaf-only-lifecycle. Move status:ready off this container 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."`
|
|
228
|
+
|
|
229
|
+
`product_relevant: false` — a build-ready container is a lifecycle/decomposition error for the caller to repair, not a product question.
|
|
230
|
+
|
|
201
231
|
### Feasibility Gates (require GitHub lookups; skip in `--spec-only`)
|
|
202
232
|
|
|
203
233
|
#### F1 — Issue type label exists in repo
|
|
@@ -230,7 +260,7 @@ Per `Phase 5` of `lisa:github-write-issue`, every issue MUST carry: `type:<issue
|
|
|
230
260
|
|
|
231
261
|
## Execution
|
|
232
262
|
|
|
233
|
-
1. Parse `$ARGUMENTS`. If it's an issue ref, fetch via `gh issue view --json` and derive the spec fields. Otherwise parse the YAML spec.
|
|
263
|
+
1. Parse `$ARGUMENTS`. If it's an issue ref, fetch via `gh issue view --json` and derive the spec fields — including `build_ready` (label set contains `status:ready`) and `child_refs` (native sub-issues plus body task-list / `Blocked by #<n>` parentage, resolved as in `lisa:github-read-issue`) so S15 can classify the issue. Otherwise parse the YAML spec.
|
|
234
264
|
2. Confirm `gh auth status` succeeds before any feasibility gate runs.
|
|
235
265
|
3. Run every Specification gate in order. Collect PASS / FAIL / N/A with a one-line reason.
|
|
236
266
|
4. Unless the caller passed `--spec-only`, run every Feasibility gate.
|
|
@@ -258,6 +288,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
|
|
|
258
288
|
- [PASS|FAIL|N/A] S12 Source Precedence — <one-line reason>
|
|
259
289
|
- [PASS|FAIL|N/A] S13 Relationship Search — <one-line reason>
|
|
260
290
|
- [PASS|FAIL|N/A] S14 Evidence manifest binding — <one-line reason>
|
|
291
|
+
- [PASS|FAIL|N/A] S15 Leaf-only build-ready — <one-line reason>
|
|
261
292
|
|
|
262
293
|
### Feasibility Gates (omit this section when --spec-only)
|
|
263
294
|
- [PASS|FAIL|N/A] F1 Issue type label exists in repo — <one-line reason>
|
|
@@ -283,7 +314,7 @@ The verdict is `PASS` if every applicable gate is `PASS`. Any `FAIL` makes the v
|
|
|
283
314
|
|
|
284
315
|
Same shape and meaning as `lisa:jira-validate-ticket` so downstream PRD-intake skills (Notion, Confluence, Linear, GitHub) can format comments uniformly:
|
|
285
316
|
|
|
286
|
-
- **gate**: the gate ID (`S1`–`
|
|
317
|
+
- **gate**: the gate ID (`S1`–`S15`, `F1`–`F4`).
|
|
287
318
|
- **category**: the gate's fixed category from the table.
|
|
288
319
|
- **product_relevant**: matches the gate's table entry. `false` means the failure is an internal data-quality problem the caller should fix without bothering product.
|
|
289
320
|
- **what**: plain-language, product-readable.
|