@codyswann/lisa 2.259.1 → 2.259.3

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 (81) 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/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +76 -4
  5. package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +3 -1
  6. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +9 -0
  8. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +5 -1
  9. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +77 -5
  10. package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +3 -1
  11. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +1 -1
  12. package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +9 -0
  13. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +5 -1
  14. package/plugins/lisa-agy/plugin.json +1 -1
  15. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +77 -5
  16. package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +3 -1
  17. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +1 -1
  18. package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +9 -0
  19. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +5 -1
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +77 -5
  27. package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +3 -1
  28. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +1 -1
  29. package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +9 -0
  30. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +5 -1
  31. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +77 -5
  33. package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +3 -1
  34. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +1 -1
  35. package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +9 -0
  36. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +5 -1
  37. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-agy/plugin.json +1 -1
  40. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  45. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  50. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  55. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  60. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-agy/plugin.json +1 -1
  65. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  70. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  75. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +77 -5
  78. package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +3 -1
  79. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +1 -1
  80. package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +9 -0
  81. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +5 -1
package/package.json CHANGED
@@ -102,7 +102,7 @@
102
102
  "form-data": ">=4.0.6"
103
103
  },
104
104
  "name": "@codyswann/lisa",
105
- "version": "2.259.1",
105
+ "version": "2.259.3",
106
106
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
107
107
  "main": "dist/index.js",
108
108
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -246,11 +246,16 @@ head's checks to start, then re-enable auto-merge (section 1). In
246
246
  `on_blocker=report` mode this whole step is off-limits (diagnose-only): do not
247
247
  merge, close, or delete anything — return `blocked:pending-auto-fix`.
248
248
 
249
- ## 3. Merge and verify it actually shipped (ancestry check)
249
+ ## 3. Merge and verify it actually shipped (ancestry + deploy run)
250
250
 
251
251
  Enabling auto-merge + green checks + resolved threads is **not** proof the merge
252
- included your fix. Auto-merge can land the PRIOR head the instant gates go green,
253
- before a late fix commit becomes the head. After the PR reports `MERGED`:
252
+ included your fix, and a passing merge **not** proof anything deployed. Both must
253
+ be verified after the PR reports `MERGED`.
254
+
255
+ ### a. Ancestry check — is my code in the merged branch
256
+
257
+ Auto-merge can land the PRIOR head the instant gates go green, before a late fix
258
+ commit becomes the head:
254
259
 
255
260
  ```bash
256
261
  git fetch origin
@@ -263,11 +268,78 @@ ship — fix forward with a new commit/PR and re-drive. Re-confirm after any com
263
268
  that lands while auto-merge is enabled; a successful merge of an older head is a
264
269
  failed drive-to-merge outcome, not a successful closeout.
265
270
 
271
+ ### b. Deploy-run check — did a deploy/release workflow run actually fire
272
+
273
+ Ancestry proves your code is *in* the merged branch; it does **not** prove
274
+ anything deployed. GitHub can **suppress the `on: push` event for a merge commit
275
+ created by auto-merge or a bot token** (`GITHUB_TOKEN`), so the deploy workflow
276
+ fires **zero** runs — no run, not even a `startup_failure` — while the ancestry
277
+ check above stays green. Incident of record: TunnlAI/frontend **TUN-186** (PR #67)
278
+ merged to `dev` via auto-merge; the merge commit `1b3f836` produced **no**
279
+ `deploy.yml` run, and only the next human push `d1fe18c` (which carries `1b3f836`
280
+ as an ancestor) actually shipped it. **Never report shipped on ancestry alone.**
281
+
282
+ After ancestry passes, capture the merge SHA and poll for a **deploy/release
283
+ workflow run** whose head SHA **is the merge SHA or an including descendant** (a
284
+ run whose head has the merge SHA as an ancestor also shipped the merge, mirroring
285
+ `d1fe18c` shipping `1b3f836`), keyed to the merged-into branch. The observing
286
+ workflow name is **not fixed** — do **not** hardcode `deploy.yml`:
287
+
288
+ - **Downstream projects:** the deploy/release workflow run keyed to the base
289
+ branch resolved via `.lisa.config.json` `deploy.branches` (the merged-into env
290
+ branch) — the same "deploy run keyed to the merged-into branch via
291
+ `deploy.branches`" observation `lisa-linear-build-intake` performs before
292
+ relabeling.
293
+ - **lisa / other repos:** the repo's release or publish workflow for
294
+ `<baseRefName>`.
295
+
296
+ Discover the run with the same `gh run list --json …headSha…` pattern
297
+ `lisa-verify-workflow-change` uses:
298
+
299
+ ```bash
300
+ merge_sha=$(gh pr view <pr> --json mergeCommit -q .mergeCommit.oid)
301
+ gh run list --branch <baseRefName> --commit "$merge_sha" \
302
+ --json databaseId,workflowName,status,conclusion,headSha,headBranch,createdAt --limit 20
303
+ ```
304
+
305
+ **Bounded wait:** a just-created run can take a few seconds to register — poll
306
+ briefly (a small number of short intervals / a short ceiling, mirroring the "wait
307
+ for that head's checks to start" bound used above) before concluding the run is
308
+ absent, so a not-yet-registered run is not mis-read as zero. When no run matches
309
+ the merge SHA directly, also accept a descendant run whose head has `$merge_sha`
310
+ as an ancestor (`git merge-base --is-ancestor "$merge_sha" <run_head_sha>`).
311
+
312
+ **Zero runs after the bounded wait — do NOT report shipped.** Recover in order:
313
+
314
+ 1. **Dispatch the deploy, then re-verify.** Trigger the env's `workflow_dispatch`
315
+ for the merged-into branch and re-poll for a run that now covers the merge SHA
316
+ (or an including descendant):
317
+ ```bash
318
+ gh workflow run <deploy-or-release-workflow> --ref <baseRefName>
319
+ ```
320
+ Once such a run appears, the merge is confirmed shipped.
321
+ 2. **Still zero, or dispatch not permitted → surface a blocker.** Emit a hard
322
+ block (`blocked:deploy`) reporting exactly what was observed — the merge SHA,
323
+ the base branch, and zero deploy runs. A failed/blocked path, **never** a
324
+ silent "done".
325
+
326
+ In **`on_blocker=report`** mode this deploy-run step is diagnose-only: dispatching
327
+ a workflow is an action, so do **not** run `gh workflow run` / `workflow_dispatch`
328
+ — classify the absence as `blocked:deploy` (or `blocked:no-deploy-run`) and return
329
+ it for the caller to act on, consistent with the report-mode contract (steps b–f
330
+ of section 2 are diagnose-only).
331
+
266
332
  ## 4. Terminal states
267
333
 
268
334
  Loop until one of:
269
335
 
270
- - **`MERGED`** and the ancestry check passes success.
336
+ - **`MERGED`** and the ancestry check passes **and** a deploy/release run for the
337
+ merge SHA (or an including descendant) is confirmed — observed directly or after
338
+ a `workflow_dispatch` recovery → success. Ancestry alone is **not** success.
339
+ - **`blocked:deploy`** — merged with ancestry green, but after the bounded wait no
340
+ deploy/release run fired for the merge SHA and dispatch recovery could not
341
+ confirm one (or was not permitted, e.g. `on_blocker=report`). Stop and report
342
+ the merge SHA, base branch, and zero observed runs — never a silent "done".
271
343
  - **`awaiting-human`** (`auto_merge=false` only) → success. The PR is `OPEN`,
272
344
  required checks are green, the review gate is clear, and
273
345
  `mergeable == MERGEABLE`, with auto-merge deliberately not enabled
@@ -51,7 +51,9 @@ Add provider-appropriate linkage to the PR title and/or body without changing th
51
51
  - For cross-repo issue refs, use the fully qualified form, for example `Closes CodySwannGT/lisa#614` or `Refs CodySwannGT/lisa#614`.
