@codyswann/lisa 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +22 -0
  2. package/all/copy-contents/gitignore +7 -0
  3. package/all/copy-overwrite/scripts/lisa-command-envelope.mjs +44 -4
  4. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +31 -0
  5. package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +125 -47
  6. package/all/copy-overwrite/scripts/lisa-work-item.mjs +24 -2
  7. package/cdk/merge/.oxlintrc.json +1 -1
  8. package/dist/cli/apply.d.ts.map +1 -1
  9. package/dist/cli/apply.js +48 -13
  10. package/dist/cli/apply.js.map +1 -1
  11. package/dist/cli/doctor-apply-freshness.d.ts +29 -0
  12. package/dist/cli/doctor-apply-freshness.d.ts.map +1 -0
  13. package/dist/cli/doctor-apply-freshness.js +194 -0
  14. package/dist/cli/doctor-apply-freshness.js.map +1 -0
  15. package/dist/cli/doctor-learnings-merge-driver.d.ts +23 -0
  16. package/dist/cli/doctor-learnings-merge-driver.d.ts.map +1 -0
  17. package/dist/cli/doctor-learnings-merge-driver.js +73 -0
  18. package/dist/cli/doctor-learnings-merge-driver.js.map +1 -0
  19. package/dist/cli/doctor-readiness-workflows.js +2 -2
  20. package/dist/cli/doctor-readiness-workflows.js.map +1 -1
  21. package/dist/cli/doctor.d.ts.map +1 -1
  22. package/dist/cli/doctor.js +13 -0
  23. package/dist/cli/doctor.js.map +1 -1
  24. package/dist/cli/ui-ci-quality-jobs-parse.js +2 -2
  25. package/dist/cli/ui-ci-quality-jobs-parse.js.map +1 -1
  26. package/dist/cli/ui-deploy-pipeline-model.d.ts +4 -0
  27. package/dist/cli/ui-deploy-pipeline-model.d.ts.map +1 -1
  28. package/dist/cli/ui-deploy-pipeline-model.js +2 -2
  29. package/dist/cli/ui-deploy-pipeline-model.js.map +1 -1
  30. package/dist/codex/agent-transformer.js +2 -2
  31. package/dist/codex/agent-transformer.js.map +1 -1
  32. package/dist/codex/command-skill-transformer.js +3 -3
  33. package/dist/codex/command-skill-transformer.js.map +1 -1
  34. package/dist/codex/scripts/block-no-verify.sh +53 -4
  35. package/dist/core/apply-receipt.d.ts +68 -0
  36. package/dist/core/apply-receipt.d.ts.map +1 -0
  37. package/dist/core/apply-receipt.js +107 -0
  38. package/dist/core/apply-receipt.js.map +1 -0
  39. package/dist/core/learnings-merge-driver-install.d.ts +34 -0
  40. package/dist/core/learnings-merge-driver-install.d.ts.map +1 -1
  41. package/dist/core/learnings-merge-driver-install.js +36 -0
  42. package/dist/core/learnings-merge-driver-install.js.map +1 -1
  43. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  44. package/dist/core/upstream-evidence-manifest.js +62 -39
  45. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  46. package/dist/migrations/ensure-lisa-postinstall.d.ts +33 -0
  47. package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
  48. package/dist/migrations/ensure-lisa-postinstall.js +47 -5
  49. package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
  50. package/dist/migrations/ensure-oxlint-base-configs.d.ts +42 -0
  51. package/dist/migrations/ensure-oxlint-base-configs.d.ts.map +1 -0
  52. package/dist/migrations/ensure-oxlint-base-configs.js +212 -0
  53. package/dist/migrations/ensure-oxlint-base-configs.js.map +1 -0
  54. package/dist/migrations/index.d.ts +1 -0
  55. package/dist/migrations/index.d.ts.map +1 -1
  56. package/dist/migrations/index.js +3 -0
  57. package/dist/migrations/index.js.map +1 -1
  58. package/dist/opencode/command-transformer.js +3 -3
  59. package/dist/opencode/command-transformer.js.map +1 -1
  60. package/dist/utils/yaml.d.ts +54 -0
  61. package/dist/utils/yaml.d.ts.map +1 -0
  62. package/dist/utils/yaml.js +124 -0
  63. package/dist/utils/yaml.js.map +1 -0
  64. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +104 -19
  65. package/expo/copy-overwrite/scripts/bdd/contract.mjs +20 -1
  66. package/expo/copy-overwrite/scripts/bdd/discover.mjs +53 -10
  67. package/expo/copy-overwrite/scripts/bdd/parse.mjs +25 -3
  68. package/expo/copy-overwrite/scripts/bdd/render.mjs +2 -1
  69. package/expo/copy-overwrite/scripts/bdd/report.mjs +26 -12
  70. package/expo/copy-overwrite/scripts/bdd/validate.mjs +2 -2
  71. package/expo/copy-overwrite/scripts/bdd/waivers.mjs +19 -4
  72. package/expo/copy-overwrite/scripts/bdd-matrix.mjs +2 -2
  73. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +58 -16
  74. package/expo/create-only/.github/required-checks.json +10 -4
  75. package/expo/merge/.oxlintrc.json +1 -1
  76. package/harper-fabric/merge/.oxlintrc.json +1 -1
  77. package/nestjs/create-only/.github/required-checks.json +57 -0
  78. package/nestjs/merge/.oxlintrc.json +1 -1
  79. package/package.json +1 -1
  80. package/phaser/merge/.oxlintrc.json +1 -1
  81. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +7 -0
  84. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +1 -1
  85. package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +37 -2
  86. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +7 -0
  87. package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +40 -3
  88. package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +44 -3
  89. package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  90. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
  91. package/plugins/lisa/hooks/block-no-verify.agy.sh +53 -4
  92. package/plugins/lisa/hooks/block-no-verify.sh +31 -0
  93. package/plugins/lisa/rules/eager/credential-substrate-precedence.md +3 -1
  94. package/plugins/lisa/rules/eager/tracked-work.md +1 -1
  95. package/plugins/lisa/rules/reference/credential-substrate-precedence.md +20 -0
  96. package/plugins/lisa/rules/reference/tracked-work.md +4 -2
  97. package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +7 -0
  98. package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -1
  99. package/plugins/lisa/skills/lisa-jam-access/SKILL.md +37 -2
  100. package/plugins/lisa/skills/lisa-notion-access/SKILL.md +7 -0
  101. package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +40 -3
  102. package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +44 -3
  103. package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  104. package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
  105. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +53 -4
  106. package/plugins/lisa-agy/plugin.json +1 -1
  107. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +7 -0
  108. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -1
  109. package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +37 -2
  110. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +7 -0
  111. package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +40 -3
  112. package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +44 -3
  113. package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  114. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
  115. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  117. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  118. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-copilot/hooks/block-no-verify.sh +31 -0
  122. package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +3 -1
  123. package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
  124. package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +20 -0
  125. package/plugins/lisa-copilot/rules/reference/tracked-work.md +4 -2
  126. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +7 -0
  127. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -1
  128. package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +37 -2
  129. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +7 -0
  130. package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +40 -3
  131. package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +44 -3
  132. package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  133. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
  134. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  135. package/plugins/lisa-cursor/hooks/block-no-verify.sh +31 -0
  136. package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +20 -0
  137. package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +3 -1
  138. package/plugins/lisa-cursor/rules/tracked-work-reference.mdc +4 -2
  139. package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
  140. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +7 -0
  141. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -1
  142. package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +37 -2
  143. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +7 -0
  144. package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +40 -3
  145. package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +44 -3
  146. package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  147. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
  148. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  149. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  150. package/plugins/lisa-expo-agy/plugin.json +1 -1
  151. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  152. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  154. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  155. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  156. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  157. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  158. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  159. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  160. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  161. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  163. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  164. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  165. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  166. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  167. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  168. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  169. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  170. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  171. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  172. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  173. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  174. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  175. package/plugins/lisa-rails-agy/plugin.json +1 -1
  176. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  177. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  178. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  179. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  180. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  181. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  182. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  183. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  184. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  185. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  186. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  187. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  188. package/plugins/src/base/hooks/block-no-verify.agy.sh +53 -4
  189. package/plugins/src/base/hooks/block-no-verify.sh +31 -0
  190. package/plugins/src/base/rules/eager/credential-substrate-precedence.md +3 -1
  191. package/plugins/src/base/rules/eager/tracked-work.md +1 -1
  192. package/plugins/src/base/rules/reference/credential-substrate-precedence.md +20 -0
  193. package/plugins/src/base/rules/reference/tracked-work.md +4 -2
  194. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +7 -0
  195. package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -1
  196. package/plugins/src/base/skills/lisa-jam-access/SKILL.md +37 -2
  197. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +7 -0
  198. package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +40 -3
  199. package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +44 -3
  200. package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +56 -0
  201. package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
  202. package/scripts/install-claude-plugins.sh +5 -1
  203. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +298 -26
  204. package/typescript/create-only/.github/required-checks.json +11 -6
  205. package/typescript/create-only/.github/workflows/required-checks-drift.yml +84 -0
  206. package/typescript/merge/.oxlintrc.json +1 -1
