@codyswann/lisa 4.55.0 → 4.55.2

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 (132) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  3. package/dist/core/upstream-evidence-manifest.js +18 -69
  4. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  5. package/expo/create-only/.github/workflows/deploy.yml +22 -6
  6. package/package.json +9 -7
  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 +1 -2
  10. package/plugins/lisa/scripts/linear-state-write-target.mjs +2 -2
  11. package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +24 -11
  12. package/plugins/lisa/scripts/resolve-lifecycle-role.mjs +2 -7
  13. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +1 -2
  14. package/plugins/lisa-agy/plugin.json +1 -1
  15. package/plugins/lisa-agy/scripts/linear-state-write-target.mjs +2 -2
  16. package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +24 -11
  17. package/plugins/lisa-agy/scripts/resolve-lifecycle-role.mjs +2 -7
  18. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +1 -2
  19. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/scripts/linear-state-write-target.mjs +2 -2
  26. package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +24 -11
  27. package/plugins/lisa-copilot/scripts/resolve-lifecycle-role.mjs +2 -7
  28. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +1 -2
  29. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cursor/scripts/linear-state-write-target.mjs +2 -2
  31. package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +24 -11
  32. package/plugins/lisa-cursor/scripts/resolve-lifecycle-role.mjs +2 -7
  33. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +1 -2
  34. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo-agy/plugin.json +1 -1
  37. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  57. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails-agy/plugin.json +1 -1
  62. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  67. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  72. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/src/base/scripts/linear-state-write-target.mjs +2 -2
  75. package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +24 -11
  76. package/plugins/src/base/scripts/resolve-lifecycle-role.mjs +2 -7
  77. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +1 -2
  78. package/scripts/two-channel-couplings.json +1 -1
  79. package/typescript/create-only/.github/workflows/ci.yml +4 -0
  80. package/typescript/package-lisa/package.lisa.json +2 -2
  81. package/plugins/lisa/.codex-plugin/skills/lisa-qa-checklist/SKILL.md +0 -70
  82. package/plugins/lisa/.codex-plugin/skills/lisa-qa-checklist/agents/openai.yaml +0 -4
  83. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/SKILL.md +0 -86
  84. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/agents/openai.yaml +0 -4
  85. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +0 -119
  86. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/agents/openai.yaml +0 -4
  87. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/SKILL.md +0 -94
  88. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/agents/openai.yaml +0 -4
  89. package/plugins/lisa/commands/qa-checklist.md +0 -6
  90. package/plugins/lisa/commands/qa-clear.md +0 -6
  91. package/plugins/lisa/commands/qa-fail.md +0 -6
  92. package/plugins/lisa/commands/qa-queue.md +0 -6
  93. package/plugins/lisa/skills/lisa-qa-checklist/SKILL.md +0 -70
  94. package/plugins/lisa/skills/lisa-qa-checklist/agents/openai.yaml +0 -4
  95. package/plugins/lisa/skills/lisa-qa-clear/SKILL.md +0 -86
  96. package/plugins/lisa/skills/lisa-qa-clear/agents/openai.yaml +0 -4
  97. package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +0 -119
  98. package/plugins/lisa/skills/lisa-qa-fail/agents/openai.yaml +0 -4
  99. package/plugins/lisa/skills/lisa-qa-queue/SKILL.md +0 -94
  100. package/plugins/lisa/skills/lisa-qa-queue/agents/openai.yaml +0 -4
  101. package/plugins/lisa-agy/commands/lisa/qa-checklist.md +0 -6
  102. package/plugins/lisa-agy/commands/lisa/qa-clear.md +0 -6
  103. package/plugins/lisa-agy/commands/lisa/qa-fail.md +0 -6
  104. package/plugins/lisa-agy/commands/lisa/qa-queue.md +0 -6
  105. package/plugins/lisa-agy/skills/lisa-qa-checklist/SKILL.md +0 -70
  106. package/plugins/lisa-agy/skills/lisa-qa-clear/SKILL.md +0 -86
  107. package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +0 -119
  108. package/plugins/lisa-agy/skills/lisa-qa-queue/SKILL.md +0 -94
  109. package/plugins/lisa-copilot/commands/lisa/qa-checklist.md +0 -6
  110. package/plugins/lisa-copilot/commands/lisa/qa-clear.md +0 -6
  111. package/plugins/lisa-copilot/commands/lisa/qa-fail.md +0 -6
  112. package/plugins/lisa-copilot/commands/lisa/qa-queue.md +0 -6
  113. package/plugins/lisa-copilot/skills/lisa-qa-checklist/SKILL.md +0 -70
  114. package/plugins/lisa-copilot/skills/lisa-qa-clear/SKILL.md +0 -86
  115. package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +0 -119
  116. package/plugins/lisa-copilot/skills/lisa-qa-queue/SKILL.md +0 -94
  117. package/plugins/lisa-cursor/commands/lisa/qa-checklist.md +0 -6
  118. package/plugins/lisa-cursor/commands/lisa/qa-clear.md +0 -6
  119. package/plugins/lisa-cursor/commands/lisa/qa-fail.md +0 -6
  120. package/plugins/lisa-cursor/commands/lisa/qa-queue.md +0 -6
  121. package/plugins/lisa-cursor/skills/lisa-qa-checklist/SKILL.md +0 -70
  122. package/plugins/lisa-cursor/skills/lisa-qa-clear/SKILL.md +0 -86
  123. package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +0 -119
  124. package/plugins/lisa-cursor/skills/lisa-qa-queue/SKILL.md +0 -94
  125. package/plugins/src/base/commands/qa-checklist.md +0 -6
  126. package/plugins/src/base/commands/qa-clear.md +0 -6
  127. package/plugins/src/base/commands/qa-fail.md +0 -6
  128. package/plugins/src/base/commands/qa-queue.md +0 -6
  129. package/plugins/src/base/skills/lisa-qa-checklist/SKILL.md +0 -70
  130. package/plugins/src/base/skills/lisa-qa-clear/SKILL.md +0 -86
  131. package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +0 -119
  132. package/plugins/src/base/skills/lisa-qa-queue/SKILL.md +0 -94
