@codyswann/lisa 2.61.0 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/agents/confluence-prd-intake.md +1 -1
  5. package/plugins/lisa/agents/github-agent.md +4 -5
  6. package/plugins/lisa/agents/github-build-intake.md +1 -1
  7. package/plugins/lisa/agents/github-prd-intake.md +1 -1
  8. package/plugins/lisa/agents/linear-prd-intake.md +1 -1
  9. package/plugins/lisa/agents/notion-prd-intake.md +1 -1
  10. package/plugins/lisa/commands/intake.md +1 -1
  11. package/plugins/lisa/commands/project-ideation.md +3 -3
  12. package/plugins/lisa/commands/repair-intake.md +6 -0
  13. package/plugins/lisa/commands/research.md +3 -3
  14. package/plugins/lisa/commands/setup-automations.md +6 -0
  15. package/plugins/lisa/commands/tear-down-automations.md +6 -0
  16. package/plugins/lisa/commands/verify-prd.md +2 -2
  17. package/plugins/lisa/rules/config-resolution.md +63 -4
  18. package/plugins/lisa/rules/intent-routing.md +4 -3
  19. package/plugins/lisa/rules/leaf-only-lifecycle.md +1 -1
  20. package/plugins/lisa/rules/prd-lifecycle-rollup.md +10 -2
  21. package/plugins/lisa/rules/repo-scope-split.md +18 -1
  22. package/plugins/lisa/skills/confluence-prd-intake/SKILL.md +24 -14
  23. package/plugins/lisa/skills/confluence-prd-intake/agents/openai.yaml +2 -2
  24. package/plugins/lisa/skills/confluence-write-prd/SKILL.md +103 -0
  25. package/plugins/lisa/skills/confluence-write-prd/agents/openai.yaml +4 -0
  26. package/plugins/lisa/skills/github-build-intake/SKILL.md +32 -21
  27. package/plugins/lisa/skills/github-evidence/SKILL.md +3 -26
  28. package/plugins/lisa/skills/github-evidence/agents/openai.yaml +2 -2
  29. package/plugins/lisa/skills/github-journey/SKILL.md +2 -2
  30. package/plugins/lisa/skills/github-prd-intake/SKILL.md +25 -11
  31. package/plugins/lisa/skills/github-prd-intake/agents/openai.yaml +2 -2
  32. package/plugins/lisa/skills/github-sync/SKILL.md +5 -5
  33. package/plugins/lisa/skills/github-write-issue/SKILL.md +15 -6
  34. package/plugins/lisa/skills/github-write-prd/SKILL.md +100 -0
  35. package/plugins/lisa/skills/github-write-prd/agents/openai.yaml +4 -0
  36. package/plugins/lisa/skills/implement/SKILL.md +13 -6
  37. package/plugins/lisa/skills/intake/SKILL.md +13 -12
  38. package/plugins/lisa/skills/intake/agents/openai.yaml +2 -2
  39. package/plugins/lisa/skills/jira-build-intake/SKILL.md +24 -11
  40. package/plugins/lisa/skills/jira-write-ticket/SKILL.md +8 -0
  41. package/plugins/lisa/skills/linear-build-intake/SKILL.md +22 -9
  42. package/plugins/lisa/skills/linear-prd-intake/SKILL.md +23 -13
  43. package/plugins/lisa/skills/linear-prd-intake/agents/openai.yaml +2 -2
  44. package/plugins/lisa/skills/linear-write-issue/SKILL.md +10 -2
  45. package/plugins/lisa/skills/linear-write-prd/SKILL.md +90 -0
  46. package/plugins/lisa/skills/linear-write-prd/agents/openai.yaml +4 -0
  47. package/plugins/lisa/skills/notion-access/SKILL.md +2 -0
  48. package/plugins/lisa/skills/notion-prd-intake/SKILL.md +22 -12
  49. package/plugins/lisa/skills/notion-prd-intake/agents/openai.yaml +2 -2
  50. package/plugins/lisa/skills/notion-write-prd/SKILL.md +107 -0
  51. package/plugins/lisa/skills/notion-write-prd/agents/openai.yaml +4 -0
  52. package/plugins/lisa/skills/prd-source-write/SKILL.md +80 -0
  53. package/plugins/lisa/skills/prd-source-write/agents/openai.yaml +4 -0
  54. package/plugins/lisa/skills/project-ideation/SKILL.md +183 -80
  55. package/plugins/lisa/skills/repair-intake/SKILL.md +403 -0
  56. package/plugins/lisa/skills/repair-intake/agents/openai.yaml +4 -0
  57. package/plugins/lisa/skills/research/SKILL.md +19 -3
  58. package/plugins/lisa/skills/research/agents/openai.yaml +2 -2
  59. package/plugins/lisa/skills/setup-automations/SKILL.md +78 -0
  60. package/plugins/lisa/skills/setup-automations/agents/openai.yaml +4 -0
  61. package/plugins/lisa/skills/setup-github/SKILL.md +0 -1
  62. package/plugins/lisa/skills/tear-down-automations/SKILL.md +34 -0
  63. package/plugins/lisa/skills/tear-down-automations/agents/openai.yaml +4 -0
  64. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +6 -2
  65. package/plugins/lisa/skills/tracker-build-intake/agents/openai.yaml +2 -2
  66. package/plugins/lisa/skills/tracker-evidence/SKILL.md +2 -2
  67. package/plugins/lisa/skills/tracker-sync/SKILL.md +1 -1
  68. package/plugins/lisa/skills/verify-prd/SKILL.md +41 -38
  69. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-expo/commands/exploratory-qa.md +3 -3
  74. package/plugins/lisa-expo/skills/exploratory-qa/SKILL.md +48 -18
  75. package/plugins/lisa-expo/skills/exploratory-qa/agents/openai.yaml +2 -2
  76. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-harper-fabric/commands/exploratory-qa.md +3 -3
  79. package/plugins/lisa-harper-fabric/skills/exploratory-qa/SKILL.md +48 -18
  80. package/plugins/lisa-harper-fabric/skills/exploratory-qa/agents/openai.yaml +2 -2
  81. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails/commands/exploratory-qa.md +3 -3
  88. package/plugins/lisa-rails/skills/exploratory-qa/SKILL.md +48 -18
  89. package/plugins/lisa-rails/skills/exploratory-qa/agents/openai.yaml +2 -2
  90. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  92. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-wiki/skills/lisa-wiki-ingest/SKILL.md +30 -1
  95. package/plugins/src/base/agents/confluence-prd-intake.md +1 -1
  96. package/plugins/src/base/agents/github-agent.md +4 -5
  97. package/plugins/src/base/agents/github-build-intake.md +1 -1
  98. package/plugins/src/base/agents/github-prd-intake.md +1 -1
  99. package/plugins/src/base/agents/linear-prd-intake.md +1 -1
  100. package/plugins/src/base/agents/notion-prd-intake.md +1 -1
  101. package/plugins/src/base/commands/intake.md +1 -1
  102. package/plugins/src/base/commands/project-ideation.md +3 -3
  103. package/plugins/src/base/commands/repair-intake.md +6 -0
  104. package/plugins/src/base/commands/research.md +3 -3
  105. package/plugins/src/base/commands/setup-automations.md +6 -0
  106. package/plugins/src/base/commands/tear-down-automations.md +6 -0
  107. package/plugins/src/base/commands/verify-prd.md +2 -2
  108. package/plugins/src/base/rules/config-resolution.md +63 -4
  109. package/plugins/src/base/rules/intent-routing.md +4 -3
  110. package/plugins/src/base/rules/leaf-only-lifecycle.md +1 -1
  111. package/plugins/src/base/rules/prd-lifecycle-rollup.md +10 -2
  112. package/plugins/src/base/rules/repo-scope-split.md +18 -1
  113. package/plugins/src/base/skills/confluence-prd-intake/SKILL.md +24 -14
  114. package/plugins/src/base/skills/confluence-write-prd/SKILL.md +103 -0
  115. package/plugins/src/base/skills/github-build-intake/SKILL.md +32 -21
  116. package/plugins/src/base/skills/github-evidence/SKILL.md +3 -26
  117. package/plugins/src/base/skills/github-journey/SKILL.md +2 -2
  118. package/plugins/src/base/skills/github-prd-intake/SKILL.md +25 -11
  119. package/plugins/src/base/skills/github-sync/SKILL.md +5 -5
  120. package/plugins/src/base/skills/github-write-issue/SKILL.md +15 -6
  121. package/plugins/src/base/skills/github-write-prd/SKILL.md +100 -0
  122. package/plugins/src/base/skills/implement/SKILL.md +13 -6
  123. package/plugins/src/base/skills/intake/SKILL.md +13 -12
  124. package/plugins/src/base/skills/jira-build-intake/SKILL.md +24 -11
  125. package/plugins/src/base/skills/jira-write-ticket/SKILL.md +8 -0
  126. package/plugins/src/base/skills/linear-build-intake/SKILL.md +22 -9
  127. package/plugins/src/base/skills/linear-prd-intake/SKILL.md +23 -13
  128. package/plugins/src/base/skills/linear-write-issue/SKILL.md +10 -2
  129. package/plugins/src/base/skills/linear-write-prd/SKILL.md +90 -0
  130. package/plugins/src/base/skills/notion-access/SKILL.md +2 -0
  131. package/plugins/src/base/skills/notion-prd-intake/SKILL.md +22 -12
  132. package/plugins/src/base/skills/notion-write-prd/SKILL.md +107 -0
  133. package/plugins/src/base/skills/prd-source-write/SKILL.md +80 -0
  134. package/plugins/src/base/skills/project-ideation/SKILL.md +183 -80
  135. package/plugins/src/base/skills/repair-intake/SKILL.md +403 -0
  136. package/plugins/src/base/skills/research/SKILL.md +19 -3
  137. package/plugins/src/base/skills/setup-automations/SKILL.md +78 -0
  138. package/plugins/src/base/skills/setup-github/SKILL.md +0 -1
  139. package/plugins/src/base/skills/tear-down-automations/SKILL.md +34 -0
  140. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +6 -2
  141. package/plugins/src/base/skills/tracker-evidence/SKILL.md +2 -2
  142. package/plugins/src/base/skills/tracker-sync/SKILL.md +1 -1
  143. package/plugins/src/base/skills/verify-prd/SKILL.md +41 -38
  144. package/plugins/src/expo/commands/exploratory-qa.md +3 -3
  145. package/plugins/src/expo/skills/exploratory-qa/SKILL.md +48 -18
  146. package/plugins/src/harper-fabric/commands/exploratory-qa.md +3 -3
  147. package/plugins/src/harper-fabric/skills/exploratory-qa/SKILL.md +48 -18
  148. package/plugins/src/rails/commands/exploratory-qa.md +3 -3
  149. package/plugins/src/rails/skills/exploratory-qa/SKILL.md +48 -18
  150. package/plugins/src/wiki/skills/lisa-wiki-ingest/SKILL.md +30 -1
