@codyswann/lisa 2.39.4 → 2.42.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 (47) 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/github-build-intake.md +2 -2
  5. package/plugins/lisa/agents/jira-build-intake.md +2 -2
  6. package/plugins/lisa/agents/linear-build-intake.md +2 -2
  7. package/plugins/lisa/rules/config-resolution.md +26 -4
  8. package/plugins/lisa/rules/leaf-only-lifecycle.md +24 -3
  9. package/plugins/lisa/rules/prd-lifecycle-rollup.md +109 -0
  10. package/plugins/lisa/skills/atlassian-access/SKILL.md +2 -0
  11. package/plugins/lisa/skills/git-submit-pr/SKILL.md +7 -0
  12. package/plugins/lisa/skills/github-build-intake/SKILL.md +14 -0
  13. package/plugins/lisa/skills/implement/SKILL.md +1 -1
  14. package/plugins/lisa/skills/jira-build-intake/SKILL.md +10 -3
  15. package/plugins/lisa/skills/linear-build-intake/SKILL.md +11 -4
  16. package/plugins/lisa/skills/prd-backlink/SKILL.md +57 -1
  17. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +15 -0
  18. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  34. package/plugins/src/base/agents/github-build-intake.md +2 -2
  35. package/plugins/src/base/agents/jira-build-intake.md +2 -2
  36. package/plugins/src/base/agents/linear-build-intake.md +2 -2
  37. package/plugins/src/base/rules/config-resolution.md +26 -4
  38. package/plugins/src/base/rules/leaf-only-lifecycle.md +24 -3
  39. package/plugins/src/base/rules/prd-lifecycle-rollup.md +109 -0
  40. package/plugins/src/base/skills/atlassian-access/SKILL.md +2 -0
  41. package/plugins/src/base/skills/git-submit-pr/SKILL.md +7 -0
  42. package/plugins/src/base/skills/github-build-intake/SKILL.md +14 -0
  43. package/plugins/src/base/skills/implement/SKILL.md +1 -1
  44. package/plugins/src/base/skills/jira-build-intake/SKILL.md +10 -3
  45. package/plugins/src/base/skills/linear-build-intake/SKILL.md +11 -4
  46. package/plugins/src/base/skills/prd-backlink/SKILL.md +57 -1
  47. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +15 -0
@@ -189,8 +189,13 @@ Wait for `lisa:jira-agent` to return. Capture its outcome:
189
189
 
190
190
  If `lisa:jira-agent` returned Success:
191
191
  1. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
192
- 2. Invoke `lisa:atlassian-access` `operation: transition key: <TICKET> to: "$DONE"`.
193
- 3. Post a `[claude-build-intake]` comment via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "Build complete. PR <URL>. Transitioned to $DONE."`
192
+ 2. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
193
+ - If `jira.workflow.done` is a string, that status is terminal.
194
+ - If `jira.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env statuses such as `On Dev` and `On Stg` are not terminal and must remain unresolved / open.
195
+ - If the project uses a different final environment name, resolve it from the configured deployment topology; if ambiguous, record an Error and do not finalize native resolution.
196
+ 3. Invoke `lisa:atlassian-access` `operation: transition key: <TICKET> to: "$DONE"`.
197
+ 4. If `$DONE` is terminal, verify the resulting JIRA issue is natively closed/resolved: status category is `Done`, and resolution is set when the project's workflow requires one. If the transition screen requires an explicit resolution, use the configured default resolution if present; otherwise record an Error naming the missing workflow setup rather than silently landing in an unresolved Done-named status.
198
+ 5. Post a `[claude-build-intake]` comment via `lisa:atlassian-access` `operation: comment key: <TICKET> body: "Build complete. PR <URL>. Transitioned to $DONE."` Include whether terminal native resolution was verified, already satisfied, skipped for an intermediate env, or blocked by workflow setup.
194
199
 
195
200
  For any non-Success outcome, do NOT transition. The ticket sits in `$CLAIMED` (or wherever `lisa:jira-agent` left it for the Blocked case) — the cycle's job is done; humans take it from there.
196
201
 
@@ -226,7 +231,8 @@ Total PRs opened: <n>
226
231
 
227
232
  - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