@@ -39,28 +39,69 @@ Sentry documents API auth tokens for REST API calls, and the same token works
39
39
  interactively and headlessly — which is why it leads. The REST tier uses:
40
40
 
41
41
  ```bash
42
+ # Resolve the token through the chokepoint before giving up on the environment.
43
+ # `$SENTRY_AUTH_TOKEN` is the documented fallback, not the only rung: without
44
+ # this, a project that keeps its credentials in Bitwarden, Doppler, or AWS has
45
+ # no tier 1 path at all and silently resolves through the interactive MCP —
46
+ # the exact divergence `credential-substrate-precedence` exists to remove.
47
+ # Mirrors `linear-access`, `atlassian-access`, and `notion-access`.
48
+ read_sentry_token() {
49
+ [ -n "${SENTRY_AUTH_TOKEN:-}" ] && { echo "$SENTRY_AUTH_TOKEN"; return; }
50
+ local resolver
51
+ for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
52
+ .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
53
+ if [ -f "$resolver" ]; then
54
+ local via_lisa
55
+ via_lisa=$(node "$resolver" get SENTRY_AUTH_TOKEN 2>/dev/null) \
56
+ && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
57
+ break
58
+ fi
59
+ done
60
+ return 1
61
+ }
62
+
42
63
  sentry_api() {
43
64
  local path="$1"
44
- [ -n "$SENTRY_AUTH_TOKEN" ] || {
45
- echo "Error: SENTRY_AUTH_TOKEN is not set." >&2
65
+ local token
66
+ token=$(read_sentry_token) || {
67
+ echo "Error: no Sentry auth token. Set SENTRY_AUTH_TOKEN, or store it as" >&2
68
+ echo "SENTRY_AUTH_TOKEN in this project's secrets provider." >&2
46
69
  return 1
47
70
  }
48
71
  curl -sS "https://sentry.io/api/0${path}" \
49
- -H "Authorization: Bearer $SENTRY_AUTH_TOKEN"
72
+ -H "Authorization: Bearer $token"
50
73
  }
51
74
  ```
