@codyswann/lisa 2.331.3 → 2.332.1

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 (72) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +5 -3
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  8. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  9. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
  10. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  11. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  12. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  15. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  16. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  24. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  25. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
  26. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  28. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  29. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
  30. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo-agy/plugin.json +1 -1
  33. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  53. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails-agy/plugin.json +1 -1
  58. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  63. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  68. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
  71. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
  72. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
@@ -76,13 +76,37 @@ function bindingsFor(surface) {
76
76
  /**
77
77
  * Install methods the toolchain runner supports.
78
78
  *
79
- * `release-tar` was missing here while the runner had supported it for as long
80
- * as gh has been pinned so this validator would have rejected the project's
81
- * own manifest, and the only reason nobody hit it is that nothing ran the two
82
- * against each other. Kept in step with `assertPinned` in `toolchain.mjs`,
83
- * which is the list that actually decides.
79
+ * This is a deliberate copy of `INSTALL_METHODS` in `toolchain.mjs`, which is
80
+ * the list that actually decides. Skills are distributed as self-contained
81
+ * directories and nothing imports across a skill boundary, so importing the
82
+ * authority here would couple two skills' distribution for one array.
83
+ *
84
+ * The copy has drifted twice. First `release-tar` was missing while the runner
85
+ * had supported it for as long as gh has been pinned. Then `release-tree` and
86
+ * `release-binary` were both missing, which rejected a valid `jq` pin. Each
87
+ * time the only safeguard was a comment asking the next author to remember.
88
+ * A conformance test now compares the two sets directly, so adding a kind on
89
+ * either side fails until both know it.
84
90
  */
85
- const INSTALL_METHODS = new Set(["release-zip", "release-tar", "npm-global"]);
91
+ const INSTALL_METHODS = new Set([
92
+ "release-zip",
93
+ "release-tar",
94
+ "release-tree",
95
+ "release-binary",
96
+ "npm-global",
97
+ ]);
98
+
99
+ /**
100
+ * Download kinds — every method that fetches a URL, so every one that needs a
101
+ * checksum. Spelled as "not npm-global" rather than as a list, because the next
102
+ * download kind should inherit the obligation by default; the drift above came
103
+ * from lists that had to be remembered.
104
+ * @param {string} method An install method.
105
+ * @returns {boolean} Whether the method fetches a pinned artifact.
106
+ */
107
+ function isDownloadKind(method) {
108
+ return INSTALL_METHODS.has(method) && method !== "npm-global";
109
+ }
86
110
 
87
111
  /**
88
112
  * Validate the `secrets` block.
@@ -171,19 +195,28 @@ function validateInstallArtifact(label, entry, problems) {
171
195
  );
172
196
  return;
173
197
  }
174
- // Both archive kinds, not just zip. A tarball pinned without a checksum
175
- // trusts whatever the URL serves today exactly as much as a zip does, and
176
- // checking only one of them meant the tool Lisa's guardrails shell out to —
177
- // gh, which ships a tarball on Linux — was the one going unverified.
178
- if (
179
- (entry.install === "release-zip" || entry.install === "release-tar") &&
180
- !(entry.url && entry.sha256)
181
- ) {
198
+ // Every download kind, not an enumerated few. A tarball pinned without a
199
+ // checksum trusts whatever the URL serves today exactly as much as a zip
200
+ // does, and checking only one of them meant the tool Lisa's guardrails shell
201
+ // out to — gh, which ships a tarball on Linux — was the one going unverified.
202
+ // A `release-binary` matters most of all: the artifact is directly
203
+ // executable, so a wrong one needs no unpacking step to run.
204
+ if (isDownloadKind(entry.install) && !(entry.url && entry.sha256)) {
182
205
  problems.push(
183
206
  `remoteEnv install ${label} needs both url and sha256. A pinned ` +
184
207
  `version with no checksum still trusts whatever the URL serves today.`
185
208
  );
186
209
  }
210
+ // A tree is the one kind with no sane default entry point: the archive root
211
+ // is a directory, so an omitted `binary` would install a directory as a
212
+ // command. The runner refuses it, and a validator that accepted it would just
213
+ // move the failure from review to provisioning.
214
+ if (entry.install === "release-tree" && !entry.binary) {
215
+ problems.push(
216
+ `remoteEnv install ${label} needs "binary" — the path to the entry ` +
217
+ `point INSIDE the archive, such as "maestro/bin/maestro".`
218
+ );
219
+ }
187
220
  if (entry.install === "npm-global" && !entry.package) {
188
221
  problems.push(`remoteEnv install ${label} needs a package`);
189
222
  }
@@ -300,6 +300,44 @@ function installReleaseTree(tool, binDir) {
300
300
  }
301
301
  }
302
302
 
303
+ /**
304
+ * Install a pinned artifact that IS the binary, with no archive around it.
305
+ *
306
+ * Several tools worth pinning publish a raw executable rather than an archive —
307
+ * jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
308
+ * checksummed pin and fits none of the archive kinds.
309
+ *
310
+ * Without this the only entry that passes `assertPinned` is `release-zip`, which
311
+ * then fails at install when `unzip` is handed a binary. A manifest entry that
312
+ * validates and cannot install is worse than one that is rejected outright: the
313
+ * error arrives during provisioning rather than during review.
314
+ *
315
+ * There is no `binary` field here, deliberately — the download is the binary,
316
+ * so there is nothing inside it to name.
317
+ * @param {object} tool Manifest entry.
318
+ * @param {string} binDir Directory to install into.
319
+ */
320
+ function installReleaseBinary(tool, binDir) {
321
+ const temporary = join(binDir, `.${tool.name}-download`);
322
+ mkdirSync(temporary, { recursive: true });
323
+ try {
324
+ const artifact = join(temporary, tool.name);
325
+ execFileSync("curl", ["-fsSL", tool.url, "-o", artifact], {
326
+ stdio: "inherit",
327
+ });
328
+ // Verify before it reaches a directory on PATH. For this kind that ordering
329
+ // matters more than for the archives: the downloaded file is directly
330
+ // executable, so a wrong artifact placed first is a wrong artifact that can
331
+ // be run.
332
+ verifyChecksum(artifact, tool.sha256, tool.name);
333
+ execFileSync("install", ["-m", "0755", artifact, join(binDir, tool.name)], {
334
+ stdio: "inherit",
335
+ });
336
+ } finally {
337
+ rmSync(temporary, { recursive: true, force: true });
338
+ }
339
+ }
340
+
303
341
  /**
304
342
  * Install a pinned global npm package.
305
343
  * @param {object} tool Manifest entry.
@@ -328,6 +366,8 @@ export function installTool(tool, binDir) {
328
366
  if (tool.install === "release-zip") installReleaseZip(tool, binDir);
329
367
  else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
330
368
  else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
369
+ else if (tool.install === "release-binary")
370
+ installReleaseBinary(tool, binDir);
331
371
  else installNpmGlobal(tool);
332
372
  }
333
373
 
@@ -301,6 +301,24 @@ export function planToolchain(
301
301
  return plan;
302
302
  }
303
303
 
304
+ /**
305
+ * Every install method the runner can execute — the authoritative list.
306
+ *
307
+ * `validate-config.mjs` maintains its own copy because skills are distributed
308
+ * as self-contained directories and nothing else imports across a skill
309
+ * boundary. A comment saying "keep these in step" has now failed twice — first
310
+ * for `release-tar`, then for `release-tree` and `release-binary` together — so
311
+ * the pairing is enforced by a conformance test against this export instead.
312
+ * Adding a kind here fails that test until the validator learns it too.
313
+ */
314
+ export const INSTALL_METHODS = Object.freeze([
315
+ "release-zip",
316
+ "release-tar",
317
+ "release-tree",
318
+ "release-binary",
319
+ "npm-global",
320
+ ]);
321
+
304
322
  /**
305
323
  * Reject a manifest entry that could install something unverifiable.
306
324
  *
@@ -354,6 +372,18 @@ export function assertPinned(tool) {
354
372
  }
355
373
  return;
356
374
  }
375
+ // The download IS the binary, so there is no `binary` path to require — but
376
+ // the checksum matters more here than anywhere else, because the artifact is
377
+ // directly executable rather than something that has to be unpacked first.
378
+ if (tool.install === "release-binary") {
379
+ if (!tool.url || !tool.sha256) {
380
+ throw new Error(
381
+ `${tool.name}: a release-binary install needs both url and sha256.\n` +
382
+ `A version bump must move the checksum in the same reviewed commit.`
383
+ );
384
+ }
385
+ return;
386
+ }
357
387
  if (tool.install === "npm-global") {
358
388
  if (!tool.package)
359
389
  throw new Error(`${tool.name}: npm-global install needs a package`);
@@ -361,6 +391,6 @@ export function assertPinned(tool) {
361
391
  }
362
392
  throw new Error(
363
393
  `${tool.name}: unknown install method "${tool.install}".\n` +
364
- `Supported: release-zip, release-tar, release-tree, npm-global.`
394
+ `Supported: ${INSTALL_METHODS.join(", ")}.`
365
395
  );
366
396
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.331.3",
3
+ "version": "2.332.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -76,13 +76,37 @@ function bindingsFor(surface) {
76
76
  /**
77
77
  * Install methods the toolchain runner supports.
78
78
  *
79
- * `release-tar` was missing here while the runner had supported it for as long
80
- * as gh has been pinned so this validator would have rejected the project's
81
- * own manifest, and the only reason nobody hit it is that nothing ran the two
82
- * against each other. Kept in step with `assertPinned` in `toolchain.mjs`,
83
- * which is the list that actually decides.
79
+ * This is a deliberate copy of `INSTALL_METHODS` in `toolchain.mjs`, which is
80
+ * the list that actually decides. Skills are distributed as self-contained
81
+ * directories and nothing imports across a skill boundary, so importing the
82
+ * authority here would couple two skills' distribution for one array.
83
+ *
84
+ * The copy has drifted twice. First `release-tar` was missing while the runner
85
+ * had supported it for as long as gh has been pinned. Then `release-tree` and
86
+ * `release-binary` were both missing, which rejected a valid `jq` pin. Each
87
+ * time the only safeguard was a comment asking the next author to remember.
88
+ * A conformance test now compares the two sets directly, so adding a kind on
89
+ * either side fails until both know it.
84
90
  */
85
- const INSTALL_METHODS = new Set(["release-zip", "release-tar", "npm-global"]);
91
+ const INSTALL_METHODS = new Set([
92
+ "release-zip",
93
+ "release-tar",
94
+ "release-tree",
95
+ "release-binary",
96
+ "npm-global",
97
+ ]);
98
+
99
+ /**
100
+ * Download kinds — every method that fetches a URL, so every one that needs a
101
+ * checksum. Spelled as "not npm-global" rather than as a list, because the next
102
+ * download kind should inherit the obligation by default; the drift above came
103
+ * from lists that had to be remembered.
104
+ * @param {string} method An install method.
105
+ * @returns {boolean} Whether the method fetches a pinned artifact.
106
+ */
107
+ function isDownloadKind(method) {
108
+ return INSTALL_METHODS.has(method) && method !== "npm-global";
109
+ }
86
110
 
87
111
  /**
88
112
  * Validate the `secrets` block.
@@ -171,19 +195,28 @@ function validateInstallArtifact(label, entry, problems) {
171
195
  );
172
196
  return;
173
197
  }
174
- // Both archive kinds, not just zip. A tarball pinned without a checksum
175
- // trusts whatever the URL serves today exactly as much as a zip does, and
176
- // checking only one of them meant the tool Lisa's guardrails shell out to —
177
- // gh, which ships a tarball on Linux — was the one going unverified.
178
- if (
179
- (entry.install === "release-zip" || entry.install === "release-tar") &&
180
- !(entry.url && entry.sha256)
181
- ) {
198
+ // Every download kind, not an enumerated few. A tarball pinned without a
199
+ // checksum trusts whatever the URL serves today exactly as much as a zip
200
+ // does, and checking only one of them meant the tool Lisa's guardrails shell
201
+ // out to — gh, which ships a tarball on Linux — was the one going unverified.
202
+ // A `release-binary` matters most of all: the artifact is directly
203
+ // executable, so a wrong one needs no unpacking step to run.
204
+ if (isDownloadKind(entry.install) && !(entry.url && entry.sha256)) {
182
205
  problems.push(
183
206
  `remoteEnv install ${label} needs both url and sha256. A pinned ` +
184
207
  `version with no checksum still trusts whatever the URL serves today.`
185
208
  );
186
209
  }
210
+ // A tree is the one kind with no sane default entry point: the archive root
211
+ // is a directory, so an omitted `binary` would install a directory as a
212
+ // command. The runner refuses it, and a validator that accepted it would just
213
+ // move the failure from review to provisioning.
214
+ if (entry.install === "release-tree" && !entry.binary) {
215
+ problems.push(
216
+ `remoteEnv install ${label} needs "binary" — the path to the entry ` +
217
+ `point INSIDE the archive, such as "maestro/bin/maestro".`
218
+ );
219
+ }
187
220
  if (entry.install === "npm-global" && !entry.package) {
188
221
  problems.push(`remoteEnv install ${label} needs a package`);
189
222
  }
@@ -300,6 +300,44 @@ function installReleaseTree(tool, binDir) {
300
300
  }
301
301
  }
302
302
 
303
+ /**
304
+ * Install a pinned artifact that IS the binary, with no archive around it.
305
+ *
306
+ * Several tools worth pinning publish a raw executable rather than an archive —
307
+ * jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
308
+ * checksummed pin and fits none of the archive kinds.
309
+ *
310
+ * Without this the only entry that passes `assertPinned` is `release-zip`, which
311
+ * then fails at install when `unzip` is handed a binary. A manifest entry that
312
+ * validates and cannot install is worse than one that is rejected outright: the
313
+ * error arrives during provisioning rather than during review.
314
+ *
315
+ * There is no `binary` field here, deliberately — the download is the binary,
316
+ * so there is nothing inside it to name.
317
+ * @param {object} tool Manifest entry.
318
+ * @param {string} binDir Directory to install into.
319
+ */
320
+ function installReleaseBinary(tool, binDir) {
321
+ const temporary = join(binDir, `.${tool.name}-download`);
322
+ mkdirSync(temporary, { recursive: true });
323
+ try {
324
+ const artifact = join(temporary, tool.name);
325
+ execFileSync("curl", ["-fsSL", tool.url, "-o", artifact], {
326
+ stdio: "inherit",
327
+ });
328
+ // Verify before it reaches a directory on PATH. For this kind that ordering
329
+ // matters more than for the archives: the downloaded file is directly
330
+ // executable, so a wrong artifact placed first is a wrong artifact that can
331
+ // be run.
332
+ verifyChecksum(artifact, tool.sha256, tool.name);
333
+ execFileSync("install", ["-m", "0755", artifact, join(binDir, tool.name)], {
334
+ stdio: "inherit",
335
+ });
336
+ } finally {
337
+ rmSync(temporary, { recursive: true, force: true });
338
+ }
339
+ }
340
+
303
341
  /**
304
342
  * Install a pinned global npm package.
305
343
  * @param {object} tool Manifest entry.
@@ -328,6 +366,8 @@ export function installTool(tool, binDir) {
328
366
  if (tool.install === "release-zip") installReleaseZip(tool, binDir);
329
367
  else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
330
368
  else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
369
+ else if (tool.install === "release-binary")
370
+ installReleaseBinary(tool, binDir);
331
371
  else installNpmGlobal(tool);
332
372
  }
333
373
 
@@ -301,6 +301,24 @@ export function planToolchain(
301
301
  return plan;
302
302
  }
303
303
 
304
+ /**
305
+ * Every install method the runner can execute — the authoritative list.
306
+ *
307
+ * `validate-config.mjs` maintains its own copy because skills are distributed
308
+ * as self-contained directories and nothing else imports across a skill
309
+ * boundary. A comment saying "keep these in step" has now failed twice — first
310
+ * for `release-tar`, then for `release-tree` and `release-binary` together — so
311
+ * the pairing is enforced by a conformance test against this export instead.
312
+ * Adding a kind here fails that test until the validator learns it too.
313
+ */
314
+ export const INSTALL_METHODS = Object.freeze([
315
+ "release-zip",
316
+ "release-tar",
317
+ "release-tree",
318
+ "release-binary",
319
+ "npm-global",
320
+ ]);
321
+
304
322
  /**
305
323
  * Reject a manifest entry that could install something unverifiable.
306
324
  *
@@ -354,6 +372,18 @@ export function assertPinned(tool) {
354
372
  }
355
373
  return;
356
374
  }
375
+ // The download IS the binary, so there is no `binary` path to require — but
376
+ // the checksum matters more here than anywhere else, because the artifact is
377
+ // directly executable rather than something that has to be unpacked first.
378
+ if (tool.install === "release-binary") {
379
+ if (!tool.url || !tool.sha256) {
380
+ throw new Error(
381
+ `${tool.name}: a release-binary install needs both url and sha256.\n` +
382
+ `A version bump must move the checksum in the same reviewed commit.`
383
+ );
384
+ }
385
+ return;
386
+ }
357
387
  if (tool.install === "npm-global") {
358
388
  if (!tool.package)
359
389
  throw new Error(`${tool.name}: npm-global install needs a package`);
@@ -361,6 +391,6 @@ export function assertPinned(tool) {
361
391
  }
362
392
  throw new Error(
363
393
  `${tool.name}: unknown install method "${tool.install}".\n` +
364
- `Supported: release-zip, release-tar, release-tree, npm-global.`
394
+ `Supported: ${INSTALL_METHODS.join(", ")}.`
365
395
  );
366
396
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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": "2.331.3",
3
+ "version": "2.332.1",
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"