@codyswann/lisa 2.59.1 → 2.60.1

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 (30) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa/rules/config-resolution.md +10 -7
  6. package/plugins/lisa/rules/leaf-only-lifecycle.md +3 -3
  7. package/plugins/lisa/rules/prd-lifecycle-rollup.md +13 -0
  8. package/plugins/lisa/skills/github-build-intake/SKILL.md +39 -17
  9. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +3 -3
  10. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  12. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  26. package/plugins/src/base/rules/config-resolution.md +10 -7
  27. package/plugins/src/base/rules/leaf-only-lifecycle.md +3 -3
  28. package/plugins/src/base/rules/prd-lifecycle-rollup.md +13 -0
  29. package/plugins/src/base/skills/github-build-intake/SKILL.md +39 -17
  30. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +3 -3
package/README.md CHANGED
@@ -80,6 +80,8 @@ Most users only ever call `/lisa:research`, `/lisa:plan`, and `/lisa:implement`.
80
80
  | --- | --- |
81
81
  | `/lisa:intake <queue-url>` | Scans a Ready queue (Notion PRD database, JIRA project, GitHub repo, Linear team, Confluence space) and dispatches each item through the right lifecycle command. Designed as the cron target for unattended runs. |
82
82
 
83
+ PRD intake records generated work with native hierarchy where the source and tracker support it, and with a durable generated-work fallback everywhere else. The vendor matrix for GitHub, Linear, JIRA/Atlassian, Confluence, Notion, and cross-vendor queues lives in `plugins/src/base/rules/prd-lifecycle-rollup.md`.
84
+
83
85
  ### Maintenance and Operations
84
86
 
85
87
  | Command | What it does |
package/package.json CHANGED
@@ -82,7 +82,7 @@
82
82
  "lodash": ">=4.18.1"
83
83
  },
84
84
  "name": "@codyswann/lisa",
85
- "version": "2.59.1",
85
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -54,7 +54,8 @@ fi
54
54
  "in_review": "<page-id>",
55
55
  "blocked": "<page-id>",
56
56
  "ticketed": "<page-id>",
57
- "shipped": "<page-id>"
57
+ "shipped": "<page-id>",
58
+ "verified": "<page-id>"
58
59
  },
59
60
  "dashboardPageId": "<page-id>",
60
61
  "feedbackPageId": "<page-id>",
@@ -75,7 +76,7 @@ fi
75
76
  "draft": "prd-draft",
76
77
  "ready": "prd-ready", "in_review": "prd-in-review",
77
78
  "blocked": "prd-blocked", "ticketed": "prd-ticketed",
78
- "shipped": "prd-shipped",
79
+ "shipped": "prd-shipped", "verified": "prd-verified",
79
80
  "sentinel": "prd-intake-feedback",
80
81
  "rollup": { "closeOnShipped": false }
81
82
  }
@@ -87,7 +88,8 @@ fi
87
88
  "statusProperty": "Status",
88
89
  "values": {
89
90
  "draft": "Draft", "ready": "Ready", "in_review": "In Review",
90
- "blocked": "Blocked", "ticketed": "Ticketed", "shipped": "Shipped"
91
+ "blocked": "Blocked", "ticketed": "Ticketed", "shipped": "Shipped",
92
+ "verified": "Verified"
91
93
  },
92
94
  "rollup": { "closeOnShipped": false }
93
95
  },
@@ -106,7 +108,7 @@ fi
106
108
  "draft": "prd-draft",
107
109
  "ready": "prd-ready", "in_review": "prd-in-review",
108
110
  "blocked": "prd-blocked", "ticketed": "prd-ticketed",
109
- "shipped": "prd-shipped",
111
+ "shipped": "prd-shipped", "verified": "prd-verified",
110
112
  "sentinel": "prd-intake-feedback",
111
113
  "rollup": { "closeOnShipped": false }
112
114
  }
@@ -171,7 +173,7 @@ When `tracker = "github"` AND `source = "github"` (self-host), both reads and wr
171
173
  | `notion.workspaceId` | `source = "notion"` | **committed** | Workspace identifier (Notion workspace UUID, or a stable human slug the user picks at setup). Same for every developer on the project. Used as the keychain `account` value when looking up the Notion API token, so each project's `notion-access` finds the right per-workspace token. |
172
174
  | `notion.prdDatabaseId` | `source = "notion"` | **committed** | Notion database ID (UUID, dashes optional). The database is the PRD queue. Same for every developer on the project. |
173
175
  | `notion.statusProperty` | `source = "notion"` | **committed** | Name of the database property that drives the lifecycle. Defaults to `"Status"` if absent. |
174
- | `notion.values` | optional | **committed** | Map of role → Notion status-value name (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`). Defaults match the role names in title case. Override here if your Notion DB uses different value names. |
176
+ | `notion.values` | optional | **committed** | Map of role → Notion status-value name (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`, `verified`). Defaults match the role names in title case. Override here if your Notion DB uses different value names. |
175
177
 
176
178
  #### `linear`
177
179
 