52
52
  - **Linear**:
53
53
  - Ensure the Linear issue identifier appears in the branch name when the branch is created upstream by `lisa-implement`.
54
- - Include the identifier in the PR title or body, for example `Linear: ENG-123`, so Linear's GitHub integration can attach the PR.
54
+ - Include the identifier as a **non-closing** attach token in the PR title or body, for example `Linear: ENG-123` or `Refs ENG-123`, so Linear's GitHub integration can attach the PR without completing the Issue.
55
+ - **Do not** emit a Linear magic word (`Closes`/`Fixes`/`Resolves ENG-123`) in the PR title, body, or commit message unless the target branch is the terminal/production branch — the repository default branch or the configured production branch from `.lisa.config.json` `deploy.branches.production` (resolved via `config-resolution`). Unlike GitHub, whose `Closes` auto-close is scoped to the default branch, Linear's integration completes a linked Issue on merge to **any branch**, so a magic word on a non-terminal env merge (for example into `dev` or `staging`) auto-closes the Issue prematurely and front-runs the env-keyed `status:*` label ladder. This is the `leaf-only-lifecycle` "Terminal native closure" invariant (native closure only at the production terminal `done`) — cite it, do not restate.
56
+ - On a non-terminal env branch, use only the non-closing attach form and strip/neutralize any magic word copied from a ticket title or commit message. Branch-name linkage alone can still auto-complete the Issue where a Linear team enables "complete on any linked-PR merge" — behavior we cannot suppress from our side — so the post-merge reconciliation in `lisa-linear-sync` is the mandatory backstop, not an optional cleanup.
55
57
  - **JIRA**:
56
58
  - Ensure the JIRA issue key appears in the branch name when the branch is created upstream by `lisa-implement`.
57
59
  - Include the key in the PR title or body, for example `JIRA: PROJ-456`, so the GitHub-JIRA integration can attach the PR.
@@ -283,7 +283,7 @@ If the lifecycle run returned Success:
283
283
  - 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.
284
284
  - 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.
285
285
  4. Update labels via `lisa-linear-access operation: save-issue`: remove `$CLAIMED` (or `$REVIEW` if `lisa-linear-evidence` already moved it forward), add `$DONE`.
286
- 5. 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.
286
+ 5. 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. Conversely, if `$DONE` is an intermediate env yet Linear already natively completed the Issue (a magic-word / branch-linkage front-run — Linear auto-completes on merge to **any branch**, per the asymmetry documented in `git-submit-pr`), reconcile it back to active via `lisa-linear-sync` Phase 4b so the native state mirrors the non-terminal env instead of a premature closure.
287
287
  6. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL> merged. Transitioned to $DONE."` Include whether native closure was applied, already satisfied, skipped for an intermediate env, or unavailable for setup reasons.
288
288
 
289
289
  For any non-Success outcome, do NOT transition. The Issue sits where the lifecycle left it — humans take it from there.
@@ -92,6 +92,15 @@ Verify exactly one `status:*` label remains after the update — having two simu
92
92
 
93
93
  Without `--update-label`, this skill posts the comment only and does NOT touch labels.
94
94
 