52
75
 
76
+ Tier 1a authenticates `sentry-cli` from the **same resolved token**
77
+ (`SENTRY_AUTH_TOKEN=$(read_sentry_token) sentry-cli …`) — never from a second
78
+ credential store, and never from an interactive `sentry-cli login` keychain.
79
+
53
80
  If neither tier works, fail with:
54
81
 
55
82
  ```text
56
83
  Error: no Sentry access substrate available. Authenticate Sentry MCP/CLI or set SENTRY_AUTH_TOKEN.
57
84
  ```
58
85
 
86
+ ## Mutation boundary
87
+
88
+ Every operation in the Invocation Contract is **read-only** — issue, event, and
89
+ release retrieval. So the `credential-substrate-precedence` guarded fallback for
90
+ mutating operations (write, read back, assert the tenant from the response, roll
91
+ back on mismatch) is not engaged here, and a failed tier is simply skipped. Any
92
+ future operation that mutates Sentry state — resolving an issue, editing a
93
+ release — is a write and MUST reconcile by read-back under that protocol before
94
+ any retry; do not add one to the contract without it.
95
+
59
96
  ## Invariants
60
97
 
61
98
  - Tier order is `credential-substrate-precedence`: `SENTRY_AUTH_TOKEN` first, the
62
99
  Sentry MCP as a preserved first-class fallback. Identity-match against the
