@codyswann/lisa 4.27.2 → 4.27.4

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 (125) hide show
  1. package/cdk/create-only/.github/workflows/ci.yml +79 -4
  2. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  3. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  4. package/dist/core/upstream-evidence-manifest.js +24 -12
  5. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  6. package/package.json +3 -3
  7. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +84 -1
  10. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +3 -3
  11. package/plugins/lisa/.codex-plugin/skills/lisa-linear-claim/SKILL.md +2 -2
  12. package/plugins/lisa/.codex-plugin/skills/lisa-linear-evidence/SKILL.md +4 -4
  13. package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +5 -2
  14. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +5 -5
  15. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +3 -2
  16. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +11 -1
  17. package/plugins/lisa/.codex-plugin/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
  18. package/plugins/lisa/agents/linear-agent.md +12 -12
  19. package/plugins/lisa/agents/linear-build-intake.md +1 -1
  20. package/plugins/lisa/scripts/linear-state-write-target.mjs +362 -0
  21. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +84 -1
  22. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +3 -3
  23. package/plugins/lisa/skills/lisa-linear-claim/SKILL.md +2 -2
  24. package/plugins/lisa/skills/lisa-linear-evidence/SKILL.md +5 -5
  25. package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +5 -2
  26. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +5 -5
  27. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +3 -2
  28. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +11 -1
  29. package/plugins/lisa/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
  30. package/plugins/lisa-agy/agents/linear-agent.md +12 -12
  31. package/plugins/lisa-agy/agents/linear-build-intake.md +1 -1
  32. package/plugins/lisa-agy/plugin.json +1 -1
  33. package/plugins/lisa-agy/scripts/linear-state-write-target.mjs +362 -0
  34. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +84 -1
  35. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +3 -3
  36. package/plugins/lisa-agy/skills/lisa-linear-claim/SKILL.md +2 -2
  37. package/plugins/lisa-agy/skills/lisa-linear-evidence/SKILL.md +5 -5
  38. package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +5 -2
  39. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +5 -5
  40. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +3 -2
  41. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +11 -1
  42. package/plugins/lisa-agy/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
  43. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  46. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-copilot/agents/linear-agent.agent.md +12 -12
  50. package/plugins/lisa-copilot/agents/linear-build-intake.agent.md +1 -1
  51. package/plugins/lisa-copilot/scripts/linear-state-write-target.mjs +362 -0
  52. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +84 -1
  53. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +3 -3
  54. package/plugins/lisa-copilot/skills/lisa-linear-claim/SKILL.md +2 -2
  55. package/plugins/lisa-copilot/skills/lisa-linear-evidence/SKILL.md +5 -5
  56. package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +5 -2
  57. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +5 -5
  58. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +3 -2
  59. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +11 -1
  60. package/plugins/lisa-copilot/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
  61. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-cursor/agents/linear-agent.md +12 -12
  63. package/plugins/lisa-cursor/agents/linear-build-intake.md +1 -1
  64. package/plugins/lisa-cursor/scripts/linear-state-write-target.mjs +362 -0
  65. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +84 -1
  66. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +3 -3
  67. package/plugins/lisa-cursor/skills/lisa-linear-claim/SKILL.md +2 -2
  68. package/plugins/lisa-cursor/skills/lisa-linear-evidence/SKILL.md +5 -5
  69. package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +5 -2
  70. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +5 -5
  71. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +3 -2
  72. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +11 -1
  73. package/plugins/lisa-cursor/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
  74. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-expo-agy/plugin.json +1 -1
  77. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  82. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  87. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  91. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  92. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  96. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  97. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  101. package/plugins/lisa-rails-agy/plugin.json +1 -1
  102. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  106. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  107. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  111. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  112. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  114. package/plugins/src/base/agents/linear-agent.md +12 -12
  115. package/plugins/src/base/agents/linear-build-intake.md +1 -1
  116. package/plugins/src/base/scripts/linear-state-write-target.mjs +362 -0
  117. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +84 -1
  118. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +3 -3
  119. package/plugins/src/base/skills/lisa-linear-claim/SKILL.md +2 -2
  120. package/plugins/src/base/skills/lisa-linear-evidence/SKILL.md +5 -5
  121. package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +5 -2
  122. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +5 -5
  123. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +3 -2
  124. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +11 -1
  125. package/plugins/src/base/skills/lisa-validate-tracker-mapping/SKILL.md +3 -1
package/package.json CHANGED
@@ -161,7 +161,7 @@
161
161
  "zod-validation-error": "^4.0.0"
162
162
  },
163
163
  "name": "@codyswann/lisa",
164
- "version": "4.27.2",
164
+ "version": "4.27.4",
165
165
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
166
166
  "main": "dist/index.js",
167
167
  "exports": {
@@ -305,6 +305,6 @@
305
305
  "test": "tests"
306
306
  },
307
307
  "types": "./dist/index.d.ts",
308
- "lisaReleaseCommit": "8fb0c84fb2111879e4237d81372cdaa1119c48be",
309
- "gitHead": "8fb0c84fb2111879e4237d81372cdaa1119c48be"
308
+ "lisaReleaseCommit": "97d9811b1db340406a0060ffa52677c0c0a2ea37",
309
+ "gitHead": "97d9811b1db340406a0060ffa52677c0c0a2ea37"
310
310
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.27.2",
3
+ "version": "4.27.4",
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": "4.27.2",
3
+ "version": "4.27.4",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -20,7 +20,7 @@ operation: get-project id:<ID>
20
20
  operation: save-project payload:{...}
21
21
  operation: list-issues [team:<ID>] [project:<ID>] [label:<NAME>] [state:<NAME>] [state_type:<arr>]
22
22
  operation: get-issue id:<ID>
23
- operation: save-issue payload:{...}
23
+ operation: save-issue payload:{...} [lifecycle_role:<ROLE>] [env:<KEY>]
24
24
  operation: list-workflow-states team:<ID>
25
25
  operation: create-workflow-state payload:{...}
26
26
  operation: list-comments issue_id:<ID>