95
+ ## Phase 4b — Reconcile Native Auto-Close (Linear-specific)
96
+
97
+ Linear's GitHub integration completes a linked Issue on merge to **any branch** — unlike GitHub's default-branch-scoped `Closes` auto-close — so a magic word (`Closes`/`Fixes`/`Resolves ENG-123`) or branch-name linkage can natively move the Issue to a Done/Completed workflow `state` at a **non-terminal** env merge, front-running the env-keyed `status:*` label ladder. `git-submit-pr` prevents the magic-word case; this phase is the required post-merge repair for the residual (branch-linkage) case we cannot suppress server-side. Run it on a `pr-merged` sync whose resolved env is **intermediate** (below the production terminal `done`):
98
+
99
+ 1. Resolve the merged PR's base branch to its env via `.lisa.config.json` `deploy.branches` (`config-resolution`). If it maps to the production/terminal `done`, this phase is a **no-op** — native completion is correct there.
100
+ 2. **Uniform / single-environment no-op.** When the project's env-keyed `done` map is uniform — every environment resolves to the same `status:done`, as in this repo (`production: main` only) and TunnlAI/frontend — dev-merge == terminal, so native completion is correct. Do nothing. Only a **non-uniform** env→`done` map (distinct `status:on-dev`/`status:on-stg`/`status:done` rungs) can desync.
101
+ 3. Otherwise (non-uniform map, resolved env intermediate): re-read the Issue's native workflow `state`. If Linear natively moved it into a Done/Completed category while the derived `status:*` label is a lower env, **re-open** the native `state` back to the active/in-progress category (via `lisa-linear-access operation: save-issue`) so it mirrors the true env stage, and post a short `[lisa-linear-sync]` reconciliation comment. This applies the `leaf-only-lifecycle` "Terminal native closure" rule — native closure fires **only** at the production terminal `done` — as a *repair* of Linear front-running it. Cite the rule by slug; do not restate it.
102
+ 4. **Safe default.** If the true terminal cannot be resolved (ambiguous env or unresolvable `done` map), do not change the native `state` — post a `[lisa-linear-sync]` reconciliation-suggestion comment and leave it untouched, mirroring the Phase 5 safe default.
103
+
95
104
  ## Phase 5 — Parent Status Rollup (`--rollup`)
96
105
 
97
106
  When the caller passes `--rollup`, this skill **derives a parent/container's `status:*` label from the roll-up of its children** instead of acting on a leaf. A **Project** (the Epic equivalent) rolls up from its Issues; an **Issue** rolls up from its sub-Issues. This implements the Linear child-issue-status arm of the **Parent status rollup (the state machine)** section of the `leaf-only-lifecycle` rule — cite that rule, do not restate the policy.
@@ -38,7 +38,11 @@ close-out** roles and moves work *unstuck* or *fully closed*:
38
38
  one missed by dependency-only re-checks: nothing else is blocking it, so re-running the same gate
39
39
  against its current content is the only way to know it is now passable.
40
40
  - **Terminal-open drift** — an item already carrying its true terminal lifecycle role (for
41
- example GitHub `status:done`) but still open/active in the provider's native state.
41
+ example GitHub `status:done`) but still open/active in the provider's native state. The inverse
42
+ also drifts on Linear: a leaf whose native `state` was auto-completed by a magic-word / branch-linkage
43
+ merge into a **non-terminal** env (Linear completes on merge to any branch, unlike GitHub's
44
+ default-branch-scoped close) while its derived `status:*` label is still intermediate — reconcile
45
+ it back to active via `lisa-linear-sync` Phase 4b per `leaf-only-lifecycle`.
42
46
  - **Rollup drift** — a parent/container item (Epic, Story, PRD, Linear Project, or equivalent)
43
47
  whose own lifecycle state does not match the roll-up of its children's states per