63
100
  configured org/project is mandatory on every tier.
101
+ - The token is resolved through `lisa-secrets-access`, with the bare
102
+ `SENTRY_AUTH_TOKEN` environment variable as the documented fallback. Never read
103
+ a second credential store (OS keychain, `~/.sentryclirc` token) directly — the
104
+ one-store rule lives at the chokepoint.
64
105
  - Org/project come from `.sentryclirc`, `.lisa.config.json`, or explicit
65
106
  operation args; never infer by searching all accessible orgs.
66
107
  - Consumer skills do not embed Sentry REST paths.
@@ -34,6 +34,49 @@ SonarQube Cloud org. Do **not** substitute the raw MCP-image names
34
34
  running the Docker image directly, and the `sonar` CLI ignores them (auth exits
35
35
  non-zero). The CI scan gate's `SONAR_TOKEN` is a third, separate name.
36
36
 
37
+ ### Where those variables come from
38
+
39
+ The `sonar` CLI reads them **from the environment only** — it has no provider
40
+ integration of its own — so on a surface that deliberately materializes nothing
41
+ to disk the credential can be provisioned and unreachable at the same time.
42
+ Resolve it through `lisa-secrets-access` and export it into the launching
43
+ process, exactly as the shared `sonar-secrets.sh` hook already does. The bare
44
+ `$SONARQUBE_CLI_TOKEN` in the environment is the documented **fallback** rung,
45
+ not the only one:
46
+
47
+ ```bash
48
+ # Preferred: the one sanctioned reader. Without this rung a project keeping its
49
+ # credentials in Bitwarden, Doppler, or AWS cannot authenticate the MCP at all,
50
+ # and Sonar access degrades to "run `sonar auth login` in a browser" — dead in
51
+ # cron, CI, and cloud sessions. See `credential-substrate-precedence`.
52
+ read_sonar_secret() {
53
+ local name="$1"
54
+ [ -n "${!name:-}" ] && { echo "${!name}"; return; }
55
+ local resolver
56
+ for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
57
+ .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
58
+ if [ -f "$resolver" ]; then
59
+ local via_lisa
60
+ via_lisa=$(node "$resolver" get "$name" 2>/dev/null) \
61
+ && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
62
+ break
63
+ fi
64
+ done
65
+ return 1
66
+ }
67
+
68
+ # Exported into this process only. Writing it anywhere durable would create a
69
+ # second live copy of a credential whose single store is the provider.
70
+ SONARQUBE_CLI_TOKEN=$(read_sonar_secret SONARQUBE_CLI_TOKEN) && export SONARQUBE_CLI_TOKEN
71
+ SONARQUBE_CLI_ORG=$(read_sonar_secret SONARQUBE_CLI_ORG) && export SONARQUBE_CLI_ORG
72
+ ```
73
+
74
+ `sonar-secrets.sh` runs the same resolution with a longer search path (it also
75
+ probes `.codex/skills/…` and `node_modules/@codyswann/lisa/plugins/lisa/…`,
76
+ because a hook fires in checkouts that never installed an agent plugin) and with
77
+ a timeout, because it sits in front of every prompt. Use its search order
78
+ verbatim when this skill runs inside a hook.
79
+
37
80
  Wiring is performed once by `/lisa:setup:sonar` (which drives `sonar integrate
38
81
  <agent>`); this access layer assumes the MCP is already wired. This is distinct