228
233
  - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:jira-agent` invocation — no double-pickup.
229
- - **No writes outside the lifecycle**: this skill only transitions `$READY → $CLAIMED` and `$CLAIMED → $DONE`. Every other status change is owned by `lisa:jira-agent` (which suggests transitions but only auto-transitions on the verify-FAIL path).
234
+ - **No writes outside the lifecycle**: this skill only transitions `$READY → $CLAIMED` and `$CLAIMED → $DONE`, then verifies terminal native resolution when `$DONE` is the true terminal state per `leaf-only-lifecycle`. Every other status change is owned by `lisa:jira-agent` (which suggests transitions but only auto-transitions on the verify-FAIL path).
235
+ - **Terminal native closure**: for terminal `$DONE`, the resulting JIRA issue must be in a resolved / closed state (`statusCategory = Done` and resolution set when required). Intermediate env statuses stay unresolved / open.
230
236
  - **Failure isolation**: per-ticket exceptions caught and recorded; the cycle continues.
231
237
  - **Single cycle per query**: do not run two `lisa:jira-build-intake` cycles in parallel against overlapping queries — concurrent claims could race. The scheduling layer (when added) is responsible for serialization.
232
238
  - **Never invent a transition**: if `$CLAIMED` or `$DONE` aren't valid transitions in the project's workflow, stop and report rather than guessing alternative names.
@@ -261,6 +267,7 @@ If a ready-equivalent status does not exist in the JIRA project's workflow, this
261
267
  - Never transition a ticket the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
262
268
  - Never bypass `lisa:jira-agent` to do build work directly. `lisa:jira-agent` owns the per-ticket lifecycle (read, verify, triage, route, sync, evidence). This skill is the dispatcher, not the builder.
263
269
  - Never auto-transition past `$DONE`. Downstream statuses are owned by QA / product / a future verification-intake skill — not this one.
270
+ - Never resolve / close a JIRA ticket at intermediate env statuses (`On Dev`, `On Stg`, or configured equivalents). Native resolution is terminal-only.
264
271
  - If the ticket has no Validation Journey or no sign-in credentials in its description, `lisa:jira-agent`'s pre-flight verify will catch it and transition to `Blocked` — **don't try to fix the ticket from here**. Pre-flight gating is `lisa:jira-agent`'s job; running build work on a thin ticket produces broken work.
265
272
  - On any unexpected response from `lisa:jira-agent` (status it doesn't claim, missing PR URL on success, etc.), record as Error and surface — never assume.
266
273
  - Never pick an arbitrary env for `$DONE` resolution. If `done` is a map and env is ambiguous, fail loudly.
@@ -71,7 +71,7 @@ In prose below, the role names refer to the resolved labels: e.g. "the `ready` l
71
71
 
72
72
  ## Why labels, not native states
73
73
 
74
- Linear's per-team workflow state names vary (`Todo` / `Backlog` / `Up Next` / etc.). Labels are workspace-scoped or team-scoped and stable across teams, so we drive the build queue off labels rather than chasing renamed native states. The native `state` field is informational only for this skill.
74
+ Linear's per-team workflow state names vary (`Todo` / `Backlog` / `Up Next` / etc.). Labels are workspace-scoped or team-scoped and stable across teams, so we drive the build queue off labels rather than chasing renamed native states. The native `state` field is informational until terminal completion; at the true terminal `done` value, the `leaf-only-lifecycle` rule requires native closure by moving the Issue to a configured Done / Completed workflow state when one exists.
75
75
 
76
76
  ## Configuration
77
77
 
@@ -199,8 +199,13 @@ Wait for the agent to return. Capture its outcome:
199
199
 
200
200
  If `lisa:linear-agent` returned Success:
201
201
  1. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
202
- 2. Update labels via `mcp__linear-server__save_issue`: remove `$CLAIMED` (or `$REVIEW` if `lisa:linear-evidence` already moved it forward), add `$DONE`.
203
- 3. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL>. Transitioned to $DONE."`
202
+ 2. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
203
+ - If `linear.labels.build.done` is a string, that string is terminal.
204
+ - If `linear.labels.build.done` is an object, only the production/final environment value is terminal (default: `status:done`). Intermediate env values such as `status:on-dev` and `status:on-stg` are not terminal and must keep the native Issue open.
205
+ - If the project uses a different final environment name, resolve it from the configured deployment topology; if ambiguous, record an Error and do not change the native state.
206
+ 3. Update labels via `mcp__linear-server__save_issue`: remove `$CLAIMED` (or `$REVIEW` if `lisa:linear-evidence` already moved it forward), add `$DONE`.
207
+ 4. If `$DONE` is terminal, move the native Linear Issue state to the configured Done / Completed state. Resolve that state from project configuration if present; otherwise inspect the team workflow for a terminal state with `state.type = "completed"` and a name such as `Done` or `Completed`. If no terminal state can be resolved, record an Error and leave the labels as the source of truth — do not invent a state name.
208
+ 5. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL>. Transitioned to $DONE."` Include whether native closure was applied, already satisfied, skipped for an intermediate env, or unavailable for setup reasons.
204
209
 