44
48
  `leaf-only-lifecycle`. This covers the *completed* case (all children terminal → close the parent
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-drive-pr-to-merge
3
- description: This skill should be used to drive a pull request all the way to MERGED, handling ANYTHING that blocks the merge. It enables auto-merge when the repo supports it (direct-merge fallback otherwise), keeps the branch rebased/synced and resolves merge conflicts, fixes failing CI/deploy checks, addresses and resolves every human and bot review comment (CodeRabbit, etc.) — implementing valid feedback and replying-then-resolving invalid feedback — dismisses stale CHANGES_REQUESTED gates, and verifies the fix actually shipped (auto-merge race ancestry check). Composable and inline — invoked by other skills (e.g. git-submit-pr, implement, sync-down) via the Skill tool, never as a standalone user command.
3
+ description: This skill should be used to drive a pull request all the way to MERGED, handling ANYTHING that blocks the merge. It enables auto-merge when the repo supports it (direct-merge fallback otherwise), keeps the branch rebased/synced and resolves merge conflicts, fixes failing CI/deploy checks, addresses and resolves every human and bot review comment (CodeRabbit, etc.) — implementing valid feedback and replying-then-resolving invalid feedback — dismisses stale CHANGES_REQUESTED gates, and verifies the fix actually shipped — both merge ancestry and that a deploy/release run fired for the merge SHA (auto-merge race + zero-deploy-run check). Composable and inline — invoked by other skills (e.g. git-submit-pr, implement, sync-down) via the Skill tool, never as a standalone user command.
4
4
  allowed-tools: ["Bash", "Read", "Edit", "Write", "Grep", "Glob", "Skill"]
5
5
  ---
6
6
 
@@ -246,11 +246,16 @@ head's checks to start, then re-enable auto-merge (section 1). In
246
246
  `on_blocker=report` mode this whole step is off-limits (diagnose-only): do not
247
247
  merge, close, or delete anything — return `blocked:pending-auto-fix`.
248
248
 
249
- ## 3. Merge and verify it actually shipped (ancestry check)
249
+ ## 3. Merge and verify it actually shipped (ancestry + deploy run)
250
250
 
251
251
  Enabling auto-merge + green checks + resolved threads is **not** proof the merge
252
- included your fix. Auto-merge can land the PRIOR head the instant gates go green,
253
- before a late fix commit becomes the head. After the PR reports `MERGED`:
252
+ included your fix, and a passing merge **not** proof anything deployed. Both must
253
+ be verified after the PR reports `MERGED`.
254
+
255
+ ### a. Ancestry check — is my code in the merged branch
256
+
257
+ Auto-merge can land the PRIOR head the instant gates go green, before a late fix
258
+ commit becomes the head:
254
259
 
255
260
  ```bash
256
261
  git fetch origin
@@ -263,11 +268,78 @@ ship — fix forward with a new commit/PR and re-drive. Re-confirm after any com
263
268
  that lands while auto-merge is enabled; a successful merge of an older head is a
264
269
  failed drive-to-merge outcome, not a successful closeout.
265
270
 
271
+ ### b. Deploy-run check — did a deploy/release workflow run actually fire
272
+
273
+ Ancestry proves your code is *in* the merged branch; it does **not** prove
274
+ anything deployed. GitHub can **suppress the `on: push` event for a merge commit
275
+ created by auto-merge or a bot token** (`GITHUB_TOKEN`), so the deploy workflow
276
+ fires **zero** runs — no run, not even a `startup_failure` — while the ancestry
277
+ check above stays green. Incident of record: TunnlAI/frontend **TUN-186** (PR #67)
278
+ merged to `dev` via auto-merge; the merge commit `1b3f836` produced **no**
279
+ `deploy.yml` run, and only the next human push `d1fe18c` (which carries `1b3f836`
280
+ as an ancestor) actually shipped it. **Never report shipped on ancestry alone.**
281
+
282
+ After ancestry passes, capture the merge SHA and poll for a **deploy/release
283
+ workflow run** whose head SHA **is the merge SHA or an including descendant** (a
284
+ run whose head has the merge SHA as an ancestor also shipped the merge, mirroring
285
+ `d1fe18c` shipping `1b3f836`), keyed to the merged-into branch. The observing
286
+ workflow name is **not fixed** — do **not** hardcode `deploy.yml`:
287
+
288
+ - **Downstream projects:** the deploy/release workflow run keyed to the base
289
+ branch resolved via `.lisa.config.json` `deploy.branches` (the merged-into env
290
+ branch) — the same "deploy run keyed to the merged-into branch via
291
+ `deploy.branches`" observation `lisa-linear-build-intake` performs before
292
+ relabeling.
293
+ - **lisa / other repos:** the repo's release or publish workflow for
294
+ `<baseRefName>`.
295
+
296
+ Discover the run with the same `gh run list --json …headSha…` pattern
297
+ `lisa-verify-workflow-change` uses:
298
+
299
+ ```bash
300
+ merge_sha=$(gh pr view <pr> --json mergeCommit -q .mergeCommit.oid)
301
+ gh run list --branch <baseRefName> --commit "$merge_sha" \
302
+ --json databaseId,workflowName,status,conclusion,headSha,headBranch,createdAt --limit 20
303
+ ```
304
+
305
+ **Bounded wait:** a just-created run can take a few seconds to register — poll
306
+ briefly (a small number of short intervals / a short ceiling, mirroring the "wait
307
+ for that head's checks to start" bound used above) before concluding the run is
308
+ absent, so a not-yet-registered run is not mis-read as zero. When no run matches
309
+ the merge SHA directly, also accept a descendant run whose head has `$merge_sha`
310
+ as an ancestor (`git merge-base --is-ancestor "$merge_sha" <run_head_sha>`).
311
+
312
+ **Zero runs after the bounded wait — do NOT report shipped.** Recover in order:
313
+
314
+ 1. **Dispatch the deploy, then re-verify.** Trigger the env's `workflow_dispatch`
315
+ for the merged-into branch and re-poll for a run that now covers the merge SHA
316
+ (or an including descendant):
317
+ ```bash
318
+ gh workflow run <deploy-or-release-workflow> --ref <baseRefName>
319
+ ```
320
+ Once such a run appears, the merge is confirmed shipped.
321
+ 2. **Still zero, or dispatch not permitted → surface a blocker.** Emit a hard
322
+ block (`blocked:deploy`) reporting exactly what was observed — the merge SHA,
323
+ the base branch, and zero deploy runs. A failed/blocked path, **never** a
324
+ silent "done".
325
+
326
+ In **`on_blocker=report`** mode this deploy-run step is diagnose-only: dispatching
327
+ a workflow is an action, so do **not** run `gh workflow run` / `workflow_dispatch`
328
+ — classify the absence as `blocked:deploy` (or `blocked:no-deploy-run`) and return
329
+ it for the caller to act on, consistent with the report-mode contract (steps b–f
330
+ of section 2 are diagnose-only).
331
+
266
332
  ## 4. Terminal states
267
333
 
268
334
  Loop until one of:
269
335
 
270
- - **`MERGED`** and the ancestry check passes success.
336
+ - **`MERGED`** and the ancestry check passes **and** a deploy/release run for the
337
+ merge SHA (or an including descendant) is confirmed — observed directly or after
338
+ a `workflow_dispatch` recovery → success. Ancestry alone is **not** success.
339
+ - **`blocked:deploy`** — merged with ancestry green, but after the bounded wait no
340
+ deploy/release run fired for the merge SHA and dispatch recovery could not
341
+ confirm one (or was not permitted, e.g. `on_blocker=report`). Stop and report
342
+ the merge SHA, base branch, and zero observed runs — never a silent "done".
271
343
  - **`awaiting-human`** (`auto_merge=false` only) → success. The PR is `OPEN`,
272
344
  required checks are green, the review gate is clear, and
273
345
  `mergeable == MERGEABLE`, with auto-merge deliberately not enabled
@@ -51,7 +51,9 @@ Add provider-appropriate linkage to the PR title and/or body without changing th
51
51
  - For cross-repo issue refs, use the fully qualified form, for example `Closes CodySwannGT/lisa#614` or `Refs CodySwannGT/lisa#614`.
52
52
  - **Linear**:
53
53
  - Ensure the Linear issue identifier appears in the branch name when the branch is created upstream by `lisa-implement`.
54
- - Include the identifier in the PR title or body, for example `Linear: ENG-123`, so Linear's GitHub integration can attach the PR.
54
+ - Include the identifier as a **non-closing** attach token in the PR title or body, for example `Linear: ENG-123` or `Refs ENG-123`, so Linear's GitHub integration can attach the PR without completing the Issue.
55
+ - **Do not** emit a Linear magic word (`Closes`/`Fixes`/`Resolves ENG-123`) in the PR title, body, or commit message unless the target branch is the terminal/production branch — the repository default branch or the configured production branch from `.lisa.config.json` `deploy.branches.production` (resolved via `config-resolution`). Unlike GitHub, whose `Closes` auto-close is scoped to the default branch, Linear's integration completes a linked Issue on merge to **any branch**, so a magic word on a non-terminal env merge (for example into `dev` or `staging`) auto-closes the Issue prematurely and front-runs the env-keyed `status:*` label ladder. This is the `leaf-only-lifecycle` "Terminal native closure" invariant (native closure only at the production terminal `done`) — cite it, do not restate.
56
+ - On a non-terminal env branch, use only the non-closing attach form and strip/neutralize any magic word copied from a ticket title or commit message. Branch-name linkage alone can still auto-complete the Issue where a Linear team enables "complete on any linked-PR merge" — behavior we cannot suppress from our side — so the post-merge reconciliation in `lisa-linear-sync` is the mandatory backstop, not an optional cleanup.
55
57
  - **JIRA**:
56
58
  - Ensure the JIRA issue key appears in the branch name when the branch is created upstream by `lisa-implement`.
57
59
  - Include the key in the PR title or body, for example `JIRA: PROJ-456`, so the GitHub-JIRA integration can attach the PR.
@@ -283,7 +283,7 @@ If the lifecycle run returned Success:
283
283
  - 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.
284
284
  - 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.
285
285
  4. Update labels via `lisa-linear-access operation: save-issue`: remove `$CLAIMED` (or `$REVIEW` if `lisa-linear-evidence` already moved it forward), add `$DONE`.
286
- 5. 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.
286
+ 5. 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. Conversely, if `$DONE` is an intermediate env yet Linear already natively completed the Issue (a magic-word / branch-linkage front-run — Linear auto-completes on merge to **any branch**, per the asymmetry documented in `git-submit-pr`), reconcile it back to active via `lisa-linear-sync` Phase 4b so the native state mirrors the non-terminal env instead of a premature closure.
287
287
  6. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL> merged. Transitioned to $DONE."` Include whether native closure was applied, already satisfied, skipped for an intermediate env, or unavailable for setup reasons.
288
288
 
289
289
  For any non-Success outcome, do NOT transition. The Issue sits where the lifecycle left it — humans take it from there.
@@ -92,6 +92,15 @@ Verify exactly one `status:*` label remains after the update — having two simu
92
92
 
93
93
  Without `--update-label`, this skill posts the comment only and does NOT touch labels.
94
94
 
95
+ ## Phase 4b — Reconcile Native Auto-Close (Linear-specific)
96
+
97
+ Linear's GitHub integration completes a linked Issue on merge to **any branch** — unlike GitHub's default-branch-scoped `Closes` auto-close — so a magic word (`Closes`/`Fixes`/`Resolves ENG-123`) or branch-name linkage can natively move the Issue to a Done/Completed workflow `state` at a **non-terminal** env merge, front-running the env-keyed `status:*` label ladder. `git-submit-pr` prevents the magic-word case; this phase is the required post-merge repair for the residual (branch-linkage) case we cannot suppress server-side. Run it on a `pr-merged` sync whose resolved env is **intermediate** (below the production terminal `done`):
98
+
99
+ 1. Resolve the merged PR's base branch to its env via `.lisa.config.json` `deploy.branches` (`config-resolution`). If it maps to the production/terminal `done`, this phase is a **no-op** — native completion is correct there.
100
+ 2. **Uniform / single-environment no-op.** When the project's env-keyed `done` map is uniform — every environment resolves to the same `status:done`, as in this repo (`production: main` only) and TunnlAI/frontend — dev-merge == terminal, so native completion is correct. Do nothing. Only a **non-uniform** env→`done` map (distinct `status:on-dev`/`status:on-stg`/`status:done` rungs) can desync.
101
+ 3. Otherwise (non-uniform map, resolved env intermediate): re-read the Issue's native workflow `state`. If Linear natively moved it into a Done/Completed category while the derived `status:*` label is a lower env, **re-open** the native `state` back to the active/in-progress category (via `lisa-linear-access operation: save-issue`) so it mirrors the true env stage, and post a short `[lisa-linear-sync]` reconciliation comment. This applies the `leaf-only-lifecycle` "Terminal native closure" rule — native closure fires **only** at the production terminal `done` — as a *repair* of Linear front-running it. Cite the rule by slug; do not restate it.
102
+ 4. **Safe default.** If the true terminal cannot be resolved (ambiguous env or unresolvable `done` map), do not change the native `state` — post a `[lisa-linear-sync]` reconciliation-suggestion comment and leave it untouched, mirroring the Phase 5 safe default.
103
+
95
104
  ## Phase 5 — Parent Status Rollup (`--rollup`)
96
105
 
97
106
  When the caller passes `--rollup`, this skill **derives a parent/container's `status:*` label from the roll-up of its children** instead of acting on a leaf. A **Project** (the Epic equivalent) rolls up from its Issues; an **Issue** rolls up from its sub-Issues. This implements the Linear child-issue-status arm of the **Parent status rollup (the state machine)** section of the `leaf-only-lifecycle` rule — cite that rule, do not restate the policy.
@@ -38,7 +38,11 @@ close-out** roles and moves work *unstuck* or *fully closed*:
38
38
  one missed by dependency-only re-checks: nothing else is blocking it, so re-running the same gate
39
39
  against its current content is the only way to know it is now passable.
40
40
  - **Terminal-open drift** — an item already carrying its true terminal lifecycle role (for
41
- example GitHub `status:done`) but still open/active in the provider's native state.
41
+ example GitHub `status:done`) but still open/active in the provider's native state. The inverse
42
+ also drifts on Linear: a leaf whose native `state` was auto-completed by a magic-word / branch-linkage
43
+ merge into a **non-terminal** env (Linear completes on merge to any branch, unlike GitHub's
44
+ default-branch-scoped close) while its derived `status:*` label is still intermediate — reconcile
45
+ it back to active via `lisa-linear-sync` Phase 4b per `leaf-only-lifecycle`.
42
46
  - **Rollup drift** — a parent/container item (Epic, Story, PRD, Linear Project, or equivalent)