39
82
  from the CI `SONAR_TOKEN` secret that authenticates the SonarCloud scan job in
@@ -79,9 +122,22 @@ Pass the project key through the tool's `projectKey` argument (or rely on a
79
122
  server-configured `SONARQUBE_PROJECT_KEY`); pass `branch` / `pullRequest` where the
80
123
  tool accepts them.
81
124
 
125
+ ## Mutation boundary
126
+
127
+ Every operation in the map above is **read-only** — quality, coverage, and
128
+ security data. So the `credential-substrate-precedence` guarded fallback for
129
+ mutating operations (write, read back, assert the tenant from the response, roll
130
+ back on mismatch) is not engaged here; with one substrate there is nothing to
131
+ fall back to in any case. A future operation that mutates Sonar state — marking
132
+ a hotspot safe, changing an issue's status — is a write and MUST reconcile by
133
+ read-back before any retry.
134
+
82
135
  ## Invariants
83
136
 
84
137
  - The official SonarQube MCP is the only substrate; there is no REST fallback.
138
+ - `SONARQUBE_CLI_*` values are resolved through `lisa-secrets-access` and
139
+ exported in-process, with the bare environment variables as the documented
140
+ fallback. Never write them to a dotfile or a `.env` on a local surface.
85
141
  - Auth is env-var only (`SONARQUBE_CLI_TOKEN` [+ `SONARQUBE_CLI_ORG` | `SONARQUBE_CLI_SERVER`]);
86
142
  never the interactive `sonar auth login` keychain flow inside a factory.
87
143
  - Sonar host access requires the host (`sonarcloud.io`, `sonarqube.us`, or the
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
47
47
  2. Persist only the canonical reference in worktree-local machine state:
48
48
 
49
49
  ```bash
50
- node scripts/lisa-work-item.mjs bind <canonical-ref>
50
+ node scripts/lisa-work-item.mjs link <canonical-ref>
51
51
  ```
52
52
 
53
53
  3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -102,16 +102,65 @@ def disables_verification(token):
102
102
  and NO_VERIFY.startswith(token)
103
103
  )
104
104
 
105
+ # The only relocations that keep a repo's own hooks in play. Anything else —
106
+ # including "" and /dev/null, which are simply the two most obvious members of
107
+ # the blocked set rather than special cases — is refused.
108
+ PERMITTED_HOOKS_PATHS = {".husky", ".githooks"}
109
+
110
+
111
+ def is_permitted_hooks_path(value):
112
+ """Whether a core.hooksPath value relocates hooks rather than disabling them.
113
+
114
+ Args:
115
+ value: The raw core.hooksPath value as it appeared on the command line.
116
+
117
+ Returns:
118
+ True if the path is an established in-repo hooks directory.
119
+ """
120
+ cleaned = value.strip().strip("'\"")
121
+ if cleaned.startswith("./"):
122
+ cleaned = cleaned[2:]
123
+ return cleaned.rstrip("/") in PERMITTED_HOOKS_PATHS
124
+
125
+
105
126
  for i, token in enumerate(normalized_tokens):
106
127
  if disables_verification(token):
107
128
  sys.exit(1)
108
129
  if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
109
130
  sys.exit(1)