205
210
  For any non-Success outcome, do NOT transition. The Issue sits where the agent left it — humans take it from there.
206
211
 
@@ -236,7 +241,8 @@ Total PRs opened: <n>
236
241
 
237
242
  - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic/Story/Spike) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
238
243
  - **Claim-first ordering**: `$CLAIMED` set BEFORE agent invocation — no double-pickup.
239
- - **No writes outside the lifecycle**: this skill only adds/removes `$READY`, `$CLAIMED`, `$DONE`. Every other label change (and the native state) is owned by the agent or `lisa:linear-evidence`.
244
+ - **No writes outside the lifecycle**: this skill only adds/removes `$READY`, `$CLAIMED`, `$DONE`, plus terminal-only native state completion required by `leaf-only-lifecycle`. Every other label change (and non-terminal native state change) is owned by the agent or `lisa:linear-evidence`.
245
+ - **Terminal native closure**: after the `$DONE` label is applied, move the Linear Issue to a native completed state only when `$DONE` is the true terminal done value; intermediate env labels stay open / active.
240
246
  - **Failure isolation**: per-Issue exceptions caught and recorded; the cycle continues.
241
247
  - **Single cycle per team**: do not run two concurrent cycles against the same team — concurrent claims could race.
242
248
  - **Single-label invariant**: after every transition, verify exactly one `status:*` label is present. Two simultaneously breaks the build queue.
@@ -258,6 +264,7 @@ If the team hasn't adopted these labels, the first run exits with an adoption hi
258
264
  - Never relabel an Issue the cycle didn't claim. The `$CLAIMED` transition is the signature of cycle ownership.
259
265
  - Never bypass `lisa:linear-agent` to do build work directly. The agent owns the per-Issue lifecycle.
260
266
  - Never auto-transition past `$DONE`. Downstream labels are owned by QA / product / a future verification-intake skill.
267
+ - Never move the native Linear state to Done / Completed for intermediate env states (`status:on-dev`, `status:on-stg`, or configured equivalents). Native completion happens only at the terminal `done` value.
261
268
  - If the Issue has no Validation Journey or no sign-in credentials in its description, `lisa:linear-agent`'s pre-flight verify will catch it and relabel to `status:blocked` — don't try to fix the Issue from here.