43
47
  whose own lifecycle state does not match the roll-up of its children's states per
44
48
  `leaf-only-lifecycle`. This covers the *completed* case (all children terminal → close the parent
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-drive-pr-to-merge
3
- description: This skill should be used to drive a pull request all the way to MERGED, handling ANYTHING that blocks the merge. It enables auto-merge when the repo supports it (direct-merge fallback otherwise), keeps the branch rebased/synced and resolves merge conflicts, fixes failing CI/deploy checks, addresses and resolves every human and bot review comment (CodeRabbit, etc.) — implementing valid feedback and replying-then-resolving invalid feedback — dismisses stale CHANGES_REQUESTED gates, and verifies the fix actually shipped (auto-merge race ancestry check). Composable and inline — invoked by other skills (e.g. git-submit-pr, implement, sync-down) via the Skill tool, never as a standalone user command.
3
+ description: This skill should be used to drive a pull request all the way to MERGED, handling ANYTHING that blocks the merge. It enables auto-merge when the repo supports it (direct-merge fallback otherwise), keeps the branch rebased/synced and resolves merge conflicts, fixes failing CI/deploy checks, addresses and resolves every human and bot review comment (CodeRabbit, etc.) — implementing valid feedback and replying-then-resolving invalid feedback — dismisses stale CHANGES_REQUESTED gates, and verifies the fix actually shipped — both merge ancestry and that a deploy/release run fired for the merge SHA (auto-merge race + zero-deploy-run check). Composable and inline — invoked by other skills (e.g. git-submit-pr, implement, sync-down) via the Skill tool, never as a standalone user command.
4
4
  allowed-tools: ["Bash", "Read", "Edit", "Write", "Grep", "Glob", "Skill"]
5
5
  ---
6
6
 
@@ -246,11 +246,16 @@ head's checks to start, then re-enable auto-merge (section 1). In
246
246
  `on_blocker=report` mode this whole step is off-limits (diagnose-only): do not
247
247
  merge, close, or delete anything — return `blocked:pending-auto-fix`.
248
248
 
249
- ## 3. Merge and verify it actually shipped (ancestry check)
249
+ ## 3. Merge and verify it actually shipped (ancestry + deploy run)
250
250
 
251
251
  Enabling auto-merge + green checks + resolved threads is **not** proof the merge
252
- included your fix. Auto-merge can land the PRIOR head the instant gates go green,
253
- before a late fix commit becomes the head. After the PR reports `MERGED`:
252
+ included your fix, and a passing merge **not** proof anything deployed. Both must
253
+ be verified after the PR reports `MERGED`.
254
+
255
+ ### a. Ancestry check — is my code in the merged branch
256
+
257
+ Auto-merge can land the PRIOR head the instant gates go green, before a late fix
258
+ commit becomes the head:
254
259
 
255
260
  ```bash
256
261
  git fetch origin
@@ -263,11 +268,78 @@ ship — fix forward with a new commit/PR and re-drive. Re-confirm after any com
263
268
  that lands while auto-merge is enabled; a successful merge of an older head is a
264
269
  failed drive-to-merge outcome, not a successful closeout.
265
270
 
271
+ ### b. Deploy-run check — did a deploy/release workflow run actually fire
272
+
273
+ Ancestry proves your code is *in* the merged branch; it does **not** prove
274
+ anything deployed. GitHub can **suppress the `on: push` event for a merge commit
275
+ created by auto-merge or a bot token** (`GITHUB_TOKEN`), so the deploy workflow
276
+ fires **zero** runs — no run, not even a `startup_failure` — while the ancestry
277
+ check above stays green. Incident of record: TunnlAI/frontend **TUN-186** (PR #67)
278
+ merged to `dev` via auto-merge; the merge commit `1b3f836` produced **no**
279
+ `deploy.yml` run, and only the next human push `d1fe18c` (which carries `1b3f836`
280
+ as an ancestor) actually shipped it. **Never report shipped on ancestry alone.**
281
+
282
+ After ancestry passes, capture the merge SHA and poll for a **deploy/release
283
+ workflow run** whose head SHA **is the merge SHA or an including descendant** (a
284
+ run whose head has the merge SHA as an ancestor also shipped the merge, mirroring
285
+ `d1fe18c` shipping `1b3f836`), keyed to the merged-into branch. The observing
286
+ workflow name is **not fixed** — do **not** hardcode `deploy.yml`:
287
+
288
+ - **Downstream projects:** the deploy/release workflow run keyed to the base
289
+ branch resolved via `.lisa.config.json` `deploy.branches` (the merged-into env
290
+ branch) — the same "deploy run keyed to the merged-into branch via
291
+ `deploy.branches`" observation `lisa-linear-build-intake` performs before
292
+ relabeling.
293
+ - **lisa / other repos:** the repo's release or publish workflow for
294
+ `<baseRefName>`.
295
+
296
+ Discover the run with the same `gh run list --json …headSha…` pattern
297
+ `lisa-verify-workflow-change` uses:
298
+
299
+ ```bash
300
+ merge_sha=$(gh pr view <pr> --json mergeCommit -q .mergeCommit.oid)
301
+ gh run list --branch <baseRefName> --commit "$merge_sha" \
302
+ --json databaseId,workflowName,status,conclusion,headSha,headBranch,createdAt --limit 20
303
+ ```
304
+
305
+ **Bounded wait:** a just-created run can take a few seconds to register — poll
306
+ briefly (a small number of short intervals / a short ceiling, mirroring the "wait
307
+ for that head's checks to start" bound used above) before concluding the run is
308
+ absent, so a not-yet-registered run is not mis-read as zero. When no run matches
309
+ the merge SHA directly, also accept a descendant run whose head has `$merge_sha`
310
+ as an ancestor (`git merge-base --is-ancestor "$merge_sha" <run_head_sha>`).
311
+
312
+ **Zero runs after the bounded wait — do NOT report shipped.** Recover in order:
313
+
314
+ 1. **Dispatch the deploy, then re-verify.** Trigger the env's `workflow_dispatch`
315
+ for the merged-into branch and re-poll for a run that now covers the merge SHA
316
+ (or an including descendant):
317
+ ```bash
318
+ gh workflow run <deploy-or-release-workflow> --ref <baseRefName>
319
+ ```
320
+ Once such a run appears, the merge is confirmed shipped.
321
+ 2. **Still zero, or dispatch not permitted → surface a blocker.** Emit a hard
322
+ block (`blocked:deploy`) reporting exactly what was observed — the merge SHA,
323
+ the base branch, and zero deploy runs. A failed/blocked path, **never** a
324
+ silent "done".
325
+
326
+ In **`on_blocker=report`** mode this deploy-run step is diagnose-only: dispatching
327
+ a workflow is an action, so do **not** run `gh workflow run` / `workflow_dispatch`
328
+ — classify the absence as `blocked:deploy` (or `blocked:no-deploy-run`) and return
329
+ it for the caller to act on, consistent with the report-mode contract (steps b–f
330
+ of section 2 are diagnose-only).
331
+
266
332
  ## 4. Terminal states
267
333
 
268
334
  Loop until one of:
269
335
 
270
- - **`MERGED`** and the ancestry check passes success.
336
+ - **`MERGED`** and the ancestry check passes **and** a deploy/release run for the
337
+ merge SHA (or an including descendant) is confirmed — observed directly or after
338
+ a `workflow_dispatch` recovery → success. Ancestry alone is **not** success.
339
+ - **`blocked:deploy`** — merged with ancestry green, but after the bounded wait no
340
+ deploy/release run fired for the merge SHA and dispatch recovery could not
341
+ confirm one (or was not permitted, e.g. `on_blocker=report`). Stop and report
342
+ the merge SHA, base branch, and zero observed runs — never a silent "done".
271
343
  - **`awaiting-human`** (`auto_merge=false` only) → success. The PR is `OPEN`,
272
344
  required checks are green, the review gate is clear, and
273
345
  `mergeable == MERGEABLE`, with auto-merge deliberately not enabled
@@ -51,7 +51,9 @@ Add provider-appropriate linkage to the PR title and/or body without changing th
51
51
  - For cross-repo issue refs, use the fully qualified form, for example `Closes CodySwannGT/lisa#614` or `Refs CodySwannGT/lisa#614`.
52
52
  - **Linear**:
53
53
  - Ensure the Linear issue identifier appears in the branch name when the branch is created upstream by `lisa-implement`.
54
- - Include the identifier in the PR title or body, for example `Linear: ENG-123`, so Linear's GitHub integration can attach the PR.
54
+ - Include the identifier as a **non-closing** attach token in the PR title or body, for example `Linear: ENG-123` or `Refs ENG-123`, so Linear's GitHub integration can attach the PR without completing the Issue.
55
+ - **Do not** emit a Linear magic word (`Closes`/`Fixes`/`Resolves ENG-123`) in the PR title, body, or commit message unless the target branch is the terminal/production branch — the repository default branch or the configured production branch from `.lisa.config.json` `deploy.branches.production` (resolved via `config-resolution`). Unlike GitHub, whose `Closes` auto-close is scoped to the default branch, Linear's integration completes a linked Issue on merge to **any branch**, so a magic word on a non-terminal env merge (for example into `dev` or `staging`) auto-closes the Issue prematurely and front-runs the env-keyed `status:*` label ladder. This is the `leaf-only-lifecycle` "Terminal native closure" invariant (native closure only at the production terminal `done`) — cite it, do not restate.
56
+ - On a non-terminal env branch, use only the non-closing attach form and strip/neutralize any magic word copied from a ticket title or commit message. Branch-name linkage alone can still auto-complete the Issue where a Linear team enables "complete on any linked-PR merge" — behavior we cannot suppress from our side — so the post-merge reconciliation in `lisa-linear-sync` is the mandatory backstop, not an optional cleanup.
55
57
  - **JIRA**:
56
58
  - Ensure the JIRA issue key appears in the branch name when the branch is created upstream by `lisa-implement`.
57
59
  - Include the key in the PR title or body, for example `JIRA: PROJ-456`, so the GitHub-JIRA integration can attach the PR.
@@ -283,7 +283,7 @@ If the lifecycle run returned Success:
283
283
  - 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.
284
284
  - 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.
285
285
  4. Update labels via `lisa-linear-access operation: save-issue`: remove `$CLAIMED` (or `$REVIEW` if `lisa-linear-evidence` already moved it forward), add `$DONE`.
286
- 5. 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.
286
+ 5. 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. Conversely, if `$DONE` is an intermediate env yet Linear already natively completed the Issue (a magic-word / branch-linkage front-run — Linear auto-completes on merge to **any branch**, per the asymmetry documented in `git-submit-pr`), reconcile it back to active via `lisa-linear-sync` Phase 4b so the native state mirrors the non-terminal env instead of a premature closure.
287
287
  6. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL> merged. Transitioned to $DONE."` Include whether native closure was applied, already satisfied, skipped for an intermediate env, or unavailable for setup reasons.
288
288
 
289
289
  For any non-Success outcome, do NOT transition. The Issue sits where the lifecycle left it — humans take it from there.
@@ -92,6 +92,15 @@ Verify exactly one `status:*` label remains after the update — having two simu
92
92
 
93
93
  Without `--update-label`, this skill posts the comment only and does NOT touch labels.
94
94
 
95
+ ## Phase 4b — Reconcile Native Auto-Close (Linear-specific)
96
+
97
+ Linear's GitHub integration completes a linked Issue on merge to **any branch** — unlike GitHub's default-branch-scoped `Closes` auto-close — so a magic word (`Closes`/`Fixes`/`Resolves ENG-123`) or branch-name linkage can natively move the Issue to a Done/Completed workflow `state` at a **non-terminal** env merge, front-running the env-keyed `status:*` label ladder. `git-submit-pr` prevents the magic-word case; this phase is the required post-merge repair for the residual (branch-linkage) case we cannot suppress server-side. Run it on a `pr-merged` sync whose resolved env is **intermediate** (below the production terminal `done`):
98
+
99
+ 1. Resolve the merged PR's base branch to its env via `.lisa.config.json` `deploy.branches` (`config-resolution`). If it maps to the production/terminal `done`, this phase is a **no-op** — native completion is correct there.
100
+ 2. **Uniform / single-environment no-op.** When the project's env-keyed `done` map is uniform — every environment resolves to the same `status:done`, as in this repo (`production: main` only) and TunnlAI/frontend — dev-merge == terminal, so native completion is correct. Do nothing. Only a **non-uniform** env→`done` map (distinct `status:on-dev`/`status:on-stg`/`status:done` rungs) can desync.
101
+ 3. Otherwise (non-uniform map, resolved env intermediate): re-read the Issue's native workflow `state`. If Linear natively moved it into a Done/Completed category while the derived `status:*` label is a lower env, **re-open** the native `state` back to the active/in-progress category (via `lisa-linear-access operation: save-issue`) so it mirrors the true env stage, and post a short `[lisa-linear-sync]` reconciliation comment. This applies the `leaf-only-lifecycle` "Terminal native closure" rule — native closure fires **only** at the production terminal `done` — as a *repair* of Linear front-running it. Cite the rule by slug; do not restate it.
102
+ 4. **Safe default.** If the true terminal cannot be resolved (ambiguous env or unresolvable `done` map), do not change the native `state` — post a `[lisa-linear-sync]` reconciliation-suggestion comment and leave it untouched, mirroring the Phase 5 safe default.
103
+
95
104
  ## Phase 5 — Parent Status Rollup (`--rollup`)
96
105
 
97
106
  When the caller passes `--rollup`, this skill **derives a parent/container's `status:*` label from the roll-up of its children** instead of acting on a leaf. A **Project** (the Epic equivalent) rolls up from its Issues; an **Issue** rolls up from its sub-Issues. This implements the Linear child-issue-status arm of the **Parent status rollup (the state machine)** section of the `leaf-only-lifecycle` rule — cite that rule, do not restate the policy.
@@ -38,7 +38,11 @@ close-out** roles and moves work *unstuck* or *fully closed*:
38
38
  one missed by dependency-only re-checks: nothing else is blocking it, so re-running the same gate
39
39
  against its current content is the only way to know it is now passable.
40
40
  - **Terminal-open drift** — an item already carrying its true terminal lifecycle role (for
41
- example GitHub `status:done`) but still open/active in the provider's native state.
41
+ example GitHub `status:done`) but still open/active in the provider's native state. The inverse
42
+ also drifts on Linear: a leaf whose native `state` was auto-completed by a magic-word / branch-linkage
43
+ merge into a **non-terminal** env (Linear completes on merge to any branch, unlike GitHub's
44
+ default-branch-scoped close) while its derived `status:*` label is still intermediate — reconcile
45
+ it back to active via `lisa-linear-sync` Phase 4b per `leaf-only-lifecycle`.
42
46
  - **Rollup drift** — a parent/container item (Epic, Story, PRD, Linear Project, or equivalent)
43
47
  whose own lifecycle state does not match the roll-up of its children's states per
44
48
  `leaf-only-lifecycle`. This covers the *completed* case (all children terminal → close the parent
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.259.1",
3
+ "version": "2.259.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"