@@ -210,6 +212,7 @@ Every lifecycle skill operates on a fixed set of **roles** (`ready`, `claimed`,
210
212
  | `blocked` | Validation failed; clarifying-comments posted | `Blocked` (status) | `prd-blocked` (label) |
211
213
  | `ticketed` | Validated and tickets created | `Ticketed` (status) | `prd-ticketed` (label) |
212
214
  | `shipped` | All child tickets shipped | `Shipped` (status) | `prd-shipped` (label) |
215
+ | `verified` | Shipped product empirically checked against the PRD | `Verified` (status) | `prd-verified` (label); parent-page lookup (Confluence) |
213
216
  | `sentinel` | (PRD-intake feedback issue marker, GitHub/Linear self-host only) | — | `prd-intake-feedback` |
214
217
 
215
218
  ### PRD rollup config (`prd.rollup`)
@@ -245,7 +248,7 @@ The true terminal `done` value is also the only value that triggers provider-nat
245
248
  ### What's configurable, what's not
246
249
 
247
250
  - **Status / label NAMES** are configurable per project — that's the point of the vocabulary maps.
248
- - **Role SEMANTICS and TRANSITIONS** are not. The build lifecycle is always `ready → claimed → done` (with optional `review` for label-driven systems). The PRD lifecycle is always `ready → in_review → (blocked | ticketed) → shipped`. Lisa skills hardcode these transitions because they encode the design intent of the framework, not the project's preferences.
251
+ - **Role SEMANTICS and TRANSITIONS** are not. The build lifecycle is always `ready → claimed → done` (with optional `review` for label-driven systems). The PRD lifecycle is always `ready → in_review → (blocked | ticketed) → shipped`, then verification may move `shipped → verified` on a pass or `shipped → blocked` on a failed verification. `verified` is terminal and product-owned like `draft` and `shipped`; Lisa does not add `prd-verifying` or `prd-verification-failed` states. Skills hardcode these transitions because they encode the design intent of the framework, not the project's preferences.
249
252
  - **Extra statuses/labels** the project uses outside these roles are fine — lisa never touches them.
250
253
 
251
254
  ### Defaults vs. requirements
@@ -324,7 +327,7 @@ Initiatives (Linear's cross-Project rollup) are NOT used — they're intended fo
324
327
 
325
328
  When `github-to-tracker` is invoked AND `tracker = "github"`, both reads and writes hit the same GitHub repo. Label namespaces are kept separate so the two flows don't collide:
326
329
 
327
- - PRD-source labels: `prd-draft`, `prd-ready`, `prd-in-review`, `prd-blocked`, `prd-ticketed`, `prd-shipped` — owned by `github-prd-intake` and the human PM.
330
+ - PRD-source labels: `prd-draft`, `prd-ready`, `prd-in-review`, `prd-blocked`, `prd-ticketed`, `prd-shipped`, `prd-verified` — owned by `github-prd-intake`, `verify-prd`, and the human PM.
328
331
  - Build-queue labels: `status:ready`, `status:in-progress`, `status:code-review`, `status:on-dev`, `status:done` — owned by `github-build-intake` and `github-agent`.
329
332
  - Sentinel issue label: `prd-intake-feedback` — owned by `github-prd-intake`.
330
333
 
@@ -10,7 +10,7 @@ The first two are the same idea seen from opposite ends: a parent never enters t
10
10
 
11
11
  ## Why this exists
12
12
 
13
- Build intake claims and implements whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent will try to implement the container itself — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
13
+ Build intake processes whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent may try to implement the container itself unless intake gates it first — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
14
14
 
15
15
  The fix is not vendor-specific. It belongs here, in a cross-vendor rule, and every writer / validator / intake path enforces it.
16
16
 
@@ -43,7 +43,7 @@ Where a vendor lacks native hierarchy for a given pair, a text link or metadata
43
43
 
44
44
  - **At decomposition / write time** — when a PRD decomposes into a hierarchy, only the leaf work units receive the `ready` role (status/label). Parent containers (Epic, Story, Project, and any parent issue that has child work) are created in their normal non-ready state and never receive the build-ready role directly. The leaves are what downstream build intake will claim.
45
45
  - **At validate time** — the `*-validate-*` gate FAILs any container carrying the build-ready role. This is the symmetric write-side guard: a stale or hand-applied build-ready role on a parent is a lifecycle error.
46
- - **At claim time** — build intake scans for the `ready` role but claims **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not claimed**: intake either skips it or safely blocks it with a clear lifecycle-repair message (move the role to the leaves; roll the parent up). Intake never silently implements a container.
46
+ - **At claim time** — build intake scans for the `ready` role but dispatches **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not dispatched**: intake either moves it into the vendor's parent/container progress state or safely blocks it with a clear lifecycle-repair message. Intake never silently implements a container.
47
47
 
48
48
  The permission boundary is the maintainer-applied build-ready role, not authorship — do not add author-based guards (PRD #522 non-goal). This rule narrows *what* may carry that role, not *who* may apply it.
49
49
 
@@ -108,7 +108,7 @@ Skills that enforce this invariant or perform rollup cite this rule by slug (the
108
108
 
109
109
  - **Decomposition / write** (`*-to-tracker`, `*-write-*`) — apply the `ready` role to leaves only; never to containers.
110
110
  - **Validate** (`*-validate-*`) — FAIL a container carrying the build-ready role; FAIL a childless Epic/Story/Spike marked build-ready.
111
- - **Build intake** (`*-build-intake`, `tracker-build-intake`) — claim leaves only; skip or safe-block containers with stale build-ready roles.
111
+ - **Build intake** (`*-build-intake`, `tracker-build-intake`) — dispatch leaves only; move or safe-block containers with stale build-ready roles according to vendor lifecycle semantics.
112
112
  - **Rollup** — derive parent state from children per the state machine above.
113
113
  - **Terminal native closure** (`*-build-intake`, terminal helpers) — after a leaf reaches the true terminal `done` role, finalize it through the provider's native close / complete / resolve mechanism where available; never do this for intermediate env states.
114
114
 
@@ -46,6 +46,19 @@ The relationship is recorded with the source tool's **native hierarchy first**,
46
46
 
47
47
  The documented-section fallback is always written so the generated child set is readable later without relying only on free-form comments. Comment summaries are still useful human-facing audit trails and are not removed (PRD #525 non-goal).
48
48
 
49
+ ### Vendor relationship and closure matrix
50
+
51
+ Use this matrix when implementing or auditing a PRD-source integration. It describes the native relationship to prefer, the documented fallback that must remain durable, and the closure behavior after the all-terminal rollup condition is met.
52
+
53
+ | PRD source / tracker shape | Native hierarchy mechanism | Documented fallback | Closure behavior |
54
+ |---|---|---|---|
55
+ | **GitHub Issues (source and tracker in the same repo)** | Link generated top-level work as native sub-issues of the PRD issue when the repo supports GitHub sub-issues. The PRD's direct sub-issues are the generated top-level child set; descendants under those children are excluded from PRD rollup. | Always maintain the machine-readable `## Tickets` / `## Generated Work` section keyed by `owner/repo#number`, and use it when sub-issues are unavailable, disabled, or incomplete. | Rollup changes the PRD lifecycle label from `prd-ticketed` to `prd-shipped` when every required generated top-level issue is terminal. Close the PRD issue only when `github.labels.prd.rollup.closeOnShipped` is `true`; the default is to leave it open. |
56
+ | **Linear** | Use Linear native grouping where the PRD also lives in Linear: generated top-level Issues are related through `parentId`, or a generated Project groups the generated Issues. Read only top-level Issues for PRD rollup. | Use the PRD's machine-readable generated-work section when the destination tracker is not Linear or native project / parent relationships cannot represent the PRD-to-work link. Entries are keyed by Linear issue or project identifier / UUID. | Rollup removes `prd-ticketed` and adds `prd-shipped` to the PRD project when every required generated top-level Issue / Project is completed. Archive or close the project only when `linear.labels.prd.rollup.closeOnShipped` is `true`; otherwise leave it active. |
57
+ | **JIRA / Atlassian tracker work** | Prefer native Epic / parent fields, or a documented issue-link type where the PRD-to-Epic relationship can be represented in JIRA. JIRA child terminal state is read from the issue's Done status category. | If the PRD source is not JIRA or the native link cannot attach tracker work to the PRD artifact, record generated top-level JIRA issue keys in the PRD's generated-work section. | Rollup may transition a JIRA-hosted PRD to the configured shipped / Done status only after all required generated top-level issues are in the Done status category. Native resolution / closure is config-gated through the PRD rollup close-on-shipped setting. |
58
+ | **Confluence PRDs** | No native issue hierarchy for tracker work. Confluence's native structure is used for PRD lifecycle lanes by parent page, not for destination work children. | The Confluence page's machine-readable `## Tickets` / `## Generated Work` section is the primary child source. Top-level generated work entries are keyed by destination ticket ref. | Rollup re-parents the PRD page from the `ticketed` parent to the `shipped` parent when every required generated-work entry is marked done. Archive the page only when `confluence.rollup.closeOnShipped` is `true`; otherwise leave it active. |
59
+ | **Notion PRDs** | No native issue hierarchy for tracker work. Notion's native status/select property stores PRD lifecycle state, not generated ticket parentage. | The Notion page's machine-readable `## Tickets` / `## Generated Work` section is the primary child source. Top-level generated work entries are keyed by destination ticket ref. | Rollup sets the configured Notion status/select value to `Shipped` when every required generated-work entry is marked done. Archive the page only when `notion.rollup.closeOnShipped` is `true`; otherwise leave it active. |
60
+ | **Cross-vendor PRD -> tracker** | Native hierarchy cannot cross systems, so the destination ticket is not expected to become a native child of the PRD artifact. Native tracker hierarchy still applies inside the destination system among generated Epics, Stories, and Sub-tasks. | The source PRD artifact's generated-work section is authoritative for the PRD-to-top-level-work child set, and each entry links to the destination ticket URL / key. | The PRD source owns the final lifecycle transition and optional close/archive. It evaluates terminal state using the destination tracker's predicate, then applies the source vendor's `shipped` transition and close-on-shipped behavior. |
61
+
49
62
  ## Per-vendor terminal-state predicate
50
63
 
51
64
  A generated top-level child is **terminal** (counts as done for rollup) when it reaches the source/tracker's done/shipped state. The predicate is vendor-specific; the *semantics* ("this child has shipped") are not:
@@ -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 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."
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 moved out of the ready pickup queue into the configured `claimed` label with a lifecycle-repair comment, never dispatched to lisa:github-agent. The `ready` label is the human-flipped signal that an issue is truly ready for direct development pickup — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash"]
5
5
  ---
6
6
 
@@ -135,13 +135,13 @@ 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. Leaf-only claim gate (skip / safe-block containers)
138
+ #### 3a. Leaf-only claim gate (repair containers)
139
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.**
140
+ Build intake dispatches **only independently implementable leaf work units** to the build agent. 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 dispatched** — intake moves it out of the pickup queue by replacing `$READY` with `$CLAIMED`, then posts 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
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.
142
+ Run this gate **before** the leaf claim relabel, for every candidate issue. Do NOT comment "Claimed" or invoke `lisa:github-agent` for an issue that fails the gate. A container repair still changes labels: remove `$READY`, add `$CLAIMED`, and explain that parent/container `$CLAIMED` means rollup/build-lane progress through child/leaf work, not direct implementation.
143
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):
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, `Parent: #<n>` references). Dependency links such as `Blocked by:` are not parentage; they are handled by the active dependency hold gate below.
145
145
 
146
146
  ```bash
147
147
  # Native sub-issues via GraphQL (same query lisa:github-read-issue uses).
@@ -168,20 +168,39 @@ Classify and act (first match wins). `type:` is read from the issue's labels (`t
168
168
 
169
169
  | Condition | Class | Action |
170
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** |
171
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Move to `$CLAIMED` as lifecycle repair — do NOT dispatch** |
172
+ | no open children AND `type ∈ {Epic, Story, Spike}` | **Childless container-type** | **Move to `$CLAIMED` as lifecycle repair — do NOT dispatch** |
173
173
  | no open children AND `type ∈ {Bug, Task, Sub-task, Improvement}` (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
174
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.
175
+ The childless-parent exception is narrow: childlessness enables direct build-agent dispatch **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 moved out of the ready pickup queue for repair/rollup and never dispatched.
176
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.
177
+ **Lifecycle repair (default action for a flagged container).** Move the issue out of the pickup queue by removing `$READY` and adding `$CLAIMED`, post a single lifecycle-repair comment, and record the issue under "Repaired (container)" in the summary. Do NOT invoke `lisa:github-agent`. 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
178
 
179
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."
180
+ gh issue edit <number> --repo <org>/<repo> --remove-label "$READY" --add-label "$CLAIMED"
181
+ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Lifecycle repair: this issue carried the build-ready role ($READY) but is a parent/container with open child work (or a childless Epic/Story/Spike). I moved it to $CLAIMED without invoking the build agent. For parent/container issues, $CLAIMED means rollup/build-lane progress through child/leaf work; direct implementation must happen on leaf issues. Build-ready is leaf-only per leaf-only-lifecycle — move $READY onto its leaf children, or decompose/reclassify a childless Epic/Story/Spike."
181
182
  ```
182
183
 
183
184
  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
 
186
+ **Active dependency hold gate.** After the leaf-only gate passes, but still before the claim relabel, parse explicit blocker relationships from the issue body and durable Lisa relationship sections. Support these forms at minimum:
187
+
188
+ - `Blocked by: #123`
189
+ - `Blocked by: #123, #456`
190
+ - `Blocked by: owner/repo#123`
191
+ - `Blocked by: https://github.com/owner/repo/issues/123`
192
+
193
+ Resolve local `#123` references against the candidate issue's repo. Resolve qualified refs and GitHub issue URLs against their named repo. For each blocker, read the blocker issue's status labels with `gh issue view <number> --repo <owner>/<repo> --json labels,state`.
194
+
195
+ Default cleared blocker labels for GitHub build intake are:
196
+
197
+ - `status:code-review`
198
+ - `status:on-dev`
199
+ - `status:on-stg`
200
+ - `status:done`
201
+
202
+ A blocker is active if it is open and has no cleared status label. Treat `status:ready`, `status:in-progress`, missing status labels, and inaccessible blockers as active. Closed blockers are cleared. If any blocker is active, skip the candidate without changing lifecycle labels, without posting "Claimed", and without invoking `lisa:github-agent`. Record it under "Skipped (active blockers)" in the summary and include the active blocker refs. Keep any dependency-hold comment idempotent with a `[claude-build-intake]` prefix.
203
+
185
204
  #### 3b. Claim
186
205
 
187
206
  ```bash
@@ -251,8 +270,10 @@ Cycle completed: <ISO timestamp>
251
270
  Issues processed: <n>
252
271
  - $DONE (build complete, PR ready): <n>
253
272
  - <org>/<repo>#<number> <title> → PR <URL>
254
- - Skipped (container — leaf-only-lifecycle): <n>
255
- - <org>/<repo>#<number> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
273
+ - Repaired (container — leaf-only-lifecycle): <n>
274
+ - <org>/<repo>#<number> <title> — build-ready on a parent/container; moved $READY $CLAIMED without invoking lisa:github-agent; lifecycle-repair comment posted
275
+ - Skipped (active blockers): <n>
276
+ - <org>/<repo>#<number> <title> — waiting on <blocker refs>
256
277
  - Blocked (pre-flight verify failed): <n>
257
278
  - <org>/<repo>#<number> <title> — see issue comments
258
279
  - Held (triage found ambiguities): <n>
@@ -265,9 +286,10 @@ Total PRs opened: <n>
265
286
 
266
287
  ## Idempotency & safety
267
288
 
268
- - **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.
269
- - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation no double-pickup.
270
- - **No writes outside the lifecycle**: this skill only relabels `$READY $CLAIMED` and `$CLAIMED $DONE`. Every other label change is owned by `lisa:github-agent`.
289
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any leaf claim; a container with open child work (or a childless Epic/Story/Spike) is moved `$READY` → `$CLAIMED` as lifecycle repair and never dispatched. The lifecycle-repair comment is idempotent — a re-entrant cycle does not re-post it.
290
+ - **Dependency hold runs before leaf claim**: explicit `Blocked by:` relationships are resolved after container repair is ruled out but before `$READY → $CLAIMED`; active blockers leave the leaf candidate in `$READY` and are reported as skipped, not blocked.
291
+ - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation for leaves; containers are also moved to `$CLAIMED` to leave the ready pickup queue, but are not dispatched.
292
+ - **No writes outside the lifecycle**: this skill only relabels `$READY → $CLAIMED` and `$CLAIMED → $DONE`. For containers, `$READY → $CLAIMED` is a lifecycle repair, not a direct build claim. Every other label change is owned by `lisa:github-agent`.
271
293
  - **Terminal native closure**: after `$CLAIMED → $DONE`, close the GitHub issue only when `$DONE` is the true terminal done value per `leaf-only-lifecycle`; intermediate env labels stay open.
272
294
  - **Failure isolation**: per-issue exceptions caught and recorded; the cycle continues.
273
295
  - **Single cycle per repo**: do not run two `lisa:github-build-intake` cycles in parallel against the same repo — concurrent claims could race. The scheduling layer is responsible for serialization.
@@ -290,8 +312,8 @@ If the repo has not adopted the `status:*` label namespace, this skill cannot ru
290
312
 
291
313
  ## Rules
292
314
 
293
- - **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.
294
- - Never relabel an issue the cycle didn't claim. The `$CLAIMED` label is the signature of cycle ownership.
315
+ - **Dispatch leaves only.** Per the `leaf-only-lifecycle` rule, never dispatch a container — an issue with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready role. Move it `$READY $CLAIMED` as lifecycle repair (Phase 3a); never silently implement a container.
316
+ - Never relabel an issue outside the cycle's allowed transitions. The `$CLAIMED` label is the signature of cycle ownership for leaves, and the parent/container progress state for lifecycle repairs.
295
317
  - Never bypass `lisa:github-agent` to do build work directly. `lisa:github-agent` owns the per-issue lifecycle.
296
318
  - Never auto-transition past `$DONE`. Downstream labels (terminal `status:done`, etc.) are owned by QA / PM / merge automation.
297
319
  - Never close a GitHub issue at intermediate env states (`status:on-dev`, `status:on-stg`, or configured equivalents). Native close happens only at the terminal `done` value.
@@ -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). 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."
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 — dispatch leaf work units only; move or safe-block a container with open child work (or a childless Epic/Story/Spike) that carries a stale build-ready role according to the vendor's lifecycle semantics. Counterpart to lisa:intake's PRD-side dispatchers."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -27,7 +27,7 @@ The vendor scanners also own the terminal native-closure step from `leaf-only-li
27
27
  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**:
28
28
 
29
29
  - A **leaf work unit** (Bug, Task, Sub-task, Improvement with no open child work) is claimed and built.
30
- - 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).
30
+ - A **container** — anything with open child work, or a childless Epic/Story/Spike — that still carries a stale build-ready role is **never dispatched**. The GitHub scanner moves it out of the pickup queue by replacing `status:ready` with `status:in-progress` and posting an idempotent lifecycle-repair comment; other vendor scanners skip or safe-block according to their native lifecycle semantics.
31
31
 
32
32
  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:
33
33
 
@@ -55,6 +55,6 @@ Intermediate env states are not native closure. A vendor scanner that resolves `
55
55
 
56
56
  - Single cycle per invocation — the vendor skill processes the current `Ready` set and exits.
57
57
  - 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.
58
- - **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.
58
+ - **Leaf-only dispatch, every vendor.** Per the `leaf-only-lifecycle` rule, each vendor scanner dispatches leaf work units only and moves or safe-blocks a container (open child work, or a childless Epic/Story/Spike) carrying a stale build-ready role according to its lifecycle semantics. 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.
59
59
  - **Terminal native closure, every capable vendor.** Per the same rule, each vendor scanner finalizes native open/closed state only at the true terminal `done` value. This shim never performs native closure itself, but callers can rely on the dispatched vendor scanner to apply the contract.
60
60
  - 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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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.59.1",
3
+ "version": "2.60.1",
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"
@@ -54,7 +54,8 @@ fi
54
54
  "in_review": "<page-id>",
55
55
  "blocked": "<page-id>",
56
56
  "ticketed": "<page-id>",
57
- "shipped": "<page-id>"
57
+ "shipped": "<page-id>",
58
+ "verified": "<page-id>"
58
59
  },
59
60
  "dashboardPageId": "<page-id>",
60
61
  "feedbackPageId": "<page-id>",
@@ -75,7 +76,7 @@ fi
75
76
  "draft": "prd-draft",
76
77
  "ready": "prd-ready", "in_review": "prd-in-review",
77
78
  "blocked": "prd-blocked", "ticketed": "prd-ticketed",
78
- "shipped": "prd-shipped",
79
+ "shipped": "prd-shipped", "verified": "prd-verified",
79
80
  "sentinel": "prd-intake-feedback",
80
81
  "rollup": { "closeOnShipped": false }
81
82
  }
@@ -87,7 +88,8 @@ fi
87
88
  "statusProperty": "Status",
88
89
  "values": {
89
90
  "draft": "Draft", "ready": "Ready", "in_review": "In Review",
90
- "blocked": "Blocked", "ticketed": "Ticketed", "shipped": "Shipped"
91
+ "blocked": "Blocked", "ticketed": "Ticketed", "shipped": "Shipped",
92
+ "verified": "Verified"
91
93
  },
92
94
  "rollup": { "closeOnShipped": false }
93
95
  },
@@ -106,7 +108,7 @@ fi
106
108
  "draft": "prd-draft",
107
109
  "ready": "prd-ready", "in_review": "prd-in-review",
108
110
  "blocked": "prd-blocked", "ticketed": "prd-ticketed",
109
- "shipped": "prd-shipped",
111
+ "shipped": "prd-shipped", "verified": "prd-verified",
110
112
  "sentinel": "prd-intake-feedback",
111
113
  "rollup": { "closeOnShipped": false }
112
114
  }
@@ -171,7 +173,7 @@ When `tracker = "github"` AND `source = "github"` (self-host), both reads and wr
171
173
  | `notion.workspaceId` | `source = "notion"` | **committed** | Workspace identifier (Notion workspace UUID, or a stable human slug the user picks at setup). Same for every developer on the project. Used as the keychain `account` value when looking up the Notion API token, so each project's `notion-access` finds the right per-workspace token. |
172
174
  | `notion.prdDatabaseId` | `source = "notion"` | **committed** | Notion database ID (UUID, dashes optional). The database is the PRD queue. Same for every developer on the project. |
173
175
  | `notion.statusProperty` | `source = "notion"` | **committed** | Name of the database property that drives the lifecycle. Defaults to `"Status"` if absent. |
174
- | `notion.values` | optional | **committed** | Map of role → Notion status-value name (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`). Defaults match the role names in title case. Override here if your Notion DB uses different value names. |
176
+ | `notion.values` | optional | **committed** | Map of role → Notion status-value name (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`, `verified`). Defaults match the role names in title case. Override here if your Notion DB uses different value names. |
175
177
 
176
178
  #### `linear`
177
179
 
@@ -210,6 +212,7 @@ Every lifecycle skill operates on a fixed set of **roles** (`ready`, `claimed`,
210
212
  | `blocked` | Validation failed; clarifying-comments posted | `Blocked` (status) | `prd-blocked` (label) |
211
213
  | `ticketed` | Validated and tickets created | `Ticketed` (status) | `prd-ticketed` (label) |
212
214
  | `shipped` | All child tickets shipped | `Shipped` (status) | `prd-shipped` (label) |
215
+ | `verified` | Shipped product empirically checked against the PRD | `Verified` (status) | `prd-verified` (label); parent-page lookup (Confluence) |
213
216
  | `sentinel` | (PRD-intake feedback issue marker, GitHub/Linear self-host only) | — | `prd-intake-feedback` |
214
217
 
215
218
  ### PRD rollup config (`prd.rollup`)
@@ -245,7 +248,7 @@ The true terminal `done` value is also the only value that triggers provider-nat
245
248
  ### What's configurable, what's not
246
249
 
247
250
  - **Status / label NAMES** are configurable per project — that's the point of the vocabulary maps.
248
- - **Role SEMANTICS and TRANSITIONS** are not. The build lifecycle is always `ready → claimed → done` (with optional `review` for label-driven systems). The PRD lifecycle is always `ready → in_review → (blocked | ticketed) → shipped`. Lisa skills hardcode these transitions because they encode the design intent of the framework, not the project's preferences.
251
+ - **Role SEMANTICS and TRANSITIONS** are not. The build lifecycle is always `ready → claimed → done` (with optional `review` for label-driven systems). The PRD lifecycle is always `ready → in_review → (blocked | ticketed) → shipped`, then verification may move `shipped → verified` on a pass or `shipped → blocked` on a failed verification. `verified` is terminal and product-owned like `draft` and `shipped`; Lisa does not add `prd-verifying` or `prd-verification-failed` states. Skills hardcode these transitions because they encode the design intent of the framework, not the project's preferences.
249
252
  - **Extra statuses/labels** the project uses outside these roles are fine — lisa never touches them.
250
253
 
251
254
  ### Defaults vs. requirements
@@ -324,7 +327,7 @@ Initiatives (Linear's cross-Project rollup) are NOT used — they're intended fo
324
327
 
325
328
  When `github-to-tracker` is invoked AND `tracker = "github"`, both reads and writes hit the same GitHub repo. Label namespaces are kept separate so the two flows don't collide:
326
329
 
327
- - PRD-source labels: `prd-draft`, `prd-ready`, `prd-in-review`, `prd-blocked`, `prd-ticketed`, `prd-shipped` — owned by `github-prd-intake` and the human PM.
330
+ - PRD-source labels: `prd-draft`, `prd-ready`, `prd-in-review`, `prd-blocked`, `prd-ticketed`, `prd-shipped`, `prd-verified` — owned by `github-prd-intake`, `verify-prd`, and the human PM.
328
331
  - Build-queue labels: `status:ready`, `status:in-progress`, `status:code-review`, `status:on-dev`, `status:done` — owned by `github-build-intake` and `github-agent`.
329
332
  - Sentinel issue label: `prd-intake-feedback` — owned by `github-prd-intake`.
330
333
 
@@ -10,7 +10,7 @@ The first two are the same idea seen from opposite ends: a parent never enters t
10
10
 
11
11
  ## Why this exists
12
12
 
13
- Build intake claims and implements whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent will try to implement the container itself — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
13
+ Build intake processes whatever carries the build-ready role (the `ready` role — see `config-resolution`). A parent container (an Epic, a Story, a Linear Project, any issue with child work) is not a unit of implementation; it organizes work. If a parent is marked build-ready, an agent may try to implement the container itself unless intake gates it first — the wrong permission and lifecycle boundary. This surfaced in real PRD intake: a PRD decomposed into an Epic, Stories, and Sub-tasks, and *every* item received the build-ready label, so a subsequent build pass would have tried to "implement" the Epic.
14
14
 
15
15
  The fix is not vendor-specific. It belongs here, in a cross-vendor rule, and every writer / validator / intake path enforces it.
16
16
 
@@ -43,7 +43,7 @@ Where a vendor lacks native hierarchy for a given pair, a text link or metadata
43
43
 
44
44
  - **At decomposition / write time** — when a PRD decomposes into a hierarchy, only the leaf work units receive the `ready` role (status/label). Parent containers (Epic, Story, Project, and any parent issue that has child work) are created in their normal non-ready state and never receive the build-ready role directly. The leaves are what downstream build intake will claim.
45
45
  - **At validate time** — the `*-validate-*` gate FAILs any container carrying the build-ready role. This is the symmetric write-side guard: a stale or hand-applied build-ready role on a parent is a lifecycle error.
46
- - **At claim time** — build intake scans for the `ready` role but claims **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not claimed**: intake either skips it or safely blocks it with a clear lifecycle-repair message (move the role to the leaves; roll the parent up). Intake never silently implements a container.
46
+ - **At claim time** — build intake scans for the `ready` role but dispatches **only leaf work units**. A container that still carries a stale build-ready role (e.g. applied before this rule existed) is **not dispatched**: intake either moves it into the vendor's parent/container progress state or safely blocks it with a clear lifecycle-repair message. Intake never silently implements a container.
47
47
 
48
48
  The permission boundary is the maintainer-applied build-ready role, not authorship — do not add author-based guards (PRD #522 non-goal). This rule narrows *what* may carry that role, not *who* may apply it.
49
49
 
@@ -108,7 +108,7 @@ Skills that enforce this invariant or perform rollup cite this rule by slug (the
108
108
 
109
109
  - **Decomposition / write** (`*-to-tracker`, `*-write-*`) — apply the `ready` role to leaves only; never to containers.
110
110
  - **Validate** (`*-validate-*`) — FAIL a container carrying the build-ready role; FAIL a childless Epic/Story/Spike marked build-ready.
111
- - **Build intake** (`*-build-intake`, `tracker-build-intake`) — claim leaves only; skip or safe-block containers with stale build-ready roles.
111
+ - **Build intake** (`*-build-intake`, `tracker-build-intake`) — dispatch leaves only; move or safe-block containers with stale build-ready roles according to vendor lifecycle semantics.
112
112
  - **Rollup** — derive parent state from children per the state machine above.
113
113
  - **Terminal native closure** (`*-build-intake`, terminal helpers) — after a leaf reaches the true terminal `done` role, finalize it through the provider's native close / complete / resolve mechanism where available; never do this for intermediate env states.
114
114
 
@@ -46,6 +46,19 @@ The relationship is recorded with the source tool's **native hierarchy first**,
46
46
 
47
47
  The documented-section fallback is always written so the generated child set is readable later without relying only on free-form comments. Comment summaries are still useful human-facing audit trails and are not removed (PRD #525 non-goal).
48
48
 
49
+ ### Vendor relationship and closure matrix
50
+
51
+ Use this matrix when implementing or auditing a PRD-source integration. It describes the native relationship to prefer, the documented fallback that must remain durable, and the closure behavior after the all-terminal rollup condition is met.
52
+
53
+ | PRD source / tracker shape | Native hierarchy mechanism | Documented fallback | Closure behavior |
54
+ |---|---|---|---|
55
+ | **GitHub Issues (source and tracker in the same repo)** | Link generated top-level work as native sub-issues of the PRD issue when the repo supports GitHub sub-issues. The PRD's direct sub-issues are the generated top-level child set; descendants under those children are excluded from PRD rollup. | Always maintain the machine-readable `## Tickets` / `## Generated Work` section keyed by `owner/repo#number`, and use it when sub-issues are unavailable, disabled, or incomplete. | Rollup changes the PRD lifecycle label from `prd-ticketed` to `prd-shipped` when every required generated top-level issue is terminal. Close the PRD issue only when `github.labels.prd.rollup.closeOnShipped` is `true`; the default is to leave it open. |
56
+ | **Linear** | Use Linear native grouping where the PRD also lives in Linear: generated top-level Issues are related through `parentId`, or a generated Project groups the generated Issues. Read only top-level Issues for PRD rollup. | Use the PRD's machine-readable generated-work section when the destination tracker is not Linear or native project / parent relationships cannot represent the PRD-to-work link. Entries are keyed by Linear issue or project identifier / UUID. | Rollup removes `prd-ticketed` and adds `prd-shipped` to the PRD project when every required generated top-level Issue / Project is completed. Archive or close the project only when `linear.labels.prd.rollup.closeOnShipped` is `true`; otherwise leave it active. |
57
+ | **JIRA / Atlassian tracker work** | Prefer native Epic / parent fields, or a documented issue-link type where the PRD-to-Epic relationship can be represented in JIRA. JIRA child terminal state is read from the issue's Done status category. | If the PRD source is not JIRA or the native link cannot attach tracker work to the PRD artifact, record generated top-level JIRA issue keys in the PRD's generated-work section. | Rollup may transition a JIRA-hosted PRD to the configured shipped / Done status only after all required generated top-level issues are in the Done status category. Native resolution / closure is config-gated through the PRD rollup close-on-shipped setting. |
58
+ | **Confluence PRDs** | No native issue hierarchy for tracker work. Confluence's native structure is used for PRD lifecycle lanes by parent page, not for destination work children. | The Confluence page's machine-readable `## Tickets` / `## Generated Work` section is the primary child source. Top-level generated work entries are keyed by destination ticket ref. | Rollup re-parents the PRD page from the `ticketed` parent to the `shipped` parent when every required generated-work entry is marked done. Archive the page only when `confluence.rollup.closeOnShipped` is `true`; otherwise leave it active. |
59
+ | **Notion PRDs** | No native issue hierarchy for tracker work. Notion's native status/select property stores PRD lifecycle state, not generated ticket parentage. | The Notion page's machine-readable `## Tickets` / `## Generated Work` section is the primary child source. Top-level generated work entries are keyed by destination ticket ref. | Rollup sets the configured Notion status/select value to `Shipped` when every required generated-work entry is marked done. Archive the page only when `notion.rollup.closeOnShipped` is `true`; otherwise leave it active. |
60
+ | **Cross-vendor PRD -> tracker** | Native hierarchy cannot cross systems, so the destination ticket is not expected to become a native child of the PRD artifact. Native tracker hierarchy still applies inside the destination system among generated Epics, Stories, and Sub-tasks. | The source PRD artifact's generated-work section is authoritative for the PRD-to-top-level-work child set, and each entry links to the destination ticket URL / key. | The PRD source owns the final lifecycle transition and optional close/archive. It evaluates terminal state using the destination tracker's predicate, then applies the source vendor's `shipped` transition and close-on-shipped behavior. |
61
+
49
62
  ## Per-vendor terminal-state predicate
50
63
 
51
64
  A generated top-level child is **terminal** (counts as done for rollup) when it reaches the source/tracker's done/shipped state. The predicate is vendor-specific; the *semantics* ("this child has shipped") are not:
@@ -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 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."
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 moved out of the ready pickup queue into the configured `claimed` label with a lifecycle-repair comment, never dispatched to lisa:github-agent. The `ready` label is the human-flipped signal that an issue is truly ready for direct development pickup — mirroring how Notion PRDs work product Draft → Ready → (us) In Review → Blocked|Ticketed."
4
4
  allowed-tools: ["Skill", "Bash"]
5
5
  ---
6
6
 
@@ -135,13 +135,13 @@ 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. Leaf-only claim gate (skip / safe-block containers)
138
+ #### 3a. Leaf-only claim gate (repair containers)
139
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.**
140
+ Build intake dispatches **only independently implementable leaf work units** to the build agent. 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 dispatched** — intake moves it out of the pickup queue by replacing `$READY` with `$CLAIMED`, then posts 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
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.
142
+ Run this gate **before** the leaf claim relabel, for every candidate issue. Do NOT comment "Claimed" or invoke `lisa:github-agent` for an issue that fails the gate. A container repair still changes labels: remove `$READY`, add `$CLAIMED`, and explain that parent/container `$CLAIMED` means rollup/build-lane progress through child/leaf work, not direct implementation.
143
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):
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, `Parent: #<n>` references). Dependency links such as `Blocked by:` are not parentage; they are handled by the active dependency hold gate below.
145
145
 
146
146
  ```bash
147
147
  # Native sub-issues via GraphQL (same query lisa:github-read-issue uses).
@@ -168,20 +168,39 @@ Classify and act (first match wins). `type:` is read from the issue's labels (`t
168
168
 
169
169
  | Condition | Class | Action |
170
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** |
171
+ | `OPEN_CHILDREN > 0` (open child work, any type) | **Container** | **Move to `$CLAIMED` as lifecycle repair — do NOT dispatch** |
172
+ | no open children AND `type ∈ {Epic, Story, Spike}` | **Childless container-type** | **Move to `$CLAIMED` as lifecycle repair — do NOT dispatch** |
173
173
  | no open children AND `type ∈ {Bug, Task, Sub-task, Improvement}` (or no `type:` label) | **Leaf work unit** | **Proceed to 3b claim** |
174
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.
175
+ The childless-parent exception is narrow: childlessness enables direct build-agent dispatch **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 moved out of the ready pickup queue for repair/rollup and never dispatched.
176
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.
177
+ **Lifecycle repair (default action for a flagged container).** Move the issue out of the pickup queue by removing `$READY` and adding `$CLAIMED`, post a single lifecycle-repair comment, and record the issue under "Repaired (container)" in the summary. Do NOT invoke `lisa:github-agent`. 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
178
 
179
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."
180
+ gh issue edit <number> --repo <org>/<repo> --remove-label "$READY" --add-label "$CLAIMED"
181
+ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Lifecycle repair: this issue carried the build-ready role ($READY) but is a parent/container with open child work (or a childless Epic/Story/Spike). I moved it to $CLAIMED without invoking the build agent. For parent/container issues, $CLAIMED means rollup/build-lane progress through child/leaf work; direct implementation must happen on leaf issues. Build-ready is leaf-only per leaf-only-lifecycle — move $READY onto its leaf children, or decompose/reclassify a childless Epic/Story/Spike."
181
182
  ```
182
183
 
183
184
  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
 
186
+ **Active dependency hold gate.** After the leaf-only gate passes, but still before the claim relabel, parse explicit blocker relationships from the issue body and durable Lisa relationship sections. Support these forms at minimum:
187
+
188
+ - `Blocked by: #123`
189
+ - `Blocked by: #123, #456`
190
+ - `Blocked by: owner/repo#123`
191
+ - `Blocked by: https://github.com/owner/repo/issues/123`
192
+
193
+ Resolve local `#123` references against the candidate issue's repo. Resolve qualified refs and GitHub issue URLs against their named repo. For each blocker, read the blocker issue's status labels with `gh issue view <number> --repo <owner>/<repo> --json labels,state`.
194
+
195
+ Default cleared blocker labels for GitHub build intake are:
196
+
197
+ - `status:code-review`
198
+ - `status:on-dev`
199
+ - `status:on-stg`
200
+ - `status:done`
201
+
202
+ A blocker is active if it is open and has no cleared status label. Treat `status:ready`, `status:in-progress`, missing status labels, and inaccessible blockers as active. Closed blockers are cleared. If any blocker is active, skip the candidate without changing lifecycle labels, without posting "Claimed", and without invoking `lisa:github-agent`. Record it under "Skipped (active blockers)" in the summary and include the active blocker refs. Keep any dependency-hold comment idempotent with a `[claude-build-intake]` prefix.
203
+
185
204
  #### 3b. Claim
186
205
 
187
206
  ```bash
@@ -251,8 +270,10 @@ Cycle completed: <ISO timestamp>
251
270
  Issues processed: <n>
252
271
  - $DONE (build complete, PR ready): <n>
253
272
  - <org>/<repo>#<number> <title> → PR <URL>
254
- - Skipped (container — leaf-only-lifecycle): <n>
255
- - <org>/<repo>#<number> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
273
+ - Repaired (container — leaf-only-lifecycle): <n>
274
+ - <org>/<repo>#<number> <title> — build-ready on a parent/container; moved $READY $CLAIMED without invoking lisa:github-agent; lifecycle-repair comment posted
275
+ - Skipped (active blockers): <n>
276
+ - <org>/<repo>#<number> <title> — waiting on <blocker refs>
256
277
  - Blocked (pre-flight verify failed): <n>
257
278
  - <org>/<repo>#<number> <title> — see issue comments
258
279
  - Held (triage found ambiguities): <n>
@@ -265,9 +286,10 @@ Total PRs opened: <n>
265
286
 
266
287
  ## Idempotency & safety
267
288
 
268
- - **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.
269
- - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation no double-pickup.
270
- - **No writes outside the lifecycle**: this skill only relabels `$READY $CLAIMED` and `$CLAIMED $DONE`. Every other label change is owned by `lisa:github-agent`.
289
+ - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any leaf claim; a container with open child work (or a childless Epic/Story/Spike) is moved `$READY` → `$CLAIMED` as lifecycle repair and never dispatched. The lifecycle-repair comment is idempotent — a re-entrant cycle does not re-post it.
290
+ - **Dependency hold runs before leaf claim**: explicit `Blocked by:` relationships are resolved after container repair is ruled out but before `$READY → $CLAIMED`; active blockers leave the leaf candidate in `$READY` and are reported as skipped, not blocked.
291
+ - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation for leaves; containers are also moved to `$CLAIMED` to leave the ready pickup queue, but are not dispatched.
292
+ - **No writes outside the lifecycle**: this skill only relabels `$READY → $CLAIMED` and `$CLAIMED → $DONE`. For containers, `$READY → $CLAIMED` is a lifecycle repair, not a direct build claim. Every other label change is owned by `lisa:github-agent`.
271
293
  - **Terminal native closure**: after `$CLAIMED → $DONE`, close the GitHub issue only when `$DONE` is the true terminal done value per `leaf-only-lifecycle`; intermediate env labels stay open.
272
294
  - **Failure isolation**: per-issue exceptions caught and recorded; the cycle continues.
273
295
  - **Single cycle per repo**: do not run two `lisa:github-build-intake` cycles in parallel against the same repo — concurrent claims could race. The scheduling layer is responsible for serialization.
@@ -290,8 +312,8 @@ If the repo has not adopted the `status:*` label namespace, this skill cannot ru
290
312
 
291
313
  ## Rules
292
314
 
293
- - **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.
294
- - Never relabel an issue the cycle didn't claim. The `$CLAIMED` label is the signature of cycle ownership.
315
+ - **Dispatch leaves only.** Per the `leaf-only-lifecycle` rule, never dispatch a container — an issue with open child work, or a childless Epic/Story/Spike — even if it carries the build-ready role. Move it `$READY $CLAIMED` as lifecycle repair (Phase 3a); never silently implement a container.
316
+ - Never relabel an issue outside the cycle's allowed transitions. The `$CLAIMED` label is the signature of cycle ownership for leaves, and the parent/container progress state for lifecycle repairs.
295
317
  - Never bypass `lisa:github-agent` to do build work directly. `lisa:github-agent` owns the per-issue lifecycle.
296
318
  - Never auto-transition past `$DONE`. Downstream labels (terminal `status:done`, etc.) are owned by QA / PM / merge automation.
297
319
  - Never close a GitHub issue at intermediate env states (`status:on-dev`, `status:on-stg`, or configured equivalents). Native close happens only at the terminal `done` value.
@@ -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). 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."
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 — dispatch leaf work units only; move or safe-block a container with open child work (or a childless Epic/Story/Spike) that carries a stale build-ready role according to the vendor's lifecycle semantics. Counterpart to lisa:intake's PRD-side dispatchers."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -27,7 +27,7 @@ The vendor scanners also own the terminal native-closure step from `leaf-only-li
27
27
  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**:
28
28
 
29
29
  - A **leaf work unit** (Bug, Task, Sub-task, Improvement with no open child work) is claimed and built.
30
- - 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).
30
+ - A **container** — anything with open child work, or a childless Epic/Story/Spike — that still carries a stale build-ready role is **never dispatched**. The GitHub scanner moves it out of the pickup queue by replacing `status:ready` with `status:in-progress` and posting an idempotent lifecycle-repair comment; other vendor scanners skip or safe-block according to their native lifecycle semantics.
31
31
 
32
32
  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:
33
33
 
@@ -55,6 +55,6 @@ Intermediate env states are not native closure. A vendor scanner that resolves `
55
55
 
56
56
  - Single cycle per invocation — the vendor skill processes the current `Ready` set and exits.
57
57
  - 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.
58
- - **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.
58
+ - **Leaf-only dispatch, every vendor.** Per the `leaf-only-lifecycle` rule, each vendor scanner dispatches leaf work units only and moves or safe-blocks a container (open child work, or a childless Epic/Story/Spike) carrying a stale build-ready role according to its lifecycle semantics. 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.
59
59
  - **Terminal native closure, every capable vendor.** Per the same rule, each vendor scanner finalizes native open/closed state only at the true terminal `done` value. This shim never performs native closure itself, but callers can rely on the dispatched vendor scanner to apply the contract.
60
60
  - Never run two intake cycles concurrently against overlapping queues — the scheduling layer is responsible for serialization.