@@ -184,6 +184,82 @@ linear_graphql() {
184
184
  Map operation names to Linear GraphQL queries/mutations in this access skill.
185
185
  Consumers pass business-shaped arguments only; they do not embed GraphQL.
186
186
 
187
+ ## `save-issue` — a state write is RESOLVED here, never accepted here
188
+
189
+ **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
+ that changes the workflow state declares `lifecycle_role:<ROLE>` — the semantic
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`,
192
+ `qa.queue`, `qa.certified`) — plus `env:<KEY>` when the role is the env-indexed
193
+ `done`. This layer then resolves that role against config and the team's own
194
+ catalog, and sends the ID **it** resolved.
195
+
196
+ ```bash
197
+ # The team's own catalog, read through this layer's `list-workflow-states`
198
+ # operation for the IDENTITY-MATCHED team, and written to a file so the
199
+ # resolver judges the same bytes the caller looked at. A catalog fetched from
200
+ # some other team is not a smaller catalog, it is the wrong one.
201
+ STATES=$(mktemp)
202
+ linear_list_workflow_states "$TEAM_ID" >"$STATES"
203
+
204
+ # Same trusted ladder `read_linear_key` walks, and for the same reason: a
205
+ # `.opencode`-layout repo that was never handed CLAUDE_PLUGIN_ROOT has no route
206
+ # to the script at all, and a guard that cannot be found is a guard that does
207
+ # not run. Machine-managed rungs only — a checkout-local `plugins/lisa` is
208
+ # repository-controlled executable code, not trusted merely by path.
209
+ resolve_state_guard() {
210
+ local candidate
211
+ for candidate in \
212
+ "${CLAUDE_PLUGIN_ROOT:+$CLAUDE_PLUGIN_ROOT/scripts/linear-state-write-target.mjs}" \
213
+ "${PLUGIN_ROOT:+$PLUGIN_ROOT/scripts/linear-state-write-target.mjs}" \
214
+ node_modules/@codyswann/lisa/plugins/lisa/scripts/linear-state-write-target.mjs; do
215
+ [ -n "$candidate" ] && [ -f "$candidate" ] && { echo "$candidate"; return; }
216
+ done
217
+ # Fail CLOSED and name every path. An unfindable guard must never degrade
218
+ # into an unguarded write.
219
+ echo "Error: could not locate linear-state-write-target.mjs; refusing the state write." >&2
220
+ return 1
221
+ }
222
+
223
+ linear_state_target() { # role [env] -> the ONE writable state id, or exit 2
224
+ local guard
225
+ guard=$(resolve_state_guard) || return 1
226
+ node "$guard" \
227
+ --role "$1" ${2:+--env "$2"} --states "$STATES" \
228
+ ${LIFECYCLE_ASSERT_STATE_ID:+--state-id "$LIFECYCLE_ASSERT_STATE_ID"}
229
+ }
230
+
231
+ STATE_ID=$(linear_state_target "$LIFECYCLE_ROLE" "$LIFECYCLE_ENV") || {
232
+ # Nothing is dispatched. The script already named the role and the
233
+ # configured value it expected on stderr; surface that verbatim.
234
+ exit 2
235
+ }
236
+ ```
237
+
238
+ Run it **before** either transport — before `linear_graphql`, before any
239
+ `mcp__linear-server__*` call. A refusal is a hard stop that sends zero
240
+ mutations, not a warning.
241
+
242
+ Why the layer resolves instead of validating: a validator standing in front of a
243
+ caller-supplied `stateId` checks one value and dispatches another, which is
244
+ exactly the shape that let a caller reach a human-only review lane in the first
245
+ place. Here the checked value and the sent value are the same object — there is
246
+ no channel through which an unvalidated state write can be expressed.
247
+
248
+ A caller that already read an ID off the board MAY pass it as `stateId` in the
249
+ payload. It is treated as an **assertion**, never as the value to send: the
250
+ layer resolves the role's target independently and refuses the call if the two
251
+ differ, naming the role and the configured value. Two further refusals matter:
252
+
253
+ - `stateId` in a payload with **no** `lifecycle_role` is refused. There is no
254
+ such thing as a state write that does not apply a lifecycle role.
255
+ - An **optional role the project never bound** (`review`, `qa.*`) is refused,
256
+ not defaulted. Absent means *skip the transition*, per `config-resolution` R1
257
+ — the caller skips the write entirely rather than asking for one here.
258
+
259
+ **Metadata-only `save-issue` is untouched.** A payload with no `stateId` and no
260
+ `lifecycle_role` — description, assignee, labels, `projectId`, `parentId` — runs
261
+ exactly as before; lifecycle validation has nothing to say about it.
262
+
187
263
  ## `list-workflow-states` — the team's states, and which one it creates into
188
264
 
189
265
  `list-workflow-states team:<ID>` returns one node per state with `id`, `name`,
@@ -295,3 +371,10 @@ query($id:String!){
295
371
  - Missing token plus missing MCP is a hard failure naming `LINEAR_API_KEY`.
296
372
  - Mutations send only the fields being changed, matching existing Linear skill
297
373
  guidance that `save_*` style updates should not clobber unrelated fields.
374
+ - Every workflow-state write is resolved by
375
+ `scripts/linear-state-write-target.mjs` from a declared `lifecycle_role`,
376
+ before either transport. The ID dispatched is the ID that script returned —
377
+ never one a caller supplied. A caller-supplied `stateId` is an assertion that
378
+ must match, and a `stateId` with no declared role is refused.
379
+ - A refusal is fail-closed and operator-readable: it names the lifecycle role
380
+ and the configured value, exits nonzero, and sends nothing.
@@ -246,10 +246,10 @@ This gate never blocks a legitimate flat Task/Bug: those have no open children a
246
246
 
247
247
  **The two `claim-time-guards` run third and fourth — still before the transition below.** Both semantics live in that one vendor-neutral slug; do not restate them here. Linear wiring only:
248
248
 
249
- 1. **`two-failed-attempts` valve.** Count `[lisa-build-attempt]` markers on the Issue from the read bundle's comments (match on the marker, never the title). With two or more, do **not** claim: set `stateId` to the configured blocked state via `lisa-linear-access operation: save-issue` (resolved from `linear.workflow.blocked` per `config-resolution`), post the operator-readable comment naming both attempts via `save-comment`, and **stop the cycle** at Phase 3e. Every non-success terminal outcome recorded in 3c/3d also appends a fresh `<!-- [lisa-build-attempt] n=<N> outcome=<outcome> -->` marker so the next cycle can count it.
249
+ 1. **`two-failed-attempts` valve.** Count `[lisa-build-attempt]` markers on the Issue from the read bundle's comments (match on the marker, never the title). With two or more, do **not** claim: invoke `lisa-linear-access operation: save-issue lifecycle_role: blocked` (the access layer resolves `linear.workflow.blocked` itself per `config-resolution`; this skill supplies the role, never a state ID), post the operator-readable comment naming both attempts via `save-comment`, and **stop the cycle** at Phase 3e. Every non-success terminal outcome recorded in 3c/3d also appends a fresh `<!-- [lisa-build-attempt] n=<N> outcome=<outcome> -->` marker so the next cycle can count it.
250
250
  2. **`already-implemented` check.** Probe for this Issue's own key — `git log --all --grep "<IDENTIFIER>"` and the merged/open PRs referencing `<IDENTIFIER>` (`gh pr list --state all --search "<IDENTIFIER>"` in the bound repo; Linear's own GitHub attachments in the read bundle are the cheaper first look). On a hit, claim as normal but route 3c to **verify-and-close** instead of `lisa-implement`: verify what shipped against the Issue's acceptance criteria, post evidence via `lisa-linear-evidence` naming the shipping PR/commit, then run the ordinary 3d transition and rollup. A partial hit implements only the remaining gap. An unreadable history degrades to "no hit" and the ordinary path proceeds — the guard never blocks the claim. This is not `DUPLICATE_ALREADY_FIXED` (a *different* canonical Issue) and not `claim-archaeology` (a *different* ancestor Issue); it is this Issue's own work already having shipped without a transition.
251
251
 
252
- Transition the Issue via `lisa-linear-access operation: save-issue` by setting `stateId` to the `$CLAIMED` state. Resolve state IDs via `list-workflow-states`; a missing `$CLAIMED` state is a setup defect (see the pre-flight check) — never create one here.
252
+ Transition the Issue via `lisa-linear-access operation: save-issue lifecycle_role: claimed`. The access layer resolves the `claimed` role's exact configured state against the team catalog and dispatches that ID; a missing or ambiguous `$CLAIMED` state is a setup defect it refuses on (see the pre-flight check) — never create one here.
253
253
 
254
254
  **Assign to the authenticated user when the Issue is unassigned.** A claim must be attributable. If the Issue has no assignee, set its `assigneeId` to the authenticated viewer (resolve the viewer's id via the Linear MCP identity — e.g. `get_user` for the current actor) through `lisa-linear-access operation: save-issue`. Leave an already-assigned Issue's assignee untouched — never reassign work that already has an owner.
255
255
 
@@ -339,7 +339,7 @@ If the lifecycle run returned Success:
339
339
  - If `linear.workflow.done` is a string, that state is terminal.
340
340
  - If `linear.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env states such as `On Dev` and `On Stg` are not terminal — they are typed `started`, so the Issue correctly stays open and on the board.
341
341
  - 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.
342
- 4. Transition via `lisa-linear-access operation: save-issue`, setting `stateId` to the `$DONE` state (from `$CLAIMED`, or from `$REVIEW` if `lisa-linear-evidence` already moved it forward).
342
+ 4. Transition via `lisa-linear-access operation: save-issue lifecycle_role: done env: <resolved-env-key>` (from `$CLAIMED`, or from `$REVIEW` if `lisa-linear-evidence` already moved it forward). Pass `env` **only where `linear.workflow.done` is a map of environments**: there the key is mandatory and the access layer refuses a bare `done`; where `done` is a single state the project has no environment rungs and the layer refuses a stray key just as firmly. Either way the layer resolves `$DONE` itself rather than accepting the ID this phase computed.
343
343
  5. **That single write IS the native closure** when `$DONE` is terminal — there is no second step, because the role and the native state are now the same field. When `$DONE` is an intermediate env (`On Dev` / `On Stg`), the state is typed `started`, so the Issue stays open and active by construction rather than by a compensating repair. If a git automation or magic word has already front-run the Issue into a `completed` state while the resolved env is intermediate, reconcile it back via `lisa-linear-sync` Phase 4b — and treat a recurrence as a setup defect, since `/lisa:setup:linear` offers to remove the `merge → Done` automation that causes it.
344
344
  6. Post a `[claude-build-intake]` comment: `"Build complete. PR <URL> merged. Transitioned to $DONE."`
345
345
 
@@ -8,11 +8,11 @@ allowed-tools: ["Bash", "Skill", "Read"]
8
8
 
9
9
  Claim exactly one canonical Linear identifier such as `ENG-123`. All Linear access goes through `lisa-linear-access`. This is the reusable direct-session counterpart of `lisa-linear-build-intake` Phase 3b.
10
10
 
11
- 1. Resolve merged `linear.workspace`, `linear.teamKey`, and the Linear build-lifecycle roles from local-over-global config. Require the identifier's team key to equal the configured team. Resolve ready and claimed roles from `linear.workflow` (defaults `Ready` and `In Progress`); do not hardcode lifecycle decisions.
11
+ 1. Resolve merged `linear.workspace`, `linear.teamKey`, and the Linear build-lifecycle roles from local-over-global config. Require the identifier's team key to equal the configured team. Resolve the `ready` and `claimed` roles through `scripts/resolve-lifecycle-role.mjs` (`--vendor linear`). Both are REQUIRED roles with **no built-in default** — an unset one is a setup defect to report, never a name to substitute. Do not hardcode lifecycle decisions.
12
12
  2. Invoke `lisa-linear-read-issue <identifier>` immediately before mutation. Reject a completed/canceled issue, active blocker, `repo:<other>` issue, any issue with open sub-Issues, or an Epic per `repo-scope-split` and `leaf-only-lifecycle`.
13
13
  3. Inspect the Issue's live workflow **state**:
14
14
  - Already claimed, review, environment-done, or another configured later non-terminal role -> preserve it and set `claim_outcome: reused`.
15
- - In the configured `ready` state -> invoke `lisa-linear-access operation: save-issue` setting `stateId` to the `claimed` state (resolve state IDs with `list-workflow-states`; a missing state is a setup defect, never created here). This is the idempotency lock.
15
+ - In the configured `ready` state -> invoke `lisa-linear-access operation: save-issue lifecycle_role: claimed`. The access layer resolves the `claimed` role's exact configured state itself and dispatches that ID; this skill never hands it a state ID to trust. A missing or ambiguous configured state is a setup defect the layer refuses on, never created here. This is the idempotency lock.
16
16
  - In any OTHER non-terminal state -> reject. An Issue that is not in the `ready` state has not been sanctioned for build, and absence of a lifecycle marker is no longer evidence of readiness: since the lane moved to states, every Issue always has one, so "no lifecycle label" is not a state this can observe.
17
17
  - Terminal/completed -> reject; never regress completed work.
18
18
  4. If and only if the Issue is unassigned, resolve the authenticated viewer and set its `assigneeId` through `lisa-linear-access operation: save-issue`. Leave an existing assignee untouched.
@@ -6,7 +6,7 @@ allowed-tools: ["Bash", "Skill"]
6
6
 
7
7
  # Linear Evidence: $ARGUMENTS
8
8
 
9
- Post verification evidence to a Linear Issue and transition it from the configured `claimed` build label to the configured `review` build label. This skill is the destination of the `lisa-tracker-evidence` shim when `tracker = "linear"`.
9
+ Post verification evidence to a Linear Issue and transition it from the configured `claimed` workflow **state** to the configured `review` workflow **state**. Linear's build lane is state-driven, not label-driven — `linear.labels.build.{ready,claimed,review,done}` is inert and nothing reads it (see `lisa-validate-tracker-mapping`). This skill is the destination of the `lisa-tracker-evidence` shim when `tracker = "linear"`.
10
10
 
11
11
  `$ARGUMENTS` is the Linear Issue identifier (e.g. `ENG-123`) and the path to the evidence directory. Caller passes both: `<IDENTIFIER> <evidence-dir>`.
12
12
 
@@ -104,15 +104,15 @@ Linear comments support markdown including `<details>` collapsibles, fenced code
104
104
 
105
105
  **If `$REVIEW` is empty, skip this phase entirely** — post the evidence comment and leave the Issue in `$CLAIMED`. Report it plainly (`No linear.workflow.review configured; leaving <ID> in its current (claimed) state`) so the skip is visible rather than silent. This is a supported configuration, not a failure.
106
106
 
107
- When `$REVIEW` is non-empty, update labels via `lisa-linear-access operation: save-issue` to remove `$CLAIMED` and add `$REVIEW`. Resolve label IDs first via `lisa-linear-access operation: list-issue-labels` (create the label via `create_issue_label` if it doesn't exist on the team).
107
+ When `$REVIEW` is non-empty, invoke `lisa-linear-access operation: save-issue lifecycle_role: review`. That is the whole transition: the Linear build lane is the native workflow **state**, so there is no accompanying label move — do not remove a `claimed` label or add a `review` one, and do not create either. Those keys are inert.
108
108
 
109
- The native Linear `state` field is updated to `$REVIEW` **only when the resolver returned it from config**. Never resolve the state by searching the team for something review-shaped: on a board with more than one review-ish state that picks by position, not by intent, and the states most likely to be found that way are the human-only ones a project deliberately kept out of its config.
109
+ The access layer resolves `$REVIEW`'s exact configured state itself and dispatches that ID; this skill never hands it one. Never resolve the state by searching the team for something review-shaped: on a board with more than one review-ish state that picks by position, not by intent, and the states most likely to be found that way are the human-only ones a project deliberately kept out of its config. The access layer refuses such a write outright — an unbound optional `review` is a refusal there, not a guess.
110
110
 
111
111
  ## Phase 6 — Report
112
112
 
113
113
  Return:
114
114
 
115
- - Linear Issue URL with new label state
115
+ - Linear Issue URL with its new workflow state
116
116
  - PR URL (if updated)
117
117
  - List of uploaded evidence file URLs
118
118
 
@@ -122,7 +122,7 @@ Run it whenever the resolved env is **intermediate** (below the production termi
122
122
 
123
123
  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.
124
124
  2. **Uniform / single-environment no-op.** When the project's env-keyed `done` map is uniform — every environment resolves to the same `Done`, as in this repo (`production: main` only) — dev-merge == terminal, so native completion is correct. Do nothing. Only a **non-uniform** env→`done` map (distinct `On Dev`/`On Stg`/`Done` rungs) can desync.
125
- 3. Otherwise (non-uniform map, resolved env intermediate): re-read the Issue's `state`. If it sits in a `completed`-typed state while the derived role is a lower env rung, **re-open** the native state by moving it back to the correct env rung (via `lisa-linear-access operation: save-issue`) and post a short `[lisa-linear-sync]` reconciliation comment **naming the likely cause** — a live `merge → Done` git automation, or a magic word in the PR body. This applies the `leaf-only-lifecycle` "Terminal native closure" rule — closure fires **only** at the production terminal `done`. Cite the rule by slug; do not restate it.
125
+ 3. Otherwise (non-uniform map, resolved env intermediate): re-read the Issue's `state`. If it sits in a `completed`-typed state while the derived role is a lower env rung, **re-open** the native state by moving it back to the correct env rung (via `lisa-linear-access operation: save-issue lifecycle_role: done env: <resolved-rung>` — the backstop names the role and the rung like every other lifecycle write; it never re-derives a state ID itself) and post a short `[lisa-linear-sync]` reconciliation comment **naming the likely cause** — a live `merge → Done` git automation, or a magic word in the PR body. This applies the `leaf-only-lifecycle` "Terminal native closure" rule — closure fires **only** at the production terminal `done`. Cite the rule by slug; do not restate it.
126
126
  4. **Safe default.** If the true terminal cannot be resolved (ambiguous env or unresolvable `done` map), do not change the `state` — post a `[lisa-linear-sync]` reconciliation-suggestion comment and leave it untouched, mirroring the Phase 5 safe default.
127
127
 
128
128
  ## Phase 5 — Parent Status Rollup (`--rollup`)
@@ -148,7 +148,10 @@ When the caller passes `--rollup`, this skill **derives a parent/container's wor
148
148
 
149
149
  **Single-environment collapse (this repo).** The env rungs resolve via the env-keyed `done` logic in `config-resolution`. In this repo `deploy.branches` declares only `production: main`, so `done` collapses to the single `Done` state, the only env rung is production, and the lifecycle is `ready → claimed → done` with **no** dev/staging promotion hops and no configured `review` rung; the rollup never resolves a dev or staging `done`. Multi-environment projects keep the env-keyed map and roll a parent up to the intermediate env states.
150
150
 
151
- **Apply the derived state** via `lisa-linear-access operation: save-issue` (Project or Issue), setting the parent's `stateId` to the derived role. Post an idempotent rollup comment naming the derived state and the child tally. Because the terminal `done` state is itself typed `completed`, a parent rolled to terminal is natively closed by the same write — there is no second closure step. **Safe default:** if the derived terminal cannot be resolved (ambiguous required-set or unresolvable env `done`), do not guess — post the derived suggestion as a comment and leave the parent's state untouched.
151
+ **Apply the derived state**, naming the derived role — the access layer resolves the configured state for that role and dispatches it. This skill never computes a `stateId` of its own; a role the project never bound is refused there rather than approximated here.
152
+
153
+ - **Issue parent** → `lisa-linear-access operation: save-issue lifecycle_role: <derived role> [env: <key>]`. An Issue's lane IS a team workflow state, so this is the guarded write.
154
+ - **Project parent** → `lisa-linear-access operation: save-project`. A Linear Project has no team workflow state — it carries its own coarse project status — so there is no `stateId` and no `lifecycle_role` to declare. Map the derived role onto the Project's own status and say in the rollup comment which role produced it. Do not send `lifecycle_role` here; the workflow-state guard governs Issues and has nothing to resolve for a Project. Post an idempotent rollup comment naming the derived state and the child tally. Because the terminal `done` state is itself typed `completed`, a parent rolled to terminal is natively closed by the same write — there is no second closure step. **Safe default:** if the derived terminal cannot be resolved (ambiguous required-set or unresolvable env `done`), do not guess — post the derived suggestion as a comment and leave the parent's state untouched.
152
155
 
153
156
  ## Rules
154
157
 
@@ -31,7 +31,7 @@ Linear's data model maps Epic / Story / Sub-task to **different entity types**.
31
31
  | `Bug` | **Issue** | `lisa-linear-access operation: save-issue` | `projectId` if part of an Epic; else top-level |
32
32
  | `Spike` | **Issue** | `lisa-linear-access operation: save-issue` | `projectId` if part of an Epic; else top-level |
33
33
 
34
- The build lifecycle uses native **workflow states** (`Ready`, `In Progress`, `In Review`, `Blocked`, `On Dev`, `On Stg`, `Done`), resolved per role from `linear.workflow` — see "Why Linear uses states, not labels" in `config-resolution`. A new **leaf** work unit is created in the configured `ready` state only on explicit `build_ready: true`; omitted or `false` leaves it in the team's default backlog state, and a container is never put in `ready` at all (see the Build-ready control input below).
34
+ The build lifecycle uses native **workflow states** (`Ready`, `In Progress`, `Blocked`, `On Dev`, `On Stg`, `Done`, plus an optional review state a project may bind), resolved per role from `linear.workflow` — see "Why Linear uses states, not labels" in `config-resolution`. A new **leaf** work unit is created in the configured `ready` state only on explicit `build_ready: true`; omitted or `false` leaves it in the team's default backlog state, and a container is never put in `ready` at all (see the Build-ready control input below).
35
35
 
36
36
  ## Phase 1 — Resolve Intent
37
37
 
@@ -271,7 +271,7 @@ For Bug / Task / Sub-task, ensure the summary is prefixed with `[<repo-name>]`.
271
271
 
272
272
  - **Omitted** → **not build-ready**: the leaf is left in the team's default backlog state. Ready is an explicit claim, never a vendor default. **This is a breaking change** — Linear previously created a leaf directly in the `ready` state on omission, so a caller that relied on that must now pass `build_ready: true`.
273
273
  - **`build_ready: false`** → create the leaf **without** the `ready` state, leaving it in the team's default backlog state so it waits for a human to review and promote it into the queue.
274
- - **`build_ready: true`** → transition the **leaf** to the resolved `ready` state (`.linear.workflow.ready`) so `lisa-intake` / `lisa-linear-build-intake` auto-picks it up. Best-effort: if the state cannot be resolved or the transition is rejected, do not fail the write — leave the Issue in its default state and record the reason.
274
+ - **`build_ready: true`** → declare `lifecycle_role: ready` so the access layer resolves `.linear.workflow.ready` and places the leaf there, and `lisa-intake` / `lisa-linear-build-intake` auto-picks it up. Best-effort **for the lane, never for the guard**: an access-layer refusal (`ready` unbound, absent from the team, ambiguous) is fail-closed and is never worked around here — surface its message verbatim, do not fail the write, leave the Issue in its default state and record the reason. The Issue is still created; what is refused is the unproven lane placement, which is the safe half to lose.
275
275
 
276
276
  **A filing with neither is an incomplete handoff.** A leaf that is not build-ready must carry an explicit `human_gate: "<why a human must judge this first>"`; nothing in the ready lane means nothing ever claims it. When `human_gate` is supplied, stamp the hold on the Issue so it is auditable — a visible line plus the verbatim marker:
277
277
 
@@ -306,7 +306,7 @@ If the validator reports `PASS`, continue to Phase 6.
306
306
 
307
307
  ### CREATE — Story / Task / Bug / Spike / Improvement (Issue with projectId)
308
308
 
309
- 1. Resolve any required Issue labels (`type:<Kind>`, `repo:<name>`, `component:<name>`, `prd-intake-feedback` only if this is a sentinel issue) via `lisa-linear-access operation: list-issue-labels` (create via `lisa-linear-access operation: create-issue-label` if missing). Separately resolve the `ready` **state** id via `lisa-linear-access operation: list-workflow-states`, and set it as `stateId` only for a **leaf** work unit and only when `build_ready` is not `false` — omit it for a container, and for a `build_ready: false` leaf which then waits in the default backlog state for a human to promote it.
309
+ 1. Resolve any required Issue labels (`type:<Kind>`, `repo:<name>`, `component:<name>`, `prd-intake-feedback` only if this is a sentinel issue) via `lisa-linear-access operation: list-issue-labels` (create via `lisa-linear-access operation: create-issue-label` if missing). Separately, place a **leaf** work unit in the `ready` lane by passing `lifecycle_role: ready` on the create call below — and only on **explicit `build_ready: true`**, per the Build-ready control input below. Omit the role for a container, and for a leaf whose `build_ready` is `false` or omitted, which then waits in the team's default backlog state for a human to promote it. Ready is an explicit claim, never an omission's default. Never resolve a state ID here and pass it as `stateId`: the access layer resolves the configured `ready` state itself and refuses anything else.
310
310
  2. Call `lisa-linear-access operation: save-issue` with: `team` (teamId), `title` (summary), `description` (markdown), `projectId` (the Epic Project), `priority` (0–4), `estimate`, `labelIds`, `assignee` if known.
311
311
  3. Capture the returned identifier (e.g. `ENG-123`) — Phase 4 sub-tasks need it as `parentId`.
312
312
  4. Add relationships from Phase 4b via `save_issue` (relations field) or paired relation calls.
@@ -314,8 +314,8 @@ If the validator reports `PASS`, continue to Phase 6.
314
314
 
315
315
  ### CREATE — Sub-task (Issue with parentId)
316
316
 
317
- 1. Resolve labels as above.
318
- 2. Call `lisa-linear-access operation: save-issue` with: `team` (teamId), `title` (`[<repo>] <summary>` prefix is mandatory), `description` (markdown), `parentId` (the Story Issue ID), `projectId` (inherit from parent), `priority`, `estimate`, `labelIds`.
317
+ 1. Resolve labels as above. A Sub-task is a **leaf**, so the same ready-lane rule applies: pass `lifecycle_role: ready` on the create call below only on explicit `build_ready: true`, and omit it otherwise. Never resolve a state ID here.
318
+ 2. Call `lisa-linear-access operation: save-issue` with: `team` (teamId), `title` (`[<repo>] <summary>` prefix is mandatory), `description` (markdown), `parentId` (the Story Issue ID), `projectId` (inherit from parent), `priority`, `estimate`, `labelIds`, plus `lifecycle_role: ready` when `build_ready` is explicitly `true`.
319
319
  3. Capture identifier.
320
320
  4. Add relationships via Phase 4b.
321
321
 
@@ -729,8 +729,9 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
729
729
 
730
730
  3. **If the derived state differs from the parent's current state, apply it** via the vendor's
731
731
  lifecycle write: a JIRA transition; a GitHub label swap in the item's own lifecycle namespace;
732
- or a Linear `save-issue` update that writes the resolved workflow `stateId`, never a lifecycle
733
- label. For GitHub, resolve the lane before writing: PRD parents use the configured `prd.*`
732
+ or a Linear `save-issue lifecycle_role: <derived role> [env: <key>]` update, which resolves the
733
+ configured workflow state in the access layer and writes that — never a lifecycle label, and
734
+ never a state ID this skill picked. For GitHub, resolve the lane before writing: PRD parents use the configured `prd.*`
734
735
  roles, while build tickets use the configured build roles. Keep exactly one lifecycle label in
735
736
  that selected namespace and never substitute or overlap the other namespace. When applying any
736
737
  of these writes, remove conflicting stale lifecycle roles — **including a stale `ready`** the
@@ -243,12 +243,22 @@ Enumerate the team's states with `lisa-linear-access operation: list-workflow-st
243
243
  |------|---------------|--------|--------------------------|
244
244
  | `ready` | `Ready` | `unstarted` | **no — must be created or mapped** |
245
245
  | `claimed` | `In Progress` | `started` | yes |
246
- | `review` | `In Review` | `started` | yes |
246
+ | `review` | **none — optional, never seeded** | `started` | n/a |
247
247
  | `blocked` | `Blocked` | `unstarted` | **no — must be created or mapped** |
248
248
  | `done.dev` | `On Dev` | `started` | **no — must be created or mapped** |
249
249
  | `done.staging` | `On Stg` | `started` | **no — must be created or mapped** |
250
250
  | `done.production` | `Done` | `completed` | yes |
251
251
 
252
+ **`review` is OPTIONAL and this setup NEVER binds it unprompted.** It has no
253
+ default state, so there is nothing to resolve unless the user asks for a review
254
+ hold; leaving `linear.workflow.review` absent is a supported configuration
255
+ meaning the project runs no agent review step, and lifecycle skills skip that
256
+ transition entirely (`config-resolution` R1). Writing a binding a project did
257
+ not ask for is how agent-owned work reaches a human-only review lane — the
258
+ stock `In Review` state a team happens to ship is not evidence the project
259
+ wants one. Offer it only on an explicit request, and resolve it through the
260
+ same four-rung cascade as any other role.
261
+
252
262
  **The env rungs are deliberately `started`, not `completed`.** `On Dev` and `On Stg` mean "merged and deployed *that far*" — work that is emphatically not finished. Typing them `completed` would make Linear treat them as closed: they would leave the active board, stop counting in cycles, and re-create the exact premature-closure problem this model exists to fix. Only `done.production` is `completed`.
253
263
 
254
264
  **Position them between `In Review` and `Done`** so the board reads left-to-right in real lifecycle order. A team that orders its board differently can pass its own `position`.
@@ -71,7 +71,9 @@ Resolve the **effective** role → name mapping using the same defaults `/lisa:i
71
71
  - **Missing / empty tracker**: report `UNRESOLVABLE` with setup guidance (`/lisa:setup:jira`, `/lisa:setup:github`, or `/lisa:setup:linear`). Do not default to JIRA.
72
72
  - **JIRA build workflow** (`jira.workflow`): `ready`, `claimed`, optional `review`, `blocked`, and each `done.<env>` (`dev` / `staging` / `production`). Defaults: `Ready`, `In Progress`, `Code Review`, `Blocked`, `{dev: "On Dev", staging: "On Stg", production: "Done"}`.
73
73
  - **GitHub build/prd labels** (`github.labels.build`, `github.labels.prd`): each configured label string.
74
- - **Linear build workflow** (`linear.workflow`): `ready`, `claimed`, `review`, `blocked`, and each `done.<env>` — native workflow **states**, the Linear analogue of `jira.workflow`, not of `github.labels`. Defaults: `Ready`, `In Progress`, `In Review`, `Blocked`, `{dev: "On Dev", staging: "On Stg", production: "Done"}`.
74
+ - **Linear build workflow** (`linear.workflow`): `ready`, `claimed`, `blocked`, and each `done.<env>` are required; `review` is optional — native workflow **states**, the Linear analogue of `jira.workflow`, not of `github.labels`. Defaults: `Ready`, `In Progress`, `Blocked`, `{dev: "On Dev", staging: "On Stg", production: "Done"}`. **`review` has no default**: an absent `review` is a valid configuration meaning the project skips that transition, not drift to report.
75
+
76
+ Report a **missing required binding as invalid** rather than auditing the default in its place. The defaults above are what `/lisa:setup:linear` and `lisa sync` write INTO a config; they are not a resolution tier, and the write path has none — `lisa-linear-access` refuses a state write whose role resolves to nothing. Validating an unbound `ready`/`claimed`/`blocked`/`done` against `Ready`/`In Progress`/`Blocked`/`Done` would pass a config whose every lifecycle write later refuses, which is the shape of a green check on a broken project.
75
77
  - **Linear labels** (`linear.labels`): the `prd.*` map plus the one surviving build-lane key, the `human_needed` marker (`linear.labels.build.human_needed`). A config that predates the state model may still carry `ready` / `claimed` / `blocked` / `done` under `linear.labels.build`; those are **inert** — nothing reads them. Do not audit them against the live label set, and do not report them as drift. Report them once as a migration note pointing at `/lisa:setup:linear`, which removes them.
76
78
  - **Notion PRD values** (`notion.values`): each configured select-option value, validated against the `notion.statusProperty` property's options.
77
79
  - **Confluence PRD parents** (`confluence.parents`): each configured parent page id, validated by existence.
@@ -47,11 +47,11 @@ Use the `linear-verify` skill to check the item against organizational standards
47
47
 
48
48
  **Gating behavior — this is the one place auto-transitioning is allowed:**
49
49
 
50
- Resolve build-lifecycle STATES from `.lisa.config.json` `linear.workflow.*` (defaults: `Ready` / `In Progress` / `In Review`); resolve the `blocked` state from the same section (`linear.workflow.blocked`, default `Blocked`) and the `human_needed` marker label from the same section (`linear.labels.build.human_needed`, default `human-needed`).
50
+ Resolve build-lifecycle STATES from `.lisa.config.json` `linear.workflow.*` through `scripts/resolve-lifecycle-role.mjs` (`--vendor linear`). `ready`, `claimed`, `blocked` and `done` are REQUIRED and have no built-in default — an unset one is a setup defect to report. `review` is OPTIONAL and deliberately has **no** default: an unset `review` means the project runs no agent review step and the transition is skipped, never substituted (`config-resolution` R1). Resolve the `human_needed` marker label from `linear.labels.build.human_needed` (default `human-needed`) — the one surviving build-lane label key.
51
51
 
52
52
  If `linear-verify` returns `FAIL` on any of the above, do NOT continue to build. **Draft the missing spec content first, then block for confirmation** — never bounce a raw "go write all this" checklist back to the creator:
53
53
  1. **Best-effort autofill (before blocking).** Run `pre-flight-autofill` for every work item. Preserve a human bare configured key or `Confirmed: <env>`; automation writes `Inferred: <env> — evidence: <title|body|reproduction|hostname>`, `Assumption: <env> — remote default branch <branch>` for a unique reverse-map, or `Assumption: remote default branch <branch>` otherwise. Human confirmation replaces the annotation with the bare key or `Confirmed: <env>`. For a legacy bare value, use managed draft markers and current ticket content only; provider edit history is not required. A marker proves automation and requires re-annotation; otherwise unknown provenance plus conflicting evidence stops for confirmation. Resolve one exact `deploy.branches` key from the human-authored title, body, and reproduction steps or URL hostname; exclude the complete `Target Backend Environment` section and other machine-authored metadata/draft blocks so annotations cannot become evidence. A reported bug environment is an example, not a restriction. Evidence supersedes only `Assumption:`. Normalize `prod` ↔ `production` only when exactly one is configured; no other aliases exist. Conflicts stop; never infer from arbitrary branch text, URL paths/query strings, or substrings. Write through `linear-write-issue`, never overwrite human prose, then re-run `linear-verify`.
54
- 2. Update labels via `lisa-linear-access operation: save-issue`: remove the current build label, add the configured `blocked` label **and** the configured `human_needed` marker label. (Create either label via `create_issue_label` if needed.) Even after the agent drafted what it could, a pre-flight gate failure bounces the item back to its creator because it still needs a human to **confirm the drafted assumptions** or supply something no agent can invent — real missing credentials, access, or an irreducible product/scoping decision — so the marker tells a human scanning the board which blocked items are waiting on them. The marker is additive to `blocked`, not a replacement. (See the `config-resolution` rule's "Build markers" for when the marker applies and when it must NOT.)
54
+ 2. Move the Issue to the configured `blocked` **state** via `lisa-linear-access operation: save-issue lifecycle_role: blocked` (the access layer resolves the configured state itself; never hand it a state ID), and add the configured `human_needed` marker **label** in the same or a following metadata update. (Create either label via `create_issue_label` if needed.) Even after the agent drafted what it could, a pre-flight gate failure bounces the item back to its creator because it still needs a human to **confirm the drafted assumptions** or supply something no agent can invent — real missing credentials, access, or an irreducible product/scoping decision — so the marker tells a human scanning the board which blocked items are waiting on them. The marker is additive to `blocked`, not a replacement. (See the `config-resolution` rule's "Build markers" for when the marker applies and when it must NOT.)
55
55
  3. Reassign the item to the **Issue creator** (the human who filed it — Linear's `creator` field).
56
56
  4. Post the **confirmation comment** from the `pre-flight-autofill` rule via `lisa-linear-access operation: save-comment`, **not** a bare remediation checklist: disclose it is a Claude draft, give one line per drafted section naming the key assumption made, list any remaining human-only item as a specific question with a recommended default, and close with *"review the drafted sections, correct anything wrong, then flip back to Ready and it builds — or reply with corrections."* Prefix with `[{repo}]`.
57
57
  5. Stop. Do not run triage, do not delegate to a flow, do not start work.
@@ -116,23 +116,23 @@ Use the `linear-evidence` skill to:
116
116
 
117
117
  ### 8. Suggest Status Transition
118
118
 
119
- Based on the milestone, suggest (but don't auto-transition the native Linear `state`). Label role names are resolved from `.lisa.config.json` `linear.workflow.*`:
119
+ Based on the milestone, name the lifecycle role the item has reached. This agent does not perform these transitions itself — the owning flow does (`linear-evidence` for `review`, `linear-build-intake` for `claimed` and `done`) — so this section is a suggestion surface, not a write surface. Role names are resolved from `.lisa.config.json` `linear.workflow.*`; there is no default column, because a role this project did not bind has no name to print:
120
120
 
121
- | Milestone | Suggested role | Default label |
122
- |-----------|----------------|---------------|
123
- | Plan created | `claimed` | `In Progress` |
124
- | PR ready | `review` | `In Review` |
125
- | PR merged | `done` (env-aware; build-intake performs if dispatched via that flow) | env-keyed variant per `linear.workflow.done` |
121
+ | Milestone | Lifecycle role | Configured state |
122
+ |-----------|----------------|------------------|
123
+ | Plan created | `claimed` | `linear.workflow.claimed` (required) |
124
+ | PR ready | `review` | `linear.workflow.review` — **optional**; unset means skip the step |
125
+ | PR merged | `done` (env-aware; build-intake performs it if dispatched via that flow) | env-keyed variant per `linear.workflow.done` |
126
126
 
127
- Note: `done` may be a string or an env-keyed map (`{ dev, staging, production }`). When suggesting the PR-merged transition, the env is implied by the PR's base branch via `deploy.branches` — surface the resolved label name; do not auto-transition.
127
+ Note: `done` may be a string or an env-keyed map (`{ dev, staging, production }`). When naming the PR-merged transition, the env is implied by the PR's base branch via `deploy.branches` — surface the resolved state name; do not perform the transition here.
128
128
 
129
- The label transitions ARE the canonical signal. The native `state` field stays as the human / triage decision.
129
+ The native workflow **state** is the canonical build lane on Linear (`linear.labels.build.{ready,claimed,review,done}` is inert and nothing reads it). Suggesting a role never authorizes writing it: every state write goes through `lisa-linear-access` with a declared `lifecycle_role`, which resolves the target from config rather than trusting a supplied ID.
130
130
 
131
131
  ## Rules
132
132
 
133
- - Never auto-transition the native Linear `state`, with one explicit exception: when `linear-verify` returns `FAIL` for the pre-flight gate (Step 2), first run the `pre-flight-autofill` draft-then-block procedure (draft the authorable missing sections into the description as labeled assumptions), then update labels to the configured `blocked` label, add the configured `human_needed` marker label (`linear.labels.build.human_needed`, default `human-needed`), and reassign to the creator with a confirmation comment. Every other status change remains a label-driven suggestion.
133
+ - **This agent** never transitions the native Linear `state`, with one explicit exception: when `linear-verify` returns `FAIL` for the pre-flight gate (Step 2), run the `pre-flight-autofill` draft-then-block procedure (draft the authorable missing sections into the description as labeled assumptions), then move the Issue to the configured `blocked` state via `lisa-linear-access operation: save-issue lifecycle_role: blocked`, add the configured `human_needed` marker label (`linear.labels.build.human_needed`, default `human-needed`), and reassign to the creator with a confirmation comment. Every other transition is a suggestion this agent surfaces and the owning lifecycle flow performs — it is not a claim that the lane is label-driven. It is not: on Linear the lane is the state.
134
134
  - Always read the full item graph via `linear-read-issue` before determining intent — don't rely on type labels alone.
135
- - Never create or materially edit an item by calling MCP write tools directly — always delegate to `linear-write-issue` so relationships, Gherkin criteria, and metadata gates are enforced. Two explicit exceptions are permitted: (1) the Step 2 pre-flight failure path (when `linear-verify` returns `FAIL`) may call `lisa-linear-access operation: save-issue` and `lisa-linear-access operation: save-comment` directly to set `Blocked`, add the configured `human_needed` marker label, and reassign to the creator — this narrow exception is already granted by the rule above; (2) the Step 3 triage path may call `lisa-linear-access operation: save-comment` to post triage findings and `lisa-linear-access operation: save-issue` to add the `claude-triaged-{repo}` label — these are lightweight metadata updates that do not create or materially edit ticket content and therefore do not need to route through `linear-write-issue`.
135
+ - Never create or materially edit an item by calling MCP write tools directly — always delegate to `linear-write-issue` so relationships, Gherkin criteria, and metadata gates are enforced. Two explicit exceptions are permitted: (1) the Step 2 pre-flight failure path (when `linear-verify` returns `FAIL`) may call `lisa-linear-access operation: save-issue lifecycle_role: blocked` and `lisa-linear-access operation: save-comment` directly to move the Issue to the configured `blocked` state, add the configured `human_needed` marker label, and reassign to the creator — this narrow exception is already granted by the rule above; (2) the Step 3 triage path may call `lisa-linear-access operation: save-comment` to post triage findings and `lisa-linear-access operation: save-issue` to add the `claude-triaged-{repo}` label — these are lightweight metadata updates that do not create or materially edit ticket content and therefore do not need to route through `linear-write-issue`.
136
136
  - If sign-in credentials are in the item, extract and pass them to the flow. If the item touches an authenticated surface and credentials are missing, that is a Step 2 failure — block and reassign rather than guessing.
137
137
  - If the item has a Validation Journey section, pass it to the verifier agent. The Validation Journey's local-verification step must point at the target backend environment named in the description.
138
138
  - The environment handoff for every work item uses the same durable grammar: human bare configured key or `Confirmed: <env>`; automated `Inferred: <env> — evidence: <title|body|reproduction|hostname>`, `Assumption: <env> — remote default branch <branch>` for a unique reverse-map, or `Assumption: remote default branch <branch>` otherwise. Human confirmation replaces an automated annotation with the bare key or `Confirmed: <env>`. For legacy bare values, managed draft markers and current ticket content decide provenance; provider edit history is not required. A marker proves automation; otherwise unknown provenance plus conflicting evidence stops for confirmation. Human-confirmed wins, then validated `Inferred:`; otherwise one exact `deploy.branches` key from the human-authored title, body, and reproduction steps or URL hostname drives the implementation base branch. Exclude the complete `Target Backend Environment` section and other machine-authored metadata/draft blocks so annotations cannot become evidence. Evidence supersedes only `Assumption:`. Normalize `prod` ↔ `production` only when exactly one is configured; no other aliases exist. Conflicts stop. Never infer from arbitrary branch text, URL paths/query strings, or substrings. With no signal, use the remote default and record the applicable assumption without blocking on a non-unique reverse-map. Require any selected mapping and remote branch. A reported bug environment is an example of the all-work-type rule. Non-integration fixes still require the linked forward cherry-pick.
@@ -17,7 +17,7 @@ skills:
17
17
 
18
18
  You are a Linear build-intake agent. Your single job is to run one cycle against a Linear team — find Issues in the configured `ready` workflow STATE, dispatch each through the build flow, transition successful builds to the configured (env-aware) `done` state — then report what happened.
19
19
 
20
- Build-lifecycle role names (`ready`, `claimed`, `review`, `blocked`, `done`) are resolved from `.lisa.config.json` `linear.workflow.*` by the `linear-build-intake` skill, and name native **workflow states**, not labels. Defaults: `Ready`, `In Progress`, `In Review`, `Blocked`, env-keyed `{ dev: "On Dev", staging: "On Stg", production: "Done" }`. `ready` is a DEDICATED state, never the team default (`Todo`) — mapping it to the default makes every untouched backlog item claimable.
20
+ Build-lifecycle role names (`ready`, `claimed`, `review`, `blocked`, `done`) are resolved from `.lisa.config.json` `linear.workflow.*` by the `linear-build-intake` skill, and name native **workflow states**, not labels. Defaults: `Ready`, `In Progress`, `Blocked`, env-keyed `{ dev: "On Dev", staging: "On Stg", production: "Done" }`. **`review` has no default and is never seeded** — it is optional, and an unset `review` means the project runs no agent review step, so the transition is skipped rather than aimed at a review-shaped state nobody configured. `ready` is a DEDICATED state, never the team default (`Todo`) — mapping it to the default makes every untouched backlog item claimable.
21
21
 
22
22
  ## Confirmation policy
23
23