262
269
  - On any unexpected response from `lisa:linear-agent` (label it doesn't claim, missing PR URL on success, etc.), record as Error and surface — never assume.
263
270
  - Never pick an arbitrary env for `$DONE` resolution. If `done` is a map and env is ambiguous, fail loudly.
@@ -39,7 +39,8 @@ Pass `$ARGUMENTS` as a single JSON-style block:
39
39
  - `linear` → Linear MCP update
40
40
  - `github` → `gh issue edit --body`
41
41
  - `file` → `Edit` (preferred) or `Write` (full rewrite if needed)
42
- 5. **Return** the rendered section (so the caller can include it in its own report) and the source URL of the updated PRD.
42
+ 5. **Record the PRD→child relationship in the source tool's native hierarchy** where the source supports it and the destination tracker is the same system. For a `github` source whose repo is the same repo as the created tickets, link each generated top-level work item as a native GitHub **sub-issue** of the PRD issue — see [Native parent linking (GitHub)](#native-parent-linking-github). The documented `## Tickets` section from step 3 is always written regardless; native linking is **in addition to** it, not a replacement (the documented section is the cross-vendor and older-host fallback per the `prd-lifecycle-rollup` rule). Sources without native issue hierarchy (Notion, Confluence, file) rely on the documented section alone.
43
+ 6. **Return** the rendered section (so the caller can include it in its own report) and the source URL of the updated PRD.
43
44
 
44
45
  ## Format
45
46
 
@@ -75,6 +76,54 @@ Rendering rules:
75
76
  - Sort epics by key (lexical). Sort stories within an epic by key. Sort sub-tasks within a story by key. Sort the unparented list by `(type, key)`.
76
77
  - The line `_Generated by ..._` is fixed text — does not include a timestamp. A timestamp would defeat the diff-equality check Debrief relies on.
77
78
 
79
+ ## Native parent linking (GitHub)
80
+
81
+ When `source_type: github` **and** the PRD issue lives in the same repository as the created tickets, make the PRD the structural parent of the work it generated by linking each generated top-level work item as a native GitHub **sub-issue** of the PRD issue. This is the GitHub leg of the PRD→child native-hierarchy requirement in the `prd-lifecycle-rollup` rule (cite it by slug; do not restate its taxonomy here). The documented `## Tickets` section is still written either way — native linking is the first-class relationship; the documented section is the durable fallback for cross-vendor and older hosts.
82
+
83
+ This section is GitHub-only. Linear/JIRA native parents and the documented-section-only fallback are governed elsewhere and are out of scope for this section.
84
+
85
+ ### What gets linked — generated top-level work only
86
+
87
+ Per the `prd-lifecycle-rollup` "generated top-level work" contract, the PRD owns **only** its generated top-level work as direct children:
88
+
89
+ - A ticket is **top-level** when its `parent_key` is null/empty — these are the created Epic(s) and any top-level Story created directly under the PRD. Link these as sub-issues of the PRD.
90
+ - A ticket with a non-null `parent_key` is a **descendant** (a Story under an Epic, or a leaf Sub-task) — it is owned by its own top-level parent, **never** linked directly to the PRD. Leaf Sub-tasks are explicitly NOT direct PRD children (PRD #525 non-goal).
91
+
92
+ So if the PRD generated `Epic E1 → Story S1 → Sub-task T1`, only `E1` becomes a sub-issue of the PRD; `S1` is a sub-issue of `E1` and `T1` of `S1` (those links were already made by the write path), and neither `S1` nor `T1` is a direct child of the PRD.
93
+
94
+ ### Same-repo guard
95
+
96
+ Native sub-issue linking only applies when the PRD and the work item are in the same repository. Parse `owner/repo` from `source_ref` (the PRD URL or `<org>/<repo>#<n>` token) and from each top-level ticket's `key`/`url`. Skip native linking for any ticket whose repo differs from the PRD's repo (cross-repo or cross-vendor) — record those in the documented section only. The cross-vendor case (e.g. a GitHub PRD with a JIRA/Linear tracker) never reaches this path because the ticket is not a GitHub issue.
97
+
98
+ ### Idempotency — dedupe by child-ref
99
+
100
+ The dedupe key is **child-ref identity** (`owner/repo#number`), per the `prd-lifecycle-rollup` idempotency rule. Before adding any link, read the PRD's current sub-issues and skip any child already linked, so re-running `prd-backlink` never creates duplicate sub-issue links and is a no-op when everything is already linked.
101
+
102
+ 1. **Read the PRD's existing sub-issues** (same GraphQL `subIssues` query `lisa:github-read-issue` Phase 3 uses), and build the set of already-linked child refs:
103
+
104
+ ```bash
105
+ gh api graphql -f query='query($org:String!,$repo:String!,$number:Int!){repository(owner:$org,name:$repo){issue(number:$number){subIssues(first:100){nodes{number repository{nameWithOwner}}}}}}' \
106
+ -F org=<prd_org> -F repo=<prd_repo> -F number=<prd_number> \
107
+ --jq '.data.repository.issue.subIssues.nodes[] | "\(.repository.nameWithOwner)#\(.number)"'
108
+ ```
109
+
110
+ The resulting `owner/repo#number` strings are the existing child-ref set.
111
+
112
+ 2. **For each generated top-level ticket** in the same repo whose child-ref is **not** already in that set, resolve node IDs and call `addSubIssue` (the same mutation `lisa:github-write-issue` Phase 6 step 3 uses):
113
+
114
+ ```bash
115
+ prd_id=$(gh api graphql -f query='query($org:String!,$repo:String!,$number:Int!){repository(owner:$org,name:$repo){issue(number:$number){id}}}' -F org=<prd_org> -F repo=<prd_repo> -F number=<prd_number> --jq '.data.repository.issue.id')
116
+ child_id=$(gh api graphql -f query='query($org:String!,$repo:String!,$number:Int!){repository(owner:$org,name:$repo){issue(number:$number){id}}}' -F org=<org> -F repo=<repo> -F number=<child_number> --jq '.data.repository.issue.id')
117
+ gh api graphql -f query='mutation($parentId:ID!,$childId:ID!){addSubIssue(input:{issueId:$parentId,subIssueId:$childId}){issue{number}subIssue{number}}}' -F parentId="$prd_id" -F childId="$child_id"
118
+ ```
119
+
120
+ A child already in the existing-sub-issue set is a no-op — do not call the mutation for it.
121
+
122
+ ### Graceful degradation
123
+
124
+ - **Already linked.** Covered by the dedupe read above (no mutation issued). If a concurrent run linked it between the read and the mutation, GitHub rejects the duplicate — treat that rejection as success (the desired end state already holds), not a failure.
125
+ - **Mutation unavailable** (older GHES, sub-issues feature off, or the `addSubIssue`/`subIssues` fields are missing). Fall back to the documented `## Tickets` section only and surface a warning to the caller (`native sub-issue linking unavailable — documented section written`). Never silently drop the relationship and never abort the run — the documented section is the contract that always lands.
126
+
78
127
  ## Failures
79
128
 
80
129
  - **Source unreachable / permission denied.** Stop and report. Do not silently swallow.
@@ -86,4 +135,11 @@ Rendering rules:
86
135
  ```text
87
136
  PRD back-link updated: <source_url>
88
137
  Section: ## Tickets — <n> epics, <n> stories, <n> sub-tasks, <n> unparented (<bugs/spikes>)
138
+ Native sub-issues: <n> linked, <n> already linked (GitHub same-repo only; documented section is the fallback)
89
139
  ```
140
+
141
+ Omit the `Native sub-issues` line when the source is not a same-repo GitHub PRD (no native linking applies). If native linking was attempted but the mutation was unavailable, replace the counts with the degradation warning from [Graceful degradation](#graceful-degradation).
142
+
143
+ ## Related rules
144
+
145
+ - `prd-lifecycle-rollup` — the vendor-neutral source of truth for PRD→generated-top-level-work ownership, the per-vendor terminal predicate, PRD `shipped` rollup, and the child-ref idempotency dedupe key. This skill implements the GitHub native-linking leg of that rule; it cites the rule by slug rather than restating its taxonomy.
@@ -10,6 +10,8 @@ Thin dispatcher. Resolves the configured destination tracker and delegates to th
10
10
 
11
11
  See the `config-resolution` rule for configuration and dispatch table.
12
12
 
13
+ The vendor scanners also own the terminal native-closure step from `leaf-only-lifecycle`: after a leaf reaches the true terminal `done` value, they close / resolve / complete the native tracker item where supported, while leaving intermediate env states open.
14
+
13
15
  ## Workflow
14
16
 
15
17
  1. Resolve tracker config (same logic as `lisa:tracker-write`).
@@ -37,9 +39,22 @@ This is the claim-time arm of the rule. Its siblings are the write-time labeling
37
39
 
38
40
  The shim never needs to inspect the item itself — it forwards `$ARGUMENTS` verbatim and the resolved vendor scanner runs its Phase 3a gate before any claim.
39
41
 
42
+ ## Terminal native-closure contract (forwarded to every vendor)
43
+
44
+ This shim also forwards the `leaf-only-lifecycle` terminal native-closure contract. It does not decide whether a `done` value is terminal; the vendor scanner resolves that from its own config and deployment topology after the per-item agent succeeds.
45
+
46
+ | Tracker | Vendor scanner behavior at true terminal `done` |
47
+ |---|---|
48
+ | `github` | apply the terminal `done` label, then `gh issue close --reason completed` |
49
+ | `jira` | transition to the configured terminal status and verify native resolved / closed state |
50
+ | `linear` | apply the terminal `done` label, then move the Issue to the configured completed workflow state |
51
+
52
+ Intermediate env states are not native closure. A vendor scanner that resolves `On Dev`, `On Stg`, `status:on-dev`, `status:on-stg`, or a configured equivalent leaves the item open / unresolved.
53
+
40
54
  ## Rules
41
55
 
42
56
  - Single cycle per invocation — the vendor skill processes the current `Ready` set and exits.
43
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.
44
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.
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.
45
60
  - Never run two intake cycles concurrently against overlapping queues — the scheduling layer is responsible for serialization.