@@ -81,7 +81,7 @@ Notes:
81
81
 
82
82
  The terminal rollup state is whatever the project configures for `done` — which is **env-keyed** (`config-resolution` "Env-keyed `done`"): a `done` map keyed by environment (`dev`, `staging`, `production`), resolved from the merged PR's base branch. This rule does **not** hardcode a `dev → staging → prod` promotion chain as required — that is a project-specific deploy topology. A downstream project with dev/staging/prod environments rolls a parent up to whichever terminal `done` value matches the environment its leaves shipped to. The rule stays generic and multi-env capable.
83
83
 
84
- **Single-environment collapse (this repo).** Lisa's own deploy has only `main`/`production` (no dev/staging), so `done` is a single value, not a map, and the build lifecycle collapses to one chain: `ready → claimed (in-progress) → review (code-review) → done`. The rollup terminal state is simply `done`. This is the *collapsed* case of the generic rule, not a different rule — projects with more environments keep the env-keyed map.
84
+ **Single-environment collapse (this repo).** Lisa's own deploy has only `main`/`production` (no dev/staging), so `done` is a single value, not a map. For GitHub, the build lifecycle collapses to one chain: `ready → claimed (in-progress) → done`. The rollup terminal state is simply `done`. This is the *collapsed* case of the generic rule, not a different rule — projects with more environments keep the env-keyed map.
85
85
 
86
86
  ## Terminal native closure
87
87
 
@@ -108,9 +108,17 @@ The PRD never advances to `shipped` on its own authority — it is **derived** f
108
108
  **`/lisa:verify` (one work item) vs `/lisa:verify-prd` (the whole initiative).** These are deliberately separate scopes:
109
109
 
110
110
  - **`/lisa:verify`** empirically verifies a **single work item** (a ticket / story / sub-task) in its target environment as part of that item's Build/Fix/Improve flow. It is what drives a build ticket to its `done` state; it operates at the *leaf/build* level (see `leaf-only-lifecycle`). It does **not** read the PRD or judge initiative-level acceptance, and it is **not** replaced by PRD verification.