@@ -188,17 +188,33 @@ jobs:
188
188
  - name: 📥 Checkout
189
189
  uses: actions/checkout@v6
190
190
  with:
191
- fetch-depth: 2
191
+ fetch-depth: 0
192
192
 
193
193
  - name: Check if app config changed
194
194
  id: check_changes
195
+ env:
196
+ PUSH_BEFORE: ${{ github.event.before }}
197
+ EVENT_NAME: ${{ github.event_name }}
195
198
  run: |
196
- if git diff --name-only HEAD^ HEAD | grep -qE "^(app\.config\.ts|app\.json|eas\.json)$"; then
197
- echo "app_config_changed=true" >> $GITHUB_OUTPUT
198
- echo "app config (app.config.ts / app.json / eas.json) has been modified"
199
+ set -euo pipefail
200
+ # A push may contain several commits. Inspect the complete push so
201
+ # this single build trigger also covers changes before the last commit.
202
+ base=HEAD^
203
+ if [ "$EVENT_NAME" = push ]; then
204
+ base="$PUSH_BEFORE"
205
+ fi
206
+ if [ -z "$base" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
207
+ # A new branch or unavailable before-revision needs a build.
208
+ echo "app_config_changed=true" >> "$GITHUB_OUTPUT"
209
+ elif git diff --quiet "$base" HEAD -- app.config.ts app.json eas.json; then
210
+ echo "app_config_changed=false" >> "$GITHUB_OUTPUT"
199
211
  else
200
- echo "app_config_changed=false" >> $GITHUB_OUTPUT
201
- echo "app config has not been modified"
212
+ result=$?
213
+ if [ "$result" -ne 1 ]; then
214
+ echo "::error::Could not compare native app configuration."
215
+ exit "$result"
216
+ fi
217
+ echo "app_config_changed=true" >> "$GITHUB_OUTPUT"
202
218
  fi
203
219
 
204
220
  # Trigger EAS Build if needed (only runs if app config changed)
package/package.json CHANGED
@@ -155,7 +155,7 @@
155
155
  "form-data": ">=4.0.6",
156
156
  "handlebars": ">=4.7.9",
157
157
  "lodash": ">=4.18.1",
158
- "multer": ">=2.2.0",
158
+ "multer": ">=2.3.0",
159
159
  "nanoid": "^3.3.18",
160
160
  "postcss": ">=8.5.18",
161
161
  "undici": "^6.28.0",
@@ -164,6 +164,7 @@
164
164
  "zod-validation-error": "^4.0.0"
165
165
  },
