@codyswann/lisa 4.18.0 → 4.20.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 (75) hide show
  1. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  2. package/dist/core/lisa-owned-hash-ledger.js +14 -0
  3. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  4. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  5. package/dist/core/upstream-evidence-manifest.js +24 -6
  6. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  7. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +10 -2
  8. package/expo/copy-overwrite/scripts/bdd/render.mjs +3 -2
  9. package/expo/copy-overwrite/scripts/bdd-matrix.mjs +3 -3
  10. package/package.json +7 -2
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  14. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  15. package/plugins/lisa/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  16. package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  17. package/plugins/lisa-agy/plugin.json +1 -1
  18. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  19. package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  27. package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  28. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  30. package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  31. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo-agy/plugin.json +1 -1
  34. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  54. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails-agy/plugin.json +1 -1
  59. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  64. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  69. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/src/base/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
  72. package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +23 -5
  73. package/scripts/check-ui-demo-data.mjs +102 -0
  74. package/ui/README.md +32 -0
  75. package/ui/index.html +901 -115
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Publish a newly emitted remote-agent AWS bootstrap bundle to the configured
4
+ * secrets provider without creating a silent split brain.
5
+ *
6
+ * Infrastructure owns the IAM access key and usually emits the complete bundle
7
+ * to AWS Secrets Manager. Remote coding environments cannot use that store as
8
+ * their bootstrap provider: reading it already requires AWS credentials. The
9
+ * provider configured by lisa-secrets-access therefore needs the same bundle,
10
+ * and a manual copy leaves two ways for a rotation to stop halfway.
11
+ *
12
+ * This command makes that handoff one fail-closed operation:
13
+ *
14
+ * 1. prove the existing provider record is writable with a no-op write;
15
+ * 2. validate the candidate and assume every declared role with it;
16
+ * 3. replace the provider value;
17
+ * 4. read the value back exactly and assume every role again; and
18
+ * 5. restore the previous provider value if a post-write check fails.
19
+ *
20
+ * The candidate arrives on stdin so it never appears in a shell command or a
21
+ * temporary file. No credential value is written to stdout or stderr.
22
+ *
23
+ * Usage:
24
+ * aws secretsmanager get-secret-value ... --query SecretString --output text \
25
+ * | publish-aws-bootstrap.mjs publish
26
+ * publish-aws-bootstrap.mjs verify
27
+ * publish-aws-bootstrap.mjs preflight
28
+ * @module publish-aws-bootstrap
29
+ */
30
+
31
+ import { readFileSync, realpathSync } from "node:fs";
32
+ import { fileURLToPath } from "node:url";
33
+
34
+ import {
35
+ BOOTSTRAP_KEY,
36
+ parseBootstrap,
37
+ readProfiles,
38
+ } from "./aws-bootstrap.mjs";
39
+ import { fetchAll, writeSecret } from "./providers.mjs";
40
+ import { readConfig } from "./surfaces.mjs";
41
+
42
+ import { boundedChildOutput } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
43
+
44
+ const PROFILE_NAME = /^[\w.@-]+$/;
45
+ const ROLE_ARN =
46
+ /^arn:(?:aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[\w+=,.@/-]+$/;
47
+
48
+ /**
49
+ * Validate and normalize one complete bundle without changing its serialized
50
+ * representation.
51
+ * @param {string} raw Candidate JSON from stdin or the provider.
52
+ * @returns {{raw: string, bundle: object, profiles: Array<{name: string, roleArn: string, region: string}>}}
53
+ * Validated candidate and its role entries.
54
+ */
55
+ export function validateAwsBootstrap(raw) {
56
+ if (typeof raw !== "string" || !raw.trim()) {
57
+ throw new Error(
58
+ `${BOOTSTRAP_KEY} is empty; refusing to replace a working credential with nothing`
59
+ );
60
+ }
61
+
62
+ // AWS CLI's text output ends with one newline. It is transport framing, not
63
+ // part of the SecretString, so do not make every publication differ by one
64
+ // invisible byte from the value AWS emitted.
65
+ const normalized = raw.trim();
66
+ const bundle = parseBootstrap(normalized);
67
+ if (!bundle || Array.isArray(bundle)) {
68
+ throw new Error(`${BOOTSTRAP_KEY} is not a JSON object`);
69
+ }
70
+
71
+ const accessKeyId = bundle.accessKeyId ?? bundle.aws_access_key_id;
72
+ const secretAccessKey =
73
+ bundle.secretAccessKey ?? bundle.aws_secret_access_key;
74
+ for (const [field, value] of [
75
+ ["accessKeyId", accessKeyId],
76
+ ["secretAccessKey", secretAccessKey],
77
+ ["externalId", bundle.externalId],
78
+ ["roleName", bundle.roleName],
79
+ ]) {
80
+ if (typeof value !== "string" || !value.trim()) {
81
+ throw new Error(`${BOOTSTRAP_KEY}.${field} must be a non-empty string`);
82
+ }
83
+ }
84
+
85
+ const entries = Object.entries(readProfiles(bundle));
86
+ if (entries.length === 0) {
87
+ throw new Error(`${BOOTSTRAP_KEY}.profiles must declare at least one role`);
88
+ }
89
+
90
+ const profiles = entries.map(([name, entry]) => {
91
+ if (!PROFILE_NAME.test(name)) {
92
+ throw new Error(`AWS bootstrap profile name is not safe: ${name}`);
93
+ }
94
+ const roleArn = entry?.roleArn ?? entry?.role_arn;
95
+ if (typeof roleArn !== "string" || !ROLE_ARN.test(roleArn)) {
96
+ throw new Error(`AWS bootstrap profile ${name} has an invalid roleArn`);
97
+ }
98
+ if (typeof entry?.region !== "string" || !entry.region.trim()) {
99
+ throw new Error(`AWS bootstrap profile ${name} has no region`);
100
+ }
101
+ return { name, roleArn, region: entry.region };
102
+ });
103
+
104
+ return { raw: normalized, bundle, profiles };
105
+ }
106
+
107
+ /**
108
+ * Build the isolated environment used to prove one candidate. Ambient AWS
109
+ * variables are removed so a green result cannot come from the host identity.
110
+ * @param {object} bundle Validated bundle.
111
+ * @returns {NodeJS.ProcessEnv} Child environment containing only this identity.
112
+ */
113
+ export function candidateAwsEnvironment(bundle) {
114
+ const env = { ...process.env };
115
+ for (const key of [
116
+ "AWS_PROFILE",
117
+ "AWS_DEFAULT_PROFILE",
118
+ "AWS_SESSION_TOKEN",
119
+ "AWS_SECURITY_TOKEN",
120
+ ]) {
121
+ delete env[key];
122
+ }
123
+ env.AWS_ACCESS_KEY_ID = String(
124
+ bundle.accessKeyId ?? bundle.aws_access_key_id
125
+ );
126
+ env.AWS_SECRET_ACCESS_KEY = String(
127
+ bundle.secretAccessKey ?? bundle.aws_secret_access_key
128
+ );
129
+ env.AWS_PAGER = "";
130
+ return env;
131
+ }
132
+
133
+ /**
134
+ * Prove that the candidate authenticates and can assume every declared role.
135
+ * Captures all child output so account ids and credential metadata never reach
136
+ * a shared log.
137
+ * @param {{bundle: object, profiles: Array<object>}} candidate Validated bundle.
138
+ * @param {(bin: string, args: string[], options: object) => string} [run]
139
+ * Injectable child runner for tests.
140
+ */
141
+ export function verifyAwsBootstrap(candidate, run = boundedChildOutput) {
142
+ const env = candidateAwsEnvironment(candidate.bundle);
143
+ for (const profile of candidate.profiles) {
144
+ const sessionName = `lisa-publish-${profile.name}`
145
+ .replace(/[^A-Za-z0-9_+=,.@-]/g, "-")
146
+ .slice(0, 64);
147
+ try {
148
+ run(
149
+ "aws",
150
+ [
151
+ "sts",
152
+ "assume-role",
153
+ "--role-arn",
154
+ profile.roleArn,
155
+ "--role-session-name",
156
+ sessionName,
157
+ "--external-id",
158
+ String(candidate.bundle.externalId),
159
+ "--region",
160
+ profile.region,
161
+ "--query",
162
+ "Credentials.Expiration",
163
+ "--output",
164
+ "text",
165
+ ],
166
+ { encoding: "utf8", env, stdio: ["ignore", "pipe", "pipe"] }
167
+ );
168
+ } catch (error) {
169
+ const reason =
170
+ error instanceof Error ? error.message.split("\n")[0] : String(error);
171
+ throw new Error(
172
+ `AWS bootstrap profile ${profile.name} failed STS verification: ${reason}`
173
+ );
174
+ }
175
+ }
176
+ return candidate.profiles.map(profile => profile.name);
177
+ }
178
+
179
+ /**
180
+ * Locate the provider record that owns the bundle.
181
+ * @param {object} cfg Resolved lisa-secrets-access configuration.
182
+ * @param {(cfg: object) => Map<string, object>} fetch Provider reader.
183
+ * @returns {{id: string, value: string}} Writable provider entry.
184
+ */
185
+ function currentEntry(cfg, fetch) {
186
+ const entry = fetch(cfg).get(BOOTSTRAP_KEY);
187
+ if (!entry) {
188
+ throw new Error(
189
+ `${BOOTSTRAP_KEY} is not available through the configured provider boundary`
190
+ );
191
+ }
192
+ if (!entry.id) {
193
+ throw new Error(
194
+ `${BOOTSTRAP_KEY} has no provider identifier, so it cannot be published`
195
+ );
196
+ }
197
+ return { id: entry.id, value: entry.value };
198
+ }
199
+
200
+ /**
201
+ * Exercise the exact provider write permission without changing its value.
202
+ * @param {object} cfg Resolved configuration.
203
+ * @param {object} [operations] Injectable provider operations for tests.
204
+ * @returns {{id: string, value: string}} Current provider entry.
205
+ */
206
+ export function preflightAwsBootstrap(cfg, operations = {}) {
207
+ const fetch = operations.fetch ?? fetchAll;
208
+ const write = operations.write ?? writeSecret;
209
+ const entry = currentEntry(cfg, fetch);
210
+ try {
211
+ write(cfg, entry.id, entry.value);
212
+ } catch (error) {
213
+ const reason =
214
+ error instanceof Error ? error.message.split("\n")[0] : String(error);
215
+ throw new Error(
216
+ `no write access to ${BOOTSTRAP_KEY}: ${reason}\n` +
217
+ `Refusing to continue because a rotated key that cannot be published would strand every remote session.`
218
+ );
219
+ }
220
+ return entry;
221
+ }
222
+
223
+ /**
224
+ * Restore the previous provider value and prove the restoration.
225
+ * @param {object} cfg Resolved configuration.
226
+ * @param {{id: string, value: string}} previous Previous entry.
227
+ * @param {Function} fetch Provider reader.
228
+ * @param {Function} write Provider writer.
229
+ * @returns {string} Human-readable rollback outcome with no secret material.
230
+ */
231
+ function rollback(cfg, previous, fetch, write) {
232
+ try {
233
+ write(cfg, previous.id, previous.value);
234
+ const restored = currentEntry(cfg, fetch);
235
+ if (restored.value !== previous.value) {
236
+ return "the previous provider value could not be verified after rollback";
237
+ }
238
+ return "the previous provider value was restored";
239
+ } catch (error) {
240
+ const reason =
241
+ error instanceof Error ? error.message.split("\n")[0] : String(error);
242
+ return `rollback failed: ${reason}`;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Publish, read back, and verify a candidate. Any failure after the candidate
248
+ * write triggers a restoration of the previous provider value.
249
+ * @param {string} raw Candidate JSON.
250
+ * @param {object} cfg Resolved configuration.
251
+ * @param {object} [operations] Injectable provider/AWS operations for tests.
252
+ * @returns {{changed: boolean, profiles: string[]}} Publication outcome.
253
+ */
254
+ export function publishAwsBootstrap(raw, cfg, operations = {}) {
255
+ const fetch = operations.fetch ?? fetchAll;
256
+ const write = operations.write ?? writeSecret;
257
+ const verify = operations.verify ?? verifyAwsBootstrap;
258
+ const previous = preflightAwsBootstrap(cfg, { fetch, write });
259
+ const candidate = validateAwsBootstrap(raw);
260
+
261
+ // Verify before the write. A candidate that cannot assume one of its roles
262
+ // never becomes the value every new remote session receives.
263
+ verify(candidate);
264
+
265
+ if (candidate.raw === previous.value) {
266
+ return {
267
+ changed: false,
268
+ profiles: candidate.profiles.map(profile => profile.name),
269
+ };
270
+ }
271
+
272
+ try {
273
+ write(cfg, previous.id, candidate.raw);
274
+ const stored = currentEntry(cfg, fetch);
275
+ if (stored.value !== candidate.raw) {
276
+ throw new Error("provider read-back did not match the candidate exactly");
277
+ }
278
+ const storedCandidate = validateAwsBootstrap(stored.value);
279
+ verify(storedCandidate);
280
+ return {
281
+ changed: true,
282
+ profiles: storedCandidate.profiles.map(profile => profile.name),
283
+ };
284
+ } catch (error) {
285
+ const reason =
286
+ error instanceof Error ? error.message.split("\n")[0] : String(error);
287
+ const rollbackOutcome = rollback(cfg, previous, fetch, write);
288
+ throw new Error(
289
+ `${BOOTSTRAP_KEY} publication failed: ${reason}; ${rollbackOutcome}`
290
+ );
291
+ }
292
+ }
293
+
294
+ /** Read a candidate from stdin without ever placing it in argv. */
295
+ function readCandidate() {
296
+ return readFileSync(0, "utf8");
297
+ }
298
+
299
+ function main() {
300
+ const [operation] = process.argv.slice(2);
301
+ const cfg = readConfig();
302
+
303
+ if (operation === "preflight") {
304
+ const current = preflightAwsBootstrap(cfg);
305
+ const candidate = validateAwsBootstrap(current.value);
306
+ const profiles = verifyAwsBootstrap(candidate);
307
+ console.log(
308
+ `${BOOTSTRAP_KEY}: provider write path and ${profiles.length} AWS profile(s) verified; no value changed`
309
+ );
310
+ return;
311
+ }
312
+
313
+ if (operation === "verify") {
314
+ const current = currentEntry(cfg, fetchAll);
315
+ const candidate = validateAwsBootstrap(current.value);
316
+ const profiles = verifyAwsBootstrap(candidate);
317
+ console.log(`${BOOTSTRAP_KEY}: ${profiles.length} AWS profile(s) verified`);
318
+ return;
319
+ }
320
+
321
+ if (operation === "publish") {
322
+ const result = publishAwsBootstrap(readCandidate(), cfg);
323
+ console.log(
324
+ `${BOOTSTRAP_KEY}: ${result.changed ? "published" : "already current"}; ` +
325
+ `${result.profiles.length} AWS profile(s) verified`
326
+ );
327
+ return;
328
+ }
329
+
330
+ throw new Error("usage: publish-aws-bootstrap.mjs preflight|verify|publish");
331
+ }
332
+
333
+ function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
334
+ if (!argv1) return false;
335
+ try {
336
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
337
+ } catch {
338
+ return false;
339
+ }
340
+ }
341
+
342
+ if (invokedAsScript(import.meta.url)) {
343
+ try {
344
+ main();
345
+ } catch (error) {
346
+ console.error(error instanceof Error ? error.message : String(error));
347
+ process.exit(1);
348
+ }
349
+ }
@@ -49,11 +49,29 @@ it to AWS Secrets Manager as `remote-agent-credentials`, but that is where it is
49
49
  *emitted*, not where it must *live*. Copy it once into the project's configured
50
50
  provider and read it from there.
51
51
 
52
- **Do not keep it in two stores.** A bundle sitting in Secrets Manager *and* in
53
- the platform's secret store is two live copies of one credential: rotate the
54
- IAM key and whichever copy you forget keeps authenticating until it doesn't,
55
- with nothing to say which is current. That is the drift the single-store rule in
56
- `lisa-secrets-access` exists to prevent, and this credential is not exempt.
52
+ **Do not maintain two stores by hand.** A bundle sitting in Secrets Manager
53
+ and in the platform's secret store is two live copies of one credential: rotate
54
+ the IAM key and whichever copy you forget becomes stale, with nothing to say
55
+ which is current. Treat the infrastructure secret as a short-lived deployment
56
+ emission and the configured provider as the consumer-facing source of truth.
57
+
58
+ Publish the emission as part of the same rotation automation. The publisher
59
+ reads the candidate on stdin, proves every role before writing, reads the
60
+ provider value back exactly, proves every stored role again, and restores the
61
+ previous provider value if a post-write check fails:
62
+
63
+ ```bash
64
+ aws --profile shared secretsmanager get-secret-value \
65
+ --secret-id remote-agent-credentials \
66
+ --query SecretString \
67
+ --output text \
68
+ | node .claude/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs publish
69
+ ```
70
+
71
+ Resolve the script from the installed runtime or `node_modules` when the
72
+ `.claude/skills` copy is not present. Never print or place the candidate in an
73
+ argument or temporary file. A CDK deployment followed later by a human copy is
74
+ not a completed rotation.
57
75
 
58
76
  **One provider cannot serve this particular secret.** If `secrets.provider` is
59
77
  `aws`, reading the bundle requires AWS credentials — and the bundle *is* the AWS
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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": "4.18.0",
3
+ "version": "4.20.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"