111
- - **`/lisa:verify-prd`** is the **initiative-level acceptance gate**. It runs *after* the PRD is `shipped` (all generated top-level work terminal), reads the PRD and its generated child set, confirms the children are terminal, then runs spec-conformance against the original PRD requirements plus empirical verification of the shipped surface. On pass it transitions the PRD `shipped → verified` and posts evidence; on fail it transitions the PRD `shipped → blocked`, posts a product-readable failure report, and creates linked fix issues for the missing or incorrect behavior. Re-runs are idempotent (no duplicate evidence, fix issues, or lifecycle transitions).
111
+ - **`/lisa:verify-prd`** is the **initiative-level acceptance gate**. It runs *after* the PRD is `shipped` (all generated top-level work terminal), reads the PRD and its generated child set, confirms the children are terminal, then runs spec-conformance against the original PRD requirements plus empirical verification of the shipped surface. On pass it transitions the PRD `shipped → verified` and posts evidence; on fail it **re-opens the PRD `shipped → ticketed`** (never `blocked`), creates **build-ready fix tickets** for the missing or incorrect behavior (registered as the PRD's generated work) and posts a product-readable failure report — the fix tickets auto-build, rollup re-ships the PRD once they are terminal, and a later cycle re-verifies (self-healing; see "Closing the loop" below). Re-runs are idempotent (no duplicate evidence, fix tickets, or lifecycle transitions).
112
112
 
113
- **No extra failure states.** A failed PRD verification reuses the existing `blocked` role (`shipped → blocked`) rather than introducing a `prd-verifying` or `prd-verification-failed` state — the lifecycle stays intentionally small. `verified` is terminal and product-owned (like `draft` and `shipped`); a PRD is never moved to `verified` solely because its tickets are closed, and a PRD is never closed/archived before verification has passed. The vendor maps for the `verified` role (`prd-verified` label for GitHub/Linear, `Verified` status for Notion, the verified parent page for Confluence) live in the `config-resolution` rule.
113
+ **No extra failure states, and verification never uses `blocked`.** A failed PRD verification does **not** move the PRD to `blocked`; it re-opens the PRD `shipped → ticketed` and creates build-ready fix tickets (see "Closing the loop"), forming a self-healing loop rather than parking the PRD for a human. It introduces no `prd-verifying` or `prd-verification-failed` state — the lifecycle stays intentionally small, and `blocked` remains the *intake* (ready-stage validation) failure state, not the verification one. `verified` is terminal and product-owned (like `draft` and `shipped`); a PRD is never moved to `verified` solely because its tickets are closed, and a PRD is never closed/archived before verification has passed. The vendor maps for the `verified` role (`prd-verified` label for GitHub/Linear, `Verified` status for Notion, the verified parent page for Confluence) live in the `config-resolution` rule.
114
+
115
+ ### Closing the loop: PRD intake dispatches `/lisa:verify-prd` for shipped PRDs
116
+
117
+ `shipped → verified` does not happen on its own — something has to run the acceptance gate. The PRD-intake scanners close that loop: in addition to the `ticketed → shipped` rollup, each PRD-intake cycle **dispatches `/lisa:verify-prd` for a shipped PRD** so a shipped PRD does not sit unverified forever. This is a **dispatch**, not a transition — the intake scanner still never *sets* the verification outcome itself; `/lisa:verify-prd` (which it invokes) performs the `shipped → verified` (pass) or, on fail, the `shipped → ticketed` re-open (it **never** uses `blocked`), with its own guard, evidence, and build-ready fix-ticket creation.
118
+
119
+ **The self-healing FAIL loop.** When verify-prd fails it re-opens the PRD `shipped → ticketed` and creates **build-ready** fix tickets (registered as the PRD's generated work). Because the fix tickets are build-ready they are auto-claimed by the build queue with no human promotion; once they reach terminal, the `ticketed → shipped` rollup (Phase 3f) re-ships the PRD, and the next cycle's verify dispatch (Phase 3g) re-verifies. PASS ends at `verified`; FAIL starts another round. The loop **never auto-halts** (the failure report carries a verification-round count for human visibility) and **never** parks the PRD in `blocked`.
120
+
121
+ Bounded, like the ready claim: `/lisa:verify-prd` is a heavy full flow (spec-conformance + empirical verification + fix-ticket creation), so a scanner verifies **one shipped PRD per cycle** and lets the scheduler drain the rest — the same one-item-per-cycle discipline the `ready` claim uses. After verify-prd runs, the PRD leaves `shipped` (to `verified` on pass, or `ticketed` on fail), so it is not re-picked by the shipped query that cycle; a PRD whose generated work is not actually terminal is guard-stopped by verify-prd and left `shipped` (verify-prd's gate, not the scanner's). A PRD that the project chose to close on ship (`*.rollup.closeOnShipped = true`) is out of the open verification queue — closing on ship is an explicit opt-out of the open loop. This dispatch is **behaviorally identical across all four PRD-intake skills** (the `github` / `linear` / `notion` / `confluence` `*-prd-intake` Phase 3g); only the `shipped`-role query surface differs.
114
122
 
115
123
  ## Idempotency dedupe key
116
124
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Leaf work units are single-repo. A **leaf work unit** is an individually implementable ticket with no child tickets — issue types **Bug, Task, Sub-task, Improvement**. Each must name exactly one repository. **Epic, Story, Spike** are coordination containers and may span repos.
4
4
 
5
- This invariant is enforced at three points: gate **S10** in the `*-validate-*` skills (write time), `task-decomposition` step 1.5 (PRD-decomposition time), and the work-time split procedure below (when an agent picks up an existing ticket to implement it).
5
+ This invariant is enforced at four points: gate **S10** in the `*-validate-*` skills (write time), `task-decomposition` step 1.5 (PRD-decomposition time), **claim-time repo scoping** in the build-intake skills (when intake decides whether to claim a ready ticket for the current repo — see below), and the work-time split procedure below (when an agent picks up an existing ticket to implement it).
6
6
 
7
7
  ## Two splitting strategies, by phase
8
8
 
@@ -32,6 +32,23 @@ Auto-split only when the split is unambiguous. Fall back to the standard BLOCK +
32
32
  - Splitting would strand stakeholder context that only the reporter can re-scope (e.g. the acceptance criteria describe a single indivisible cross-repo behavior with no clean per-repo boundary).
33
33
  - The metadata required to clone (parent, environment, credentials) is itself missing — block on the missing metadata first; do not propagate gaps into the siblings.
34
34
 
35
+ ## Claim-time repo scoping (build-intake)
36
+
37
+ A ticketing system can oversee multiple repos (one JIRA project / Linear team for `frontend`, `backend`, `infrastructure`). When `lisa:*-build-intake` runs inside one repo, it claims only tickets for **that** repo — it never pulls a ready ticket meant for a sibling repo. This is the fourth enforcement point of the single-repo-leaf invariant; it runs in each vendor build-intake's claim gate (Phase 3a), **before** the leaf-only container gate and the claim.
38
+
39
+ Resolve the current repo per the `config-resolution` "Repo scoping" section (config `repo` → `github.repo` → git remote basename; stop with a clear error if unresolvable). Then walk the ready candidates in priority order and apply the **repo-scope decision** to each before claiming:
40
+
41
+ 1. **Read the candidate's repo marker** — the `repo:<name>` label (JIRA: also a component equal to a repo name).
42
+ - **Labeled for another repo** → **skip** cheaply (do not claim, do not re-determine); it stays `ready` for that repo's own intake. Move to the next candidate.
43
+ - **Labeled for the current repo** → proceed to the leaf-only gate + claim (the cheap, common path once labels exist).
44
+ - **Unlabeled** → **determine** the target repo(s) from the ticket (description, acceptance criteria, technical approach) confirmed against the actual code surfaces the change requires — the same detection as step 1 of the work-time split. Then **stamp** the resolved `repo:<name>` label(s) so future cycles filter cheaply, and re-apply this decision with the now-known repo.
45
+ 2. **Multi-repo leaf → split, never claim.** If determination finds the leaf touches more than one repo, run the **work-time split procedure** below to break it into single-repo siblings — each created **build-ready** (`build_ready: true`, so the build queue auto-claims it) and stamped with its own `repo:<name>`. After the split, the current repo's sibling (if any) becomes a normal current-repo candidate; the others are separate single-repo `ready` leaves for their repos. A multi-repo leaf is never claimed as-is.
46
+ 3. **Wrong repo → skip.** A single-repo leaf whose `repo:<name>` ≠ the current repo is left `ready` (and labeled) and skipped; intake moves on until it finds a claimable current-repo leaf, then stops (one item per cycle).
47
+
48
+ **Cost.** Only **unlabeled** candidates need content determination; once stamped, wrong-repo candidates are skipped by label alone. Prefer candidates already labeled `repo:<current>` first (cheap claim), falling through to unlabeled candidates (determine + stamp) only when no pre-labeled current-repo leaf is ready.
49
+
50
+ A container (Epic/Story/Spike) is handled by the leaf-only gate, not here — containers may span repos and are never claimed/built directly.
51
+
35
52
  ## Vendor mechanics
36
53
 
37
54
  The procedure is vendor-neutral; the create + link + edit mechanics differ:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: confluence-prd-intake
3
- description: "Scans a Confluence space (or a parent page) for PRD pages currently parented under the configured `ready` lifecycle page and runs each one through the dry-run validation pipeline. PRDs that pass every gate get tickets written and are re-parented under the `ticketed` lifecycle page; PRDs that fail get clarifying-question comments and are re-parented under the `blocked` lifecycle page. Confluence counterpart of `lisa:notion-prd-intake` — the workflow is identical; only the source-of-truth tools and the state encoding differ (parent-page re-parenting instead of a status property). Composes existing skills (confluence-to-tracker, tracker-validate, tracker-source-artifacts, product-walkthrough)."
3
+ description: "Scans a Confluence space (or a parent page) for PRD pages currently parented under the configured `ready` lifecycle page and runs the first eligible one through the dry-run validation pipeline. A PRD that passes every gate gets tickets written and is re-parented under the `ticketed` lifecycle page; a PRD that fails gets clarifying-question comments and is re-parented under the `blocked` lifecycle page. Confluence counterpart of `lisa:notion-prd-intake` — the workflow is identical; only the source-of-truth tools and the state encoding differ (parent-page re-parenting instead of a status property). Composes existing skills (confluence-to-tracker, tracker-validate, tracker-source-artifacts, product-walkthrough)."
4
4
  allowed-tools: ["Skill", "Bash"]
5
5
  ---
6
6
 
@@ -11,10 +11,10 @@ All Atlassian operations in this skill go through `lisa:atlassian-access`. Do no
11
11
  `$ARGUMENTS` is one of:
12
12
 
13
13
  - A Confluence **space** URL or space key — used as the surrounding scope sanity check. Example: `https://mycompany.atlassian.net/wiki/spaces/PRD` or `PRD`.
14
- - A Confluence **parent page** URL or page ID — overrides the configured `confluence.parents.ready` page for this run (useful when an operator wants to drain an alternative lifecycle parent). Example: `https://mycompany.atlassian.net/wiki/spaces/PRD/pages/123456789/PRDs`.
14
+ - A Confluence **parent page** URL or page ID — overrides the configured `confluence.parents.ready` page for this run (useful when an operator wants to target an alternative lifecycle parent). Example: `https://mycompany.atlassian.net/wiki/spaces/PRD/pages/123456789/PRDs`.
15
15
  - Empty — use `confluence.parents.ready` from `.lisa.config.json` as the scope.
16
16
 
17
- Run one intake cycle against the resolved `ready` lifecycle parent. Each direct child of that parent is treated as a PRD currently in the `ready` state. Each PRD is claimed (re-parented to `in_review`), validated, and routed to either the `blocked` parent (with clarifying comments) or the `ticketed` parent (with destination tickets created).
17
+ Run one intake cycle against the resolved `ready` lifecycle parent. Each direct child of that parent is treated as a PRD currently in the `ready` state. The first eligible PRD is claimed (re-parented to `in_review`), validated, routed to either the `blocked` parent (with clarifying comments) or the `ticketed` parent (with destination tickets created), then the cycle exits. Remaining ready PRDs stay queued for later scheduler invocations.
18
18
 
19
19
  ## Why parent pages, not labels
20
20
 
@@ -82,7 +82,7 @@ The **PRD closure rollup phase (3f)** re-parents a `ticketed` PRD to the `shippe
82
82
 
83
83
  ## Confirmation policy
84
84
 
85
- Do NOT ask the caller whether to proceed. Once invoked, run the cycle to completion — claim, validate, branch to the `blocked` or `ticketed` parent, write the summary. The caller (a human or a cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background batch.
85
+ Do NOT ask the caller whether to proceed. Once invoked, run the cycle to completion for the first eligible PRD — claim, validate, branch to the `blocked` or `ticketed` parent, write the summary, and exit. The caller (a human or a cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background queue.
86
86
 
87
87
  Specifically forbidden:
88
88
 
@@ -108,7 +108,7 @@ draft → ready → in_review → blocked | ticketed → shipped → verified
108
108
 
109
109
  (Each role corresponds to a dedicated parent page; the `verified` parent is resolved from `confluence.parents.verified`.)
110
110
 
111
- `verified` is the terminal state after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification re-parents back under `blocked` rather than introducing a separate `verifying` / `verification-failed` parent. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never re-parents a PRD into or out of the `verified` parent. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
111
+ `verified` is the terminal state after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification does **not** use `blocked`; `/lisa:verify-prd` re-parents the PRD `shipped → ticketed` and creates build-ready fix tickets that auto-build and trigger a re-verify (the self-healing loop), introducing no `verifying` / `verification-failed` parent. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never re-parents a PRD into or out of the `verified` parent. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
112
112
 
113
113
  A PRD's current state is determined entirely by which lifecycle parent it sits under. Re-parenting is the transition.
114
114
 
@@ -151,9 +151,9 @@ If the result set is empty, run a sanity probe across the other lifecycle parent
151
151
  - If every lifecycle parent has zero direct children → the lifecycle scaffolding is provisioned but unused, OR PRDs are still parented under the legacy structure. Surface: `"No PRDs found under any of the configured lifecycle parent pages (ready, in_review, blocked, ticketed). If this is a new project, move PRDs that are ready for ticketing under the configured 'ready' parent page (see Adoption section)."` Exit with an error — this is a setup / migration issue, not a normal idle cycle.
152
152
  - If any non-ready parent has children → the queue is genuinely empty (PRDs exist but are in `in_review`, `blocked`, `ticketed`, or `shipped`). Exit cleanly with `"No PRDs currently parented under the 'ready' lifecycle page. Nothing to do."`
153
153
 
154
- ### Phase 3 — Process each ready PRD
154
+ ### Phase 3 — Process the first eligible ready PRD
155
155
 
156
- For each PRD page (process serially to keep transitions auditable):
156
+ Select the first ready PRD page returned by Phase 2 and process only that page. Later scheduler invocations process the remaining ready PRDs.
157
157
 
158
158
  #### 3a. Claim
159
159
 
@@ -277,9 +277,9 @@ Use these exact badge labels — they are the validator's category values transl
277
277
 
278
278
  After all comments are posted (anchored groups + the optional footer summary), re-parent the PRD to the `blocked` lifecycle parent: `transition_prd "$PRD_ID" blocked`. Do NOT write any destination tickets.
279
279
 
280
- #### 3d. Continue
280
+ #### 3d. Stop
281
281
 
282
- Move to the next ready PRD. One PRD failing does not affect others.
282
+ Stop immediately after the claimed PRD is ticketed, blocked, or recorded as an error.
283
283
 
284
284
  #### 3e. Coverage audit (mandatory after re-parenting to `ticketed`)
285
285
 
@@ -290,7 +290,7 @@ Per-ticket gates prove each ticket is well-formed; they do NOT prove the *set* o
290
290
 
291
291
  | Verdict | Action |
292
292
  |---------|--------|
293
- | `COMPLETE` | Done. Leave PRD under `ticketed` parent. Move to next PRD. |
293
+ | `COMPLETE` | Done. Leave PRD under `ticketed` parent. End the cycle. |
294
294
  | `COMPLETE_WITH_SCOPE_CREEP` | Post an advisory footer comment naming the scope-creep tickets (so product can decide whether to close them as out-of-scope). Leave PRD under `ticketed` parent. |
295
295
  | `GAPS_FOUND` | The created ticket set is incomplete. (a) For each gap, post a comment using the same product-facing template as Phase 3c.3 — inline-anchored when `prd_anchor` is non-null, footer otherwise; category badge from the gap's `category` field; `What's unclear` and `Recommendation` from the audit report's `what` and `recommendation` fields. Apply the same forbidden-language rules from Phase 3c.5. (b) Post one footer summary comment listing the tickets that *were* successfully created (so product knows what to keep vs. what to extend). (c) Re-parent the PRD from `ticketed` back to `blocked`: `transition_prd "$PRD_ID" blocked`. |
296
296
  | `NO_TICKETS_FOUND` | Should not happen if step 2 succeeded. If it does, log it as an Error in the cycle summary and leave the PRD under `ticketed` with a comment flagging the audit failure for human review. |
@@ -352,9 +352,19 @@ The set of **required** children for the all-terminal check is the top-level chi
352
352
 
353
353
  This phase implements exactly one PRD-lifecycle hop — `ticketed → shipped` — and the optional config-gated archive that follows it. All terminal-state semantics, the generated-top-level-work boundary, and the dedupe-by-child-ref idempotency come from the `prd-lifecycle-rollup` rule; this skill is its Confluence implementation, not a second source of truth.
354
354
 
355
+ #### 3g. PRD verification dispatch (close the loop on shipped PRDs)
356
+
357
+ `shipped` and `verified` are distinct facts about a PRD (see the `prd-lifecycle-rollup` rule's "PRD-level verification vs ticket verification" and "Closing the loop" sections). Rollup (3f) only reaches the shipped parent; the `shipped → verified` (pass) / `shipped → ticketed` (fail) hops are owned by `/lisa:verify-prd`. This phase **closes that loop** by dispatching the initiative-level acceptance gate for shipped PRDs. It never performs the verification transition itself — the "never sets the verification outcome" invariant holds: `lisa:verify-prd`, not this skill, sets `verified` (or, on failure, re-opens the PRD to `ticketed`).
358
+
359
+ Re-query the PRDs currently parented under the shipped lifecycle parent via `lisa:atlassian-access` `operation: read-page-descendants id: <confluence.parents.shipped>`. Pick the **first** one and invoke `lisa:verify-prd <page-url>`. Process **one shipped PRD per cycle** — `lisa:verify-prd` is a heavy full flow (spec-conformance + empirical verification + fix-issue creation), so it is bounded exactly like the single-ready-PRD claim in Phase 3; the scheduler drains the rest.
360
+
361
+ **Per-cycle combined bound:** each scheduler cycle dispatches at most one ready PRD (the Phase 3 single-ready-PRD claim) **and** at most one shipped PRD for verification (this Phase 3g dispatch), for a maximum of two PRD operations per cycle. Ready intake runs first (Phase 3), then shipped verify (Phase 3g).
362
+
363
+ `lisa:verify-prd` owns the outcome: on a CONFORMS verdict with all empirical checks passing it transitions the PRD to the verified parent and posts evidence; on a conformance miss or a failing/unavailable check it **re-parents the PRD shipped → ticketed** (never the blocked parent) and creates **build-ready** fix tickets registered as the PRD's generated work, then posts a failure report — the fix tickets auto-build, rollup (3f) re-ships the PRD once they are terminal, and a later cycle re-verifies (the self-healing loop). Either branch moves the PRD out of the shipped parent, so it is not re-picked this cycle; a PRD whose generated work is not actually terminal is guard-stopped by `lisa:verify-prd` (left under shipped) — that is verify-prd's gate, not this skill's. A PRD archived on ship (`confluence.rollup.closeOnShipped = true`) is out of the open verification queue. This phase, like 3f, is **behaviorally identical across all four intake skills** (`github-prd-intake`, `linear-prd-intake`, `notion-prd-intake`, `confluence-prd-intake`) — only the `$SHIPPED` query surface differs; keep them aligned. Record the dispatched PRD + verify-prd's verdict in the summary.
364
+
355
365
  ### Phase 4 — Summary report
356
366
 
357
- After processing every ready PRD, emit a summary:
367
+ After processing the single selected PRD, emit a summary:
358
368
 
359
369
  ```text
360
370
  ## confluence-prd-intake summary
@@ -379,10 +389,10 @@ Print to the agent's output. Do not write this summary to Confluence or the dest
379
389
 
380
390
  ## Idempotency & safety
381
391
 
382
- - **Single-cycle scope**: this skill processes the ready set as it exists at the start of Phase 2. New PRDs moved under the `ready` parent mid-cycle are picked up next run.
392
+ - **One item per cycle**: this skill processes the first eligible ready PRD from Phase 2, then exits. New or remaining PRDs under the `ready` parent are picked up by later scheduler invocations.
383
393
  - **No writes outside the lifecycle**: this skill only ever writes to the destination tracker via `lisa:confluence-to-tracker` (which delegates to `lisa:tracker-write`), and only ever re-parents PRDs among `in_review`, `blocked`, `ticketed`, and `shipped` (the last via the rollup phase 3f only) via `lisa:atlassian-access` `operation: write-page`. It never edits PRD body content, never re-parents into or out of `draft`, never deletes pages. It re-parents under `shipped` and may archive the PRD page **only** through the config-gated rollup phase (3f).
384
394
  - **Claim-first ordering**: the re-parent to `in_review` happens BEFORE validation runs, so a re-entrant call won't double-process.
385
- - **Failure isolation**: an exception processing one PRD must not stop the cycle. Catch, record under "Errors" in the summary, continue to the next PRD. The PRD that errored is left under whatever parent it currently occupies (usually `in_review` if claim succeeded) — the human investigates from there.
395
+ - **Failure handling**: an exception processing the selected PRD is caught and recorded under "Errors" in the summary, then the cycle exits. The PRD that errored is left under whatever parent it currently occupies (usually `in_review` if claim succeeded) — the human investigates from there.
386
396
  - **Single-parent invariant**: a page has exactly one parent by construction in Confluence — the multi-state ambiguity that label-based systems can hit (two `prd-*` labels simultaneously) cannot occur here. After every transition, re-read the page and confirm `parentId` matches the expected role; if not, surface as an Error and skip.
387
397
  - **Rollup idempotency**: rollup (Phase 3f) is a no-op on a PRD already parented under `$SHIPPED_PARENT` (and already archived when `closeOnShipped` is `true`) — no duplicate re-parent, no duplicate archive, no duplicate comment. The all-terminal condition is a pure function of the children's current states (deduped by child-ref identity), so recomputing it is safe to re-run. Archival NEVER precedes the all-terminal condition.
388
398
 
@@ -425,4 +435,4 @@ Before this skill can run against a project, the project must adopt the lifecycl
425
435
  3. Move each existing PRD page under the appropriate lifecycle parent based on its current state. Product moves PRDs from `draft` → `ready` when they are ready for ticketing (replaces the Notion `Status = Ready` flip).
426
436
  4. Reserve the `in_review`, `blocked`, and `ticketed` parents for this skill — humans should not re-parent PRDs into them manually except to recover from an error.
427
437
 
428
- If the project hasn't adopted these parent pages, the first run exits with a provisioning error (not the idle empty-set message) — this distinguishes a setup issue from a genuinely empty queue so operators know to run `/lisa:setup:confluence` rather than assuming the queue is drained. See Phase 2 for how the skill detects this case.
438
+ If the project hasn't adopted these parent pages, the first run exits with a provisioning error (not the idle empty-set message) — this distinguishes a setup issue from a genuinely empty queue so operators know to run `/lisa:setup:confluence` rather than assuming there is no work. See Phase 2 for how the skill detects this case.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: confluence-write-prd
3
+ description: "Creates or idempotently updates a PRD as a Confluence page parented under the configured lifecycle parent page (the draft parent by default, or the ready parent when initial_role is ready so lisa:confluence-prd-intake auto-claims it). The Confluence PRD-source writer behind lisa:prd-source-write. Confluence models PRD state by PARENT PAGE (not labels), per config-resolution. Dedupes by a stable marker embedded in the page body, found via CQL (matched by marker, never by title). All Atlassian access goes through lisa:atlassian-access."
4
+ allowed-tools: ["Skill", "Bash"]
5
+ ---
6
+
7
+ # Write Confluence PRD: $ARGUMENTS
8
+
9
+ Create (or update) a PRD page in Confluence. Invoked by `lisa:prd-source-write` when
10
+ `source = confluence`; do not call directly from a vendor-neutral caller. **All Confluence
11
+ operations go through `lisa:atlassian-access`** — never call the Atlassian API/MCP or `acli` directly.
12
+
13
+ Confluence's PRD lifecycle uses **parent pages**, not labels (scoped API tokens can't write
14
+ Confluence labels — see `config-resolution` "Confluence PRD lifecycle uses parent pages"). A PRD's
15
+ state is which lifecycle parent it lives under; "promote to ready" = re-parent to the ready parent.
16
+
17
+ `$ARGUMENTS` carries the `lisa:prd-source-write` spec: `title`, `body` (full PRD markdown),
18
+ `initial_role` (`draft` | `ready`, default `draft`), `dedupe_key`, `marker`, optional `source_ref`.
19
+
20
+ ## Phase 1 — Resolve lifecycle parents
21
+
22
+ ```bash
23
+ read_g() { local lv gv; lv=$(jq -r "$1 // empty" .lisa.config.local.json 2>/dev/null); gv=$(jq -r "$1 // empty" .lisa.config.json 2>/dev/null); echo "${lv:-${gv:-$2}}"; }
24
+ SPACE=$(read_g '.confluence.spaceKey' '')
25
+ CLOUDID=$(read_g '.atlassian.cloudId' '')
26
+ [ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set in .lisa.config.json."; exit 1; }
27
+ # Resolve the FULL set of lifecycle parents from config (never hard-code) — needed for the target
28
+ # parent, the past-ready reverse-lookup, and to derive the space when spaceKey is absent.
29
+ DRAFT_PARENT=$(read_g '.confluence.parents.draft' '')
30
+ READY_PARENT=$(read_g '.confluence.parents.ready' '')
31
+ IN_REVIEW_PARENT=$(read_g '.confluence.parents.in_review' '')
32
+ BLOCKED_PARENT=$(read_g '.confluence.parents.blocked' '')
33
+ TICKETED_PARENT=$(read_g '.confluence.parents.ticketed' '')
34
+ SHIPPED_PARENT=$(read_g '.confluence.parents.shipped' '')
35
+ VERIFIED_PARENT=$(read_g '.confluence.parents.verified' '')
36
+ # "Progressed past ready" parents (never re-parent a PRD down from these):
37
+ PROGRESSED_PARENTS=("$IN_REVIEW_PARENT" "$BLOCKED_PARENT" "$TICKETED_PARENT" "$SHIPPED_PARENT" "$VERIFIED_PARENT")
38
+ ```
39
+
40
+ Resolve the target parent from `initial_role`: `ready` → `$READY_PARENT`, otherwise `$DRAFT_PARENT`.
41
+ If the needed parent id is unset, stop and report that `/lisa:setup:confluence` must provision the
42
+ lifecycle parent pages — do not create a PRD outside the lifecycle scaffolding.
43
+
44
+ **Resolve the space (config allows parent-page-only setups with no `spaceKey`).** If `$SPACE` is
45
+ empty, derive it from the target lifecycle parent: `lisa:atlassian-access` `operation: read-page id:
46
+ <target parent>` and read its space key from the response. If neither `confluence.spaceKey` is set nor
47
+ a space can be derived from the parent, **stop and report** that a space could not be established —
48
+ do not attempt a CQL search or create without it.
49
+
50
+ ## Phase 2 — Dedupe by marker (CQL search before create)
51
+
52
+ The `marker` is embedded in the page body. Find an existing PRD page carrying it — match the marker,
53
+ **never** the title:
54
+
55
+ ```text
56
+ lisa:atlassian-access operation: search-pages cql: 'space = "<SPACE>" AND text ~ "<marker>"'
57
+ ```
58
+
59
+ If `source_ref` was passed, target that page directly. If a page with the marker exists → **update**;
60
+ else → **create**.
61
+
62
+ ## Phase 3 — Create or update
63
+
64
+ **Storage-format body + marker (both paths).** Convert the PRD markdown to Confluence **storage
65
+ format** (XHTML): `#`/`##`/`###` → `<h1>/<h2>/<h3>`, paragraphs → `<p>`, lists → `<ul>/<ol><li>`,
66
+ fenced code → `<ac:structured-macro ac:name="code">`. Embed the marker as a storage comment
67
+ (`<!-- $MARKER -->`) or a small `<p>` so future CQL dedupe finds it. The body must always contain
68
+ **exactly one** marker; never write a markerless page (CREATE or UPDATE).
69
+
70
+ **CREATE:** `lisa:atlassian-access` `operation: write-page` (create form) with a payload that sets:
71
+ - `title`: `$TITLE`
72
+ - `space`: `$SPACE` (resolved in Phase 1)
73
+ - `ancestors`/`parentId`: the resolved lifecycle parent (`$DRAFT_PARENT` or `$READY_PARENT`)
74
+ - `body`: the storage-format, marker-normalized body above.
75
+
76
+ **UPDATE** (existing page or `source_ref`):
77
+ 1. **GET-then-PUT** (load-bearing, as `confluence-prd-intake` documents): first `operation: read-page
78
+ id: <page-id>` to read the current `version.number`, then `operation: write-page` (edit form) with
79
+ the marker-normalized storage body and `version.number` bumped to current+1. A PUT without the
80
+ current version is rejected; never drop the marker on the edit.
81
+ 2. Re-parent to the target lifecycle parent if the role changed — **unless** the page's current
82
+ parent is in the resolved `${PROGRESSED_PARENTS[@]}` set (already past `ready`). If so, leave it
83
+ and report `reused (already past ready)`. Reverse-lookup the current parent in
84
+ `confluence.parents.*` to determine its role before re-parenting.
85
+
86
+ ## Phase 4 — Return
87
+
88
+ ```yaml
89
+ ref: "<confluence-page-id>"
90
+ url: "<page url>"
91
+ role: draft | ready # derived from the lifecycle parent the page now lives under (or its current role when reused past ready)
92
+ marker: "<MARKER>"
93
+ outcome: created | reused
94
+ ```
95
+
96
+ ## Rules
97
+
98
+ - All access via `lisa:atlassian-access`; never call Atlassian directly.
99
+ - State is the parent page, not a label — never attempt Confluence label writes (they 401 on scoped
100
+ tokens; see `config-resolution`).
101
+ - Match dedupe by marker, never by title.
102
+ - Never re-parent a PRD already past `ready` down to draft/ready.
103
+ - Resolve parents from config (`confluence.parents.{draft,ready}`) — never hardcode page ids.
@@ -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 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."
3
+ description: "GitHub counterpart to lisa:jira-build-intake. Scans a GitHub repository for issues carrying the configured `ready` build label, processes the first eligible issue, runs leaf work via lisa:github-agent, relabels to the configured `done` label on completion, then exits. 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
 
@@ -12,7 +12,7 @@ allowed-tools: ["Skill", "Bash"]
12
12
  2. A full GitHub repo URL (e.g., `https://github.com/acme/frontend-v2`).
13
13
  3. The literal token `github` — falls back to `.lisa.config.json` (`github.org` / `github.repo`).
14
14
 
15
- Run one build-intake cycle. Each issue in the configured `ready` build label is claimed, built via the `lisa:github-agent` flow, and relabeled to the configured `done` label (env-aware — see Workflow resolution). The cycle is the symmetric mirror of `lisa:notion-prd-intake`: humans flip the `ready` label, agents pick up and progress.
15
+ Run one build-intake cycle. The first eligible issue in the configured `ready` build label is claimed, built via the `lisa:github-agent` flow, relabeled to the configured `done` label (env-aware — see Workflow resolution), then the cycle exits. Remaining ready issues stay queued for later scheduler invocations.
16
16
 
17
17
  ## Workflow resolution
18
18
 
@@ -30,7 +30,6 @@ read_role() {
30
30
 
31
31
  READY=$(read_role ready "status:ready")
32
32
  CLAIMED=$(read_role claimed "status:in-progress")
33
- REVIEW=$(read_role review "status:code-review")
34
33
  ```
35
34
 
36
35
  For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
@@ -51,10 +50,12 @@ fi
51
50
  DONE_TYPE=$(jq -r '.github.labels.build.done | type' .lisa.config.json 2>/dev/null)
52
51
  if [ "$DONE_TYPE" = "string" ]; then
53
52
  DONE=$(jq -r '.github.labels.build.done' .lisa.config.json)
53
+ DONE_LABELS_JSON=$(jq -c '[.github.labels.build.done]' .lisa.config.json)
54
54
  elif [ "$DONE_TYPE" = "object" ]; then
55
55
  [ -z "$TARGET_ENV" ] && { echo "ERROR: github.labels.build.done is env-keyed but env not resolvable"; exit 1; }
56
56
  DONE=$(jq -r --arg e "$TARGET_ENV" '.github.labels.build.done[$e] // empty' .lisa.config.json)
57
57
  [ -z "$DONE" ] && { echo "ERROR: github.labels.build.done has no entry for env '$TARGET_ENV'"; exit 1; }
58
+ DONE_LABELS_JSON=$(jq -c '[.github.labels.build.done[]]' .lisa.config.json)
58
59
  else
59
60
  case "$TARGET_ENV" in
60
61
  dev) DONE="status:on-dev" ;;
@@ -62,6 +63,7 @@ else
62
63
  production) DONE="status:done" ;;
63
64
  *) echo "ERROR: cannot resolve done label without env"; exit 1 ;;
64
65
  esac
66
+ DONE_LABELS_JSON=$(jq -cn --arg d "$DONE" '[$d]')
65
67
  fi
66
68
  ```
67
69
 
@@ -69,7 +71,7 @@ In prose below, the role names refer to the resolved labels: e.g. "the `ready` l
69
71
 
70
72
  ## Confirmation policy
71
73
 
72
- Do NOT ask the caller whether to proceed. Once invoked with a repo, run the cycle to completion — claim, dispatch each issue through `lisa:github-agent`, relabel successful builds to `$DONE`, write the summary. The caller (a human or a cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background batch.
74
+ Do NOT ask the caller whether to proceed. Once invoked with a repo, run the cycle to completion — claim and dispatch the first eligible issue through `lisa:github-agent`, relabel a successful build to `$DONE`, write the summary, and exit. The caller (a human or a cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background queue.
73
75
 
74
76
  Specifically forbidden:
75
77
 
@@ -81,7 +83,7 @@ Specifically forbidden:
81
83
  The only legitimate reasons to stop early:
82
84
 
83
85
  - Missing repo or required configuration. Surface the missing value and exit.
84
- - Label namespace not adopted (no issue carries any of `$READY` / `$CLAIMED` / `$REVIEW` / `$DONE`). Surface a label-convention error and exit (this is setup, not a normal idle cycle — see "Adoption" at the bottom).
86
+ - Label namespace not adopted (no issue carries any of `$READY` / `$CLAIMED` / `$DONE`). Surface a label-convention error and exit (this is setup, not a normal idle cycle — see "Adoption" at the bottom).
85
87
  - Empty ready set. Exit cleanly with `"No GitHub issues labeled $READY in <org>/<repo>. Nothing to do."`
86
88
 
87
89
  ## Lifecycle assumed
@@ -89,22 +91,20 @@ The only legitimate reasons to stop early:
89
91
  The GitHub Issues build lifecycle uses **labels** (we deliberately do NOT key off open/closed alone — closed issues aren't always the right post-build state):
90
92
 
91
93
  ```text
92
- ready → claimed → review → done(env-keyed) (downstream merge / archive)
93
- (human) (us claim) (us / PR opens) (us done; PR ready)
94
+ ready → claimed → done(env-keyed)
95
+ (human) (us claim) (us done; PR ready)
94
96
  ```
95
97
 
96
- (Defaults: `status:ready` / `status:in-progress` / `status:code-review` / `status:on-dev`/`status:on-stg`/`status:done`.)
98
+ (Defaults: `status:ready` / `status:in-progress` / `status:on-dev`/`status:on-stg`/`status:done`.)
97
99
 
98
100
  This skill ONLY transitions:
99
101
 
100
102
  - `$READY` → `$CLAIMED` (claim)
101
103
  - `$CLAIMED` → `$DONE` (build complete, PR ready)
102
104
 
103
- It never touches `$REVIEW` (set by the agent / PR open hook), `status:done`-as-terminal (set by merge automation or PM), or any other status.
105
+ A "transition" means: remove the old role label and add the new one, in two `gh issue edit` calls (`--remove-label` + `--add-label`) or one combined call. The skill MUST verify exactly one build-lifecycle label (from the resolved `$READY`/`$CLAIMED`/`$DONE` set) is present after the update having two simultaneously breaks idempotency.
104
106
 
105
- A "transition" means: remove the old role label and add the new one, in two `gh issue edit` calls (`--remove-label` + `--add-label`) or one combined call. The skill MUST verify exactly one build-lifecycle label (from the resolved `$READY`/`$CLAIMED`/`$REVIEW`/`$DONE` set) is present after the update having two simultaneously breaks idempotency.
106
-
107
- **Pre-flight check**: at the start of each cycle, confirm at least one of the resolved role labels (`$READY`, `$CLAIMED`, `$REVIEW`, or any `$DONE` value) exists on the repo via `gh label list --repo <org>/<repo> --json name`. If none exist, the convention has not been adopted — surface the label-convention error and exit.
107
+ **Pre-flight check**: at the start of each cycle, confirm at least one of the resolved role labels (`$READY`, `$CLAIMED`, or any `$DONE` value) exists on the repo via `gh label list --repo <org>/<repo> --json name`. If none exist, the convention has not been adoptedsurface the label-convention error and exit.
108
108
 
109
109
  ## Phases
110
110
 
@@ -127,19 +127,32 @@ If empty, run a secondary check to distinguish a genuinely empty queue from an u
127
127
 
128
128
  ```bash
129
129
  gh label list --repo <org>/<repo> --json name \
130
- | jq -r --arg r "$READY" --arg c "$CLAIMED" --arg v "$REVIEW" --arg d "$DONE" \
131
- '[.[] | .name | select(. == $r or . == $c or . == $v or . == $d)] | length'
130
+ | jq -r --arg r "$READY" --arg c "$CLAIMED" --argjson d "$DONE_LABELS_JSON" \
131
+ '[.[] | .name | select(. == $r or . == $c or (. as $n | $d | index($n)))] | length'
132
132
  ```
133
133
 
134
134
  If none of the configured role labels exist on the repo → label convention not adopted, surface a setup error and exit. If the role labels exist but none are `$READY` on any open issue → genuinely empty queue, exit cleanly with `"No GitHub issues labeled $READY. Nothing to do."`
135
135
 
136
- ### Phase 3 — Process each ready issue (serial)
136
+ ### Phase 3 — Process the first eligible ready issue
137
+
138
+ #### 3a.0 Repo-scope gate (claim only current-repo issues)
139
+
140
+ GitHub Issues live in one repo by definition, so the scanned repo's issues are usually inherently current-repo. But a planning/umbrella repo's issues can target sibling repos, so this skill still claims only issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
141
+
142
+ 1. **Resolve the current repo** per `config-resolution` "Repo scoping" (`.repo` → `.github.repo` → `git remote get-url origin` basename). If unresolvable, stop and report.
143
+ 2. **Cheap path first.** Prefer candidates already carrying the `repo:<current>` label. Keep the Phase 2 scan broad so unlabeled issues are still seen, determined, and stamped.
144
+ 3. **Per candidate, apply the repo-scope decision (`repo-scope-split`):**
145
+ - Carries `repo:<other>` → **skip** (leave it `ready` for that repo's own intake); next candidate.
146
+ - **Unlabeled** → determine the target repo(s) from the issue + code surfaces, then **stamp** `repo:<name>` via `gh issue edit <n> --add-label "repo:<name>"` (create the label lazily) so later cycles filter cheaply; re-apply with the now-known repo. (An issue whose work is entirely in the scanned repo is simply labeled `repo:<current>`.)
147
+ - **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
148
+ - **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
149
+ 4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the ready set is exhausted — exit cleanly with `"No ready issues for repo <current>. Nothing to do."`.
137
150
 
138
151
  #### 3a. Leaf-only claim gate (repair containers)
139
152
 
140
153
  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
154
 
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.
155
+ Run this gate **before** the leaf claim relabel, starting with the oldest/highest-priority ready candidate. 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`, explain that parent/container `$CLAIMED` means rollup/build-lane progress through child/leaf work rather than direct implementation, record it, and end the cycle.
143
156
 
144
157
  **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
158
 
@@ -254,9 +267,9 @@ This close is idempotent: if the issue is already closed, record that native clo
254
267
 
255
268
  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.
256
269
 
257
- #### 3e. Continue
270
+ #### 3e. Stop
258
271
 
259
- Move to the next ready issue. One issue failing does not stop others.
272
+ Stop immediately after the first claimed, skipped, blocked, held, or errored issue. Later scheduler invocations process the remaining ready issues.
260
273
 
261
274
  ### Phase 4 — Summary report
262
275
 
@@ -291,7 +304,7 @@ Total PRs opened: <n>
291
304
  - **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
305
  - **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`.
293
306
  - **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.
294
- - **Failure isolation**: per-issue exceptions caught and recorded; the cycle continues.
307
+ - **One item per cycle**: per-issue exceptions are caught and recorded, then the cycle exits. The scheduler owns retrying or moving on to the next ready item.
295
308
  - **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.
296
309
  - **Single-label invariant**: after every transition, verify exactly one `status:*` label is present on the issue. If two are present (rare race), surface as an Error and skip — do NOT auto-resolve.
297
310
  - **Never pick an arbitrary env for `$DONE`**. If `done` is a map and env is ambiguous, fail loudly.
@@ -304,7 +317,6 @@ Total PRs opened: <n>
304
317
  | `.lisa.config.json` `github.repo` | (from `$ARGUMENTS`) | GitHub repo for the default queue |
305
318
  | `.lisa.config.json` `github.labels.build.ready` | `status:ready` | The label that signals "human says this is buildable" |
306
319
  | `.lisa.config.json` `github.labels.build.claimed` | `status:in-progress` | The label set on pickup |
307
- | `.lisa.config.json` `github.labels.build.review` | `status:code-review` | The label set when the PR opens (owned by `lisa:github-evidence`) |
308
320
  | `.lisa.config.json` `github.labels.build.done` | env-keyed map or string | The label set after a successful build; env-aware |
309
321
  | `.lisa.config.json` `deploy.branches` | — | Reverse-lookup map for env inference from PR base branch |
310
322
 
@@ -329,7 +341,6 @@ Before this skill can run, the repo must adopt the `status:*` label namespace. U
329
341
  ```bash
330
342
  gh label create status:ready --color FBCA04 --description "Ready for build" --repo <org>/<repo>
331
343
  gh label create status:in-progress --color 0E8A16 --description "Build in progress" --repo <org>/<repo>
332
- gh label create status:code-review --color 5319E7 --description "PR open, awaiting review" --repo <org>/<repo>
333
344
  gh label create status:on-dev --color 1D76DB --description "Built, deployed to dev" --repo <org>/<repo>
334
345
  gh label create status:done --color 0E8A16 --description "Shipped" --repo <org>/<repo>
335
346
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: github-evidence
3
- description: "Upload text evidence to the GitHub `pr-assets` release, update PR description, post a GitHub Issue comment with code blocks, and relabel the issue to the configured `review` label (default `status:code-review`). Reusable by any skill that captures evidence and generates evidence/comment.md (and optionally evidence/comment.txt). The GitHub counterpart of lisa:jira-evidence."
3
+ description: "Upload text evidence to the GitHub `pr-assets` release, update PR description, and post a GitHub Issue comment with code blocks. Reusable by any skill that captures evidence and generates evidence/comment.md (and optionally evidence/comment.txt). The GitHub counterpart of lisa:jira-evidence."
4
4
  allowed-tools: ["Bash"]
5
5
  ---
6
6
 
@@ -8,23 +8,6 @@ allowed-tools: ["Bash"]
8
8
 
9
9
  Upload captured evidence and generated templates to the GitHub PR description and the originating GitHub Issue. This skill is the posting step — it assumes evidence files and a comment template already exist in the evidence directory.
10
10
 
11
- ## Workflow resolution
12
-
13
- The `claimed` and `review` build labels are read from `.lisa.config.json` `github.labels.build.*`, falling back to the defaults documented in the `config-resolution` rule (`status:in-progress` and `status:code-review`).
14
-
15
- ```bash
16
- read_role() {
17
- local role="$1" default="$2"
18
- local local_v global_v
19
- local_v=$(jq -r ".github.labels.build.${role} // empty" .lisa.config.local.json 2>/dev/null)
20
- global_v=$(jq -r ".github.labels.build.${role} // empty" .lisa.config.json 2>/dev/null)
21
- echo "${local_v:-${global_v:-$default}}"
22
- }
23
-
24
- CLAIMED=$(read_role claimed "status:in-progress")
25
- REVIEW=$(read_role review "status:code-review")
26
- ```
27
-
28
11
  ## Arguments
29
12
 
30
13
  `$ARGUMENTS`: `<ISSUE_REF> <EVIDENCE_DIR> <PR_NUMBER>`
@@ -92,15 +75,9 @@ REVIEW=$(read_role review "status:code-review")
92
75
  gh issue comment <issue-number> --repo <issue-org>/<issue-repo> --body-file "$EVIDENCE_DIR/comment.md"
93
76
  ```
94
77
 
95
- 6. **Relabel the issue to the configured `review` label**
96
-
97
- ```bash
98
- gh issue edit <issue-number> --repo <issue-org>/<issue-repo> \
99
- --remove-label "$CLAIMED" \
100
- --add-label "$REVIEW"
101
- ```
78
+ 6. **Leave lifecycle labels unchanged**
102
79
 
103
- If the current label is already `$REVIEW`, skip. If neither `$CLAIMED` nor `$REVIEW` is present, log a warning and continue without changing labels the issue may have been hand-managed.
80
+ GitHub evidence posting is evidence-only. The caller that owns the build lifecycle (`lisa:github-build-intake` / `lisa:github-agent`) transitions the issue from the configured `claimed` label directly to the configured `done` label after a successful build. Do not apply `status:code-review` here.
104
81
 
105
82
  ## Evidence Naming Convention
106
83
 
@@ -89,7 +89,7 @@ Use `lisa:github-evidence` to post everything:
89
89
  # Equivalent of: bash scripts/post-evidence.sh — but invoked via the Skill tool
90
90
  ```
91
91
 
92
- Invoke the skill with `<ISSUE_REF> ./evidence <PR_NUMBER>`. It uploads the files to the `pr-assets` release, edits the PR's `## Evidence` section, posts a comment on the issue, and relabels the issue to `status:code-review`.
92
+ Invoke the skill with `<ISSUE_REF> ./evidence <PR_NUMBER>`. It uploads the files to the `pr-assets` release, edits the PR's `## Evidence` section, and posts a comment on the issue. The build-intake owner performs the direct `claimed` `done` lifecycle transition after success.
93
93
 
94
94
  ### Step 6: Verify
95
95
 
@@ -97,7 +97,7 @@ Confirm:
97
97
  - Evidence files exist as release assets named `pr-<PR>-NN-name.{txt,json}`.
98
98
  - The PR description contains the `## Evidence` section with code-block embeds.
99
99
  - The issue has a new comment whose body matches `comment.md`.
100
- - The issue's labels include `status:code-review` and not `status:in-progress`.
100
+ - The issue has the evidence comment, and the build-intake owner can transition it from `status:in-progress` directly to the configured `done` label.
101
101
 
102
102
  ## Verification Patterns Reference
103
103