166
166
  "overrides": {
167
+ "smol-toml": "1.7.1",
167
168
  "@isaacs/brace-expansion": "^5.0.1",
168
169
  "@istanbuljs/load-nyc-config": {
169
170
  "js-yaml": "^3.15.1"
@@ -177,7 +178,7 @@
177
178
  "form-data": ">=4.0.6",
178
179
  "handlebars": ">=4.7.9",
179
180
  "lodash": ">=4.18.1",
180
- "multer": ">=2.2.0",
181
+ "multer": ">=2.3.0",
181
182
  "nanoid": "^3.3.18",
182
183
  "postcss": ">=8.5.18",
183
184
  "undici": "^6.28.0",
@@ -186,7 +187,7 @@
186
187
  "zod-validation-error": "^4.0.0"
187
188
  },
188
189
  "name": "@codyswann/lisa",
189
- "version": "4.55.0",
190
+ "version": "4.55.2",
190
191
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
191
192
  "main": "dist/index.js",
192
193
  "exports": {
@@ -303,7 +304,7 @@
303
304
  "picocolors": "^1.0.0",
304
305
  "prettier": "^3.3.3",
305
306
  "semver": "^7.8.1",
306
- "smol-toml": "^1.6.1",
307
+ "smol-toml": "1.7.1",
307
308
  "standard-version": "^9.5.0",
308
309
  "ts-jest": "^29.4.9",
309
310
  "ts-morph": "^27.0.2",
@@ -313,6 +314,7 @@
313
314
  "vitest": "^4.1.9"
314
315
  },
315
316
  "devDependencies": {
317
+ "@actions/expressions": "0.3.61",
316
318
  "@codyswann/lisa": "^4.33.1",
317
319
  "@playwright/test": "^1.61.1",
318
320
  "@stryker-mutator/api": "^9.0.0",
@@ -334,7 +336,7 @@
334
336
  "test": "tests"
335
337
  },
336
338
  "types": "./dist/index.d.ts",
337
- "lisaReleaseCommit": "e01ec6ad7d10e2f097c3cb8034b7903fbf66534d",
338
- "gitHead": "e01ec6ad7d10e2f097c3cb8034b7903fbf66534d",
339
- "lisaReleaseTag": "v4.55.0"
339
+ "lisaReleaseCommit": "dfa8eee4cbdc81e60bd25f83e68afea9deb8d45a",
340
+ "gitHead": "dfa8eee4cbdc81e60bd25f83e68afea9deb8d45a",
341
+ "lisaReleaseTag": "v4.55.2"
340
342
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.55.0",
3
+ "version": "4.55.2",
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.55.0",
3
+ "version": "4.55.2",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,8 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
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
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
193
192
  `done`. This layer then resolves that role against config and the team's own
194
193
  catalog, and sends the ID **it** resolved.
195
194
 
@@ -174,8 +174,8 @@ export const resolveStateWriteTarget = ({
174
174
  return refuse(
175
175
  REFUSALS.MISSING_ROLE,
176
176
  "no lifecycle role was declared. Every `stateId` write must name the role " +
177
- "it is applying (ready | claimed | blocked | review | done | qa.queue | " +
178
- "qa.certified) so the configured target can be resolved instead of trusted."
177
+ "it is applying (ready | claimed | blocked | review | done) so the " +
178
+ "configured target can be resolved instead of trusted."
179
179
  );
180
180
 
181
181
  // The environment key belongs to exactly one shape: an env-indexed `done`.
@@ -71,10 +71,13 @@ import {
71
71
  parseArgs,
72
72
  parseConfig,
73
73
  readPath,
74
- resolveRole,
75
74
  } from "./resolve-lifecycle-role.mjs";
76
75
 
77
- /** The QA-failure signal: applied by `lisa-qa-fail`, read by rework triage. */
76
+ /**
77
+ * The QA-failure signal. Historically applied by the retired `lisa-qa-fail`
78
+ * skill; now applied by a human or by whatever a project uses for QA, and read
79
+ * by rework triage.
80
+ */
78
81
  export const QA_FAILURE_SIGNAL = "qa-failure";
79
82
 
80
83
  /** The human-gate signal: applied at filing, read by every intake sweep. */
@@ -190,8 +193,13 @@ const verdictOf = body => {
190
193
  };
191
194
 
192
195
  /**
193
- * Role names whose occupancy proves the QA failure was resolved: the certified
194
- * role, and every environment rung of the terminal `done` map.
196
+ * Role names whose occupancy proves the QA failure was resolved: every
197
+ * environment rung of the terminal `done` map.
198
+ *
199
+ * Previously this also resolved a `qa.certified` role. That role was retired
200
+ * along with the QA acceptance skills that were its only writers, so reaching a
201
+ * `done` rung is now the whole proof. A project that wants a distinct
202
+ * post-QA state expresses it as a `done` rung.
195
203
  *
196
204
  * @param {object} options inputs
197
205
  * @param {string} options.vendor `jira` | `linear` | `github`
@@ -199,16 +207,21 @@ const verdictOf = body => {
199
207
  * @returns {string[]} configured role names, empty when none are bound
200
208
  */
201
209
  export const voidingRoles = ({ vendor, config }) => {
202
- const certified = resolveRole({
203
- role: "qa.certified",
204
- vendor,
205
- global: config,
206
- });
207
210
  const root = VENDOR_ROOTS[vendor];
208
211
  const doneMap = root ? readPath(config, `${root}.done`) : undefined;
212
+ // `done` has two sanctioned shapes: an env-indexed map, and a single scalar
213
+ // state for a project with one terminal rung. Every other reader handles
214
+ // both — linear-state-write-target.mjs:147, lifecycle-label-trust.mjs:213,
215
+ // queue-status-build-readers.mjs:315 — and this one must too. Missing the
216
+ // scalar here returns NO voiding roles at all, which holds a QA failure live
217
+ // forever on exactly the projects with the simplest config.
209
218
  const done =
210
- doneMap && typeof doneMap === "object" ? Object.values(doneMap) : [];
211
- return [certified.value, ...done].filter(
219
+ typeof doneMap === "string"
220
+ ? [doneMap]
221
+ : doneMap && typeof doneMap === "object"
222
+ ? Object.values(doneMap)
223
+ : [];
224
+ return done.filter(
212
225
  name => typeof name === "string" && name.trim().length > 0
213
226
  );
214
227
  };
@@ -80,12 +80,7 @@ export const REQUIRED_ROLES = Object.freeze([
80
80
  * Roles a project MAY bind. Omitting one means the lifecycle skips that
81
81
  * transition. These have NO built-in default — that is the whole point of R1.
82
82
  */
83
- export const OPTIONAL_ROLES = Object.freeze([
84
- "review",
85
- "qa.queue",
86
- "qa.certified",
87
- "human_needed",
88
- ]);
83
+ export const OPTIONAL_ROLES = Object.freeze(["review", "human_needed"]);
89
84
 
90
85
  /** Where each vendor keeps its build lifecycle role map. */
91
86
  export const VENDOR_ROOTS = Object.freeze({
@@ -155,7 +150,7 @@ export const parseConfig = file => {
155
150
  * built-in default tier: see R1 in the module docblock.
156
151
  *
157
152
  * @param {object} options resolution inputs
158
- * @param {string} options.role role name (`ready`, `review`, `qa.queue`, …)
153
+ * @param {string} options.role role name (`ready`, `review`, `human_needed`, …)
159
154
  * @param {string} options.vendor one of `jira` | `linear` | `github`
160
155
  * @param {"read" | "write"} [options.intent] what the caller will do with it
161
156
  * @param {string} [options.env] env key when resolving the env-keyed `done` map
@@ -188,8 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
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
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
193
192
  `done`. This layer then resolves that role against config and the team's own
194
193
  catalog, and sends the ID **it** resolved.
195
194
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.55.0",
3
+ "version": "4.55.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -174,8 +174,8 @@ export const resolveStateWriteTarget = ({
174
174
  return refuse(
175
175
  REFUSALS.MISSING_ROLE,
176
176
  "no lifecycle role was declared. Every `stateId` write must name the role " +
177
- "it is applying (ready | claimed | blocked | review | done | qa.queue | " +
178
- "qa.certified) so the configured target can be resolved instead of trusted."
177
+ "it is applying (ready | claimed | blocked | review | done) so the " +
178
+ "configured target can be resolved instead of trusted."
179
179
  );
180
180
 
181
181
  // The environment key belongs to exactly one shape: an env-indexed `done`.
@@ -71,10 +71,13 @@ import {
71
71
  parseArgs,
72
72
  parseConfig,
73
73
  readPath,
74
- resolveRole,
75
74
  } from "./resolve-lifecycle-role.mjs";
76
75
 
77
- /** The QA-failure signal: applied by `lisa-qa-fail`, read by rework triage. */
76
+ /**
77
+ * The QA-failure signal. Historically applied by the retired `lisa-qa-fail`
78
+ * skill; now applied by a human or by whatever a project uses for QA, and read
79
+ * by rework triage.
80
+ */
78
81
  export const QA_FAILURE_SIGNAL = "qa-failure";
79
82
 
80
83
  /** The human-gate signal: applied at filing, read by every intake sweep. */
@@ -190,8 +193,13 @@ const verdictOf = body => {
190
193
  };
191
194
 
192
195
  /**
193
- * Role names whose occupancy proves the QA failure was resolved: the certified
194
- * role, and every environment rung of the terminal `done` map.
196
+ * Role names whose occupancy proves the QA failure was resolved: every
197
+ * environment rung of the terminal `done` map.
198
+ *
199
+ * Previously this also resolved a `qa.certified` role. That role was retired
200
+ * along with the QA acceptance skills that were its only writers, so reaching a
201
+ * `done` rung is now the whole proof. A project that wants a distinct
202
+ * post-QA state expresses it as a `done` rung.
195
203
  *
196
204
  * @param {object} options inputs
197
205
  * @param {string} options.vendor `jira` | `linear` | `github`
@@ -199,16 +207,21 @@ const verdictOf = body => {
199
207
  * @returns {string[]} configured role names, empty when none are bound
200
208
  */
201
209
  export const voidingRoles = ({ vendor, config }) => {
202
- const certified = resolveRole({
203
- role: "qa.certified",
204
- vendor,
205
- global: config,
206
- });
207
210
  const root = VENDOR_ROOTS[vendor];
208
211
  const doneMap = root ? readPath(config, `${root}.done`) : undefined;
212
+ // `done` has two sanctioned shapes: an env-indexed map, and a single scalar
213
+ // state for a project with one terminal rung. Every other reader handles
214
+ // both — linear-state-write-target.mjs:147, lifecycle-label-trust.mjs:213,
215
+ // queue-status-build-readers.mjs:315 — and this one must too. Missing the
216
+ // scalar here returns NO voiding roles at all, which holds a QA failure live
217
+ // forever on exactly the projects with the simplest config.
209
218
  const done =
210
- doneMap && typeof doneMap === "object" ? Object.values(doneMap) : [];
211
- return [certified.value, ...done].filter(
219
+ typeof doneMap === "string"
220
+ ? [doneMap]
221
+ : doneMap && typeof doneMap === "object"
222
+ ? Object.values(doneMap)
223
+ : [];
224
+ return done.filter(
212
225
  name => typeof name === "string" && name.trim().length > 0
213
226
  );
214
227
  };
@@ -80,12 +80,7 @@ export const REQUIRED_ROLES = Object.freeze([
80
80
  * Roles a project MAY bind. Omitting one means the lifecycle skips that
81
81
  * transition. These have NO built-in default — that is the whole point of R1.
82
82
  */
83
- export const OPTIONAL_ROLES = Object.freeze([
84
- "review",
85
- "qa.queue",
86
- "qa.certified",
87
- "human_needed",
88
- ]);
83
+ export const OPTIONAL_ROLES = Object.freeze(["review", "human_needed"]);
89
84
 
90
85
  /** Where each vendor keeps its build lifecycle role map. */
91
86
  export const VENDOR_ROOTS = Object.freeze({
@@ -155,7 +150,7 @@ export const parseConfig = file => {
155
150
  * built-in default tier: see R1 in the module docblock.
156
151
  *
157
152
  * @param {object} options resolution inputs
158
- * @param {string} options.role role name (`ready`, `review`, `qa.queue`, …)
153
+ * @param {string} options.role role name (`ready`, `review`, `human_needed`, …)
159
154
  * @param {string} options.vendor one of `jira` | `linear` | `github`
160
155
  * @param {"read" | "write"} [options.intent] what the caller will do with it
161
156
  * @param {string} [options.env] env key when resolving the env-keyed `done` map
@@ -188,8 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
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
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
193
192
  `done`. This layer then resolves that role against config and the team's own
194
193
  catalog, and sends the ID **it** resolved.
195
194
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.55.0",
3
+ "version": "4.55.2",
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": "4.55.0",
3
+ "version": "4.55.2",
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": "4.55.0",
3
+ "version": "4.55.2",
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": "4.55.0",
3
+ "version": "4.55.2",
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": "4.55.0",
3
+ "version": "4.55.2",
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": "4.55.0",
3
+ "version": "4.55.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -174,8 +174,8 @@ export const resolveStateWriteTarget = ({
174
174
  return refuse(
175
175
  REFUSALS.MISSING_ROLE,
176
176
  "no lifecycle role was declared. Every `stateId` write must name the role " +
177
- "it is applying (ready | claimed | blocked | review | done | qa.queue | " +
178
- "qa.certified) so the configured target can be resolved instead of trusted."
177
+ "it is applying (ready | claimed | blocked | review | done) so the " +
178
+ "configured target can be resolved instead of trusted."
179
179
  );
180
180
 
181
181
  // The environment key belongs to exactly one shape: an env-indexed `done`.
@@ -71,10 +71,13 @@ import {
71
71
  parseArgs,
72
72
  parseConfig,
73
73
  readPath,
74
- resolveRole,
75
74
  } from "./resolve-lifecycle-role.mjs";
76
75
 
77
- /** The QA-failure signal: applied by `lisa-qa-fail`, read by rework triage. */
76
+ /**
77
+ * The QA-failure signal. Historically applied by the retired `lisa-qa-fail`
78
+ * skill; now applied by a human or by whatever a project uses for QA, and read
79
+ * by rework triage.
80
+ */
78
81
  export const QA_FAILURE_SIGNAL = "qa-failure";
79
82
 
80
83
  /** The human-gate signal: applied at filing, read by every intake sweep. */
@@ -190,8 +193,13 @@ const verdictOf = body => {
190
193
  };
191
194
 
192
195
  /**
193
- * Role names whose occupancy proves the QA failure was resolved: the certified
194
- * role, and every environment rung of the terminal `done` map.
196
+ * Role names whose occupancy proves the QA failure was resolved: every
197
+ * environment rung of the terminal `done` map.
198
+ *
199
+ * Previously this also resolved a `qa.certified` role. That role was retired
200
+ * along with the QA acceptance skills that were its only writers, so reaching a
201
+ * `done` rung is now the whole proof. A project that wants a distinct
202
+ * post-QA state expresses it as a `done` rung.
195
203
  *
196
204
  * @param {object} options inputs
197
205
  * @param {string} options.vendor `jira` | `linear` | `github`
@@ -199,16 +207,21 @@ const verdictOf = body => {
199
207
  * @returns {string[]} configured role names, empty when none are bound
200
208
  */
201
209
  export const voidingRoles = ({ vendor, config }) => {
202
- const certified = resolveRole({
203
- role: "qa.certified",
204
- vendor,
205
- global: config,
206
- });
207
210
  const root = VENDOR_ROOTS[vendor];
208
211
  const doneMap = root ? readPath(config, `${root}.done`) : undefined;
212
+ // `done` has two sanctioned shapes: an env-indexed map, and a single scalar
213
+ // state for a project with one terminal rung. Every other reader handles
214
+ // both — linear-state-write-target.mjs:147, lifecycle-label-trust.mjs:213,
215
+ // queue-status-build-readers.mjs:315 — and this one must too. Missing the
216
+ // scalar here returns NO voiding roles at all, which holds a QA failure live
217
+ // forever on exactly the projects with the simplest config.
209
218
  const done =
210
- doneMap && typeof doneMap === "object" ? Object.values(doneMap) : [];
211
- return [certified.value, ...done].filter(
219
+ typeof doneMap === "string"
220
+ ? [doneMap]
221
+ : doneMap && typeof doneMap === "object"
222
+ ? Object.values(doneMap)
223
+ : [];
224
+ return done.filter(
212
225
  name => typeof name === "string" && name.trim().length > 0
213
226
  );
214
227
  };
@@ -80,12 +80,7 @@ export const REQUIRED_ROLES = Object.freeze([
80
80
  * Roles a project MAY bind. Omitting one means the lifecycle skips that
81
81
  * transition. These have NO built-in default — that is the whole point of R1.
82
82
  */
83
- export const OPTIONAL_ROLES = Object.freeze([
84
- "review",
85
- "qa.queue",
86
- "qa.certified",
87
- "human_needed",
88
- ]);
83
+ export const OPTIONAL_ROLES = Object.freeze(["review", "human_needed"]);
89
84
 
90
85
  /** Where each vendor keeps its build lifecycle role map. */
91
86
  export const VENDOR_ROOTS = Object.freeze({
@@ -155,7 +150,7 @@ export const parseConfig = file => {
155
150
  * built-in default tier: see R1 in the module docblock.
156
151
  *
157
152
  * @param {object} options resolution inputs
158
- * @param {string} options.role role name (`ready`, `review`, `qa.queue`, …)
153
+ * @param {string} options.role role name (`ready`, `review`, `human_needed`, …)
159
154
  * @param {string} options.vendor one of `jira` | `linear` | `github`
160
155
  * @param {"read" | "write"} [options.intent] what the caller will do with it
161
156
  * @param {string} [options.env] env key when resolving the env-keyed `done` map
@@ -188,8 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
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
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
193
192
  `done`. This layer then resolves that role against config and the team's own
194
193
  catalog, and sends the ID **it** resolved.
195
194
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.55.0",
3
+ "version": "4.55.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -174,8 +174,8 @@ export const resolveStateWriteTarget = ({
174
174
  return refuse(
175
175
  REFUSALS.MISSING_ROLE,
176
176
  "no lifecycle role was declared. Every `stateId` write must name the role " +
177
- "it is applying (ready | claimed | blocked | review | done | qa.queue | " +
178
- "qa.certified) so the configured target can be resolved instead of trusted."
177
+ "it is applying (ready | claimed | blocked | review | done) so the " +
178
+ "configured target can be resolved instead of trusted."
179
179
  );
180
180
 
181
181
  // The environment key belongs to exactly one shape: an env-indexed `done`.
@@ -71,10 +71,13 @@ import {
71
71
  parseArgs,
72
72
  parseConfig,
73
73
  readPath,
74
- resolveRole,
75
74
  } from "./resolve-lifecycle-role.mjs";
76
75
 
77
- /** The QA-failure signal: applied by `lisa-qa-fail`, read by rework triage. */
76
+ /**
77
+ * The QA-failure signal. Historically applied by the retired `lisa-qa-fail`
78
+ * skill; now applied by a human or by whatever a project uses for QA, and read
79
+ * by rework triage.
80
+ */
78
81
  export const QA_FAILURE_SIGNAL = "qa-failure";
79
82
 
80
83
  /** The human-gate signal: applied at filing, read by every intake sweep. */
@@ -190,8 +193,13 @@ const verdictOf = body => {
190
193
  };
191
194
 
192
195
  /**
193
- * Role names whose occupancy proves the QA failure was resolved: the certified
194
- * role, and every environment rung of the terminal `done` map.
196
+ * Role names whose occupancy proves the QA failure was resolved: every
197
+ * environment rung of the terminal `done` map.
198
+ *
199
+ * Previously this also resolved a `qa.certified` role. That role was retired
200
+ * along with the QA acceptance skills that were its only writers, so reaching a
201
+ * `done` rung is now the whole proof. A project that wants a distinct
202
+ * post-QA state expresses it as a `done` rung.
195
203
  *
196
204
  * @param {object} options inputs
197
205
  * @param {string} options.vendor `jira` | `linear` | `github`
@@ -199,16 +207,21 @@ const verdictOf = body => {
199
207
  * @returns {string[]} configured role names, empty when none are bound
200
208
  */
201
209
  export const voidingRoles = ({ vendor, config }) => {
202
- const certified = resolveRole({
203
- role: "qa.certified",
204
- vendor,
205
- global: config,
206
- });
207
210
  const root = VENDOR_ROOTS[vendor];
208
211
  const doneMap = root ? readPath(config, `${root}.done`) : undefined;
212
+ // `done` has two sanctioned shapes: an env-indexed map, and a single scalar
213
+ // state for a project with one terminal rung. Every other reader handles
214
+ // both — linear-state-write-target.mjs:147, lifecycle-label-trust.mjs:213,
215
+ // queue-status-build-readers.mjs:315 — and this one must too. Missing the
216
+ // scalar here returns NO voiding roles at all, which holds a QA failure live
217
+ // forever on exactly the projects with the simplest config.
209
218
  const done =
210
- doneMap && typeof doneMap === "object" ? Object.values(doneMap) : [];
211
- return [certified.value, ...done].filter(
219
+ typeof doneMap === "string"
220
+ ? [doneMap]
221
+ : doneMap && typeof doneMap === "object"
222
+ ? Object.values(doneMap)
223
+ : [];
224
+ return done.filter(
212
225
  name => typeof name === "string" && name.trim().length > 0
213
226
  );
214
227
  };
@@ -80,12 +80,7 @@ export const REQUIRED_ROLES = Object.freeze([
80
80
  * Roles a project MAY bind. Omitting one means the lifecycle skips that
81
81
  * transition. These have NO built-in default — that is the whole point of R1.
82
82
  */
83
- export const OPTIONAL_ROLES = Object.freeze([
84
- "review",
85
- "qa.queue",
86
- "qa.certified",
87
- "human_needed",
88
- ]);
83
+ export const OPTIONAL_ROLES = Object.freeze(["review", "human_needed"]);
89
84
 
90
85
  /** Where each vendor keeps its build lifecycle role map. */
91
86
  export const VENDOR_ROOTS = Object.freeze({
@@ -155,7 +150,7 @@ export const parseConfig = file => {
155
150
  * built-in default tier: see R1 in the module docblock.
156
151
  *
157
152
  * @param {object} options resolution inputs
158
- * @param {string} options.role role name (`ready`, `review`, `qa.queue`, …)
153
+ * @param {string} options.role role name (`ready`, `review`, `human_needed`, …)
159
154
  * @param {string} options.vendor one of `jira` | `linear` | `github`
160
155
  * @param {"read" | "write"} [options.intent] what the caller will do with it
161
156
  * @param {string} [options.env] env key when resolving the env-keyed `done` map
@@ -188,8 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
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
191
+ role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
193
192
  `done`. This layer then resolves that role against config and the team's own
194
193
  catalog, and sends the ID **it** resolved.
195
194
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.55.0",
3
+ "version": "4.55.2",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"