110
- if token.startswith("core.hooksPath="):
111
- value = token.split("=", 1)[1]
112
- if value in ("", "/dev/null"):
131
+ # Allowlist the destinations, do not denylist the disabling ones: hooks are
132
+ # disabled just as completely by any directory that happens to contain none
133
+ # (`-c core.hooksPath=/tmp/empty`), so the set that DISABLES hooks is
134
+ # unbounded while the set that legitimately relocates them is tiny. Matched
135
+ # case-insensitively because git config names are.
136
+ lowered = token.lower()
137
+ if lowered.startswith("core.hookspath="):
138
+ if not is_permitted_hooks_path(token.split("=", 1)[1]):
139
+ sys.exit(1)
140
+ if lowered == "core.hookspath" and i + 1 < len(normalized_tokens):
141
+ if not is_permitted_hooks_path(normalized_tokens[i + 1]):
142
+ sys.exit(1)
143
+ # `--config-env=core.hooksPath=SOMEVAR` reads the path out of the named env
144
+ # var, so it is not in the command at all and cannot be allowlisted.
145
+ if lowered.startswith("--config-env="):
146
+ spec = token.split("=", 1)[1]
147
+ if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
148
+ sys.exit(1)
149
+ # git also accepts `--config-env <name>=<envvar>` as two tokens. Guarding
150
+ # only the `=` spelling let the trailing `core.hooksPath=.husky` fall
151
+ # through to the allowlist above, which reads `.husky` as a path — but here
152
+ # it names an ENVIRONMENT VARIABLE, which can hold /dev/null. Checked at the
153
+ # `--config-env` token, which the loop reaches first.
154
+ if lowered == "--config-env" and i + 1 < len(normalized_tokens):
155
+ spec = normalized_tokens[i + 1]
156
+ if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
113
157
  sys.exit(1)
114
- if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
158
+ # `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
159
+ # GIT_CONFIG_VALUE_0=/dev/null git ...` sets the same command-scope config
160
+ # via env-var-style assignments. The index is arbitrary below
161
+ # GIT_CONFIG_COUNT, so it is matched as `\d+` rather than pinned to 0.
162
+ key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
163
+ if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
115
164
  sys.exit(1)
116
165
 
117
166
  sys.exit(0)
@@ -186,6 +186,37 @@ for i, token in enumerate(normalized_tokens):
186
186
  spec = token.split("=", 1)[1]
187
187
  if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
188
188
  sys.exit(1)
189
+ # git accepts `--config-env <name>=<envvar>` as TWO tokens as well as one,
190
+ # and guarding only the `=` spelling was worse than missing the separate
191
+ # form outright: the trailing `core.hooksPath=.husky` then fell through to
192
+ # the allowlist above, which reads `.husky` as a PATH and permits it. But
193
+ # here it is an ENVIRONMENT VARIABLE NAME, and `env '.husky=/dev/null' git
194
+ # --config-env core.hooksPath=.husky` really does resolve hooksPath to
195
+ # /dev/null. The allowlist was being used as the bypass.
196
+ #
197
+ # Checked at the `--config-env` token, which the loop reaches first, so the
198
+ # refusal happens before the value token can be mistaken for a path.
199
+ if lowered == "--config-env" and i + 1 < len(normalized_tokens):
200
+ spec = normalized_tokens[i + 1]
201
+ if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
202
+ sys.exit(1)
203
+ # `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
204
+ # GIT_CONFIG_VALUE_0=/dev/null git commit` sets command-scope config the
205
+ # same way `-c core.hooksPath=...` does — env-var-style assignments ahead of
206
+ # the invocation instead of a flag — so it disables every hook just as
207
+ # completely while matching none of the token shapes above. Upstream missed
208
+ # this until a downstream fork hardened its own copy against it, which is
209
+ # the one direction a guard must never be caught in.
210
+ #
211
+ # The index is matched as `\d+` rather than pinned to 0: git accepts any
212
+ # index below GIT_CONFIG_COUNT, so a single-index check is evaded by typing
213
+ # a 1. Refused outright, like --config-env=, because the path lives in a
214
+ # separate GIT_CONFIG_VALUE_<n> token that can be exported earlier,
215
+ # reordered, or left out entirely — there is nothing here to allowlist
216
+ # against.
217
+ key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
218
+ if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
219
+ sys.exit(1)
189
220
 
190
221
  sys.exit(0)
191
222
  PY