@codyswann/lisa 3.33.1 → 3.33.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  2. package/dist/core/lisa-owned-hash-ledger.js +3 -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 +2 -1
  6. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  7. package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +286 -20
  8. package/package.json +1 -1
  9. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-agy/plugin.json +1 -1
  12. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  15. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-expo-agy/plugin.json +1 -1
  22. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  32. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  37. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  42. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-rails-agy/plugin.json +1 -1
  47. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  52. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  57. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
@@ -47,6 +47,10 @@ const FLOW_FILE_PATTERN = /\.(?:yaml|yml)$/;
47
47
  // `e2e-route: /path` inside any comment declares a route as covered even when
48
48
  // the spec reaches it by tapping/navigating rather than by URL or deep link.
49
49
  const ROUTE_ANNOTATION_PATTERN = /e2e-route:\s*(\/[^\s'"`,)]*)/g;
50
+ // `e2e-route-exempt: /path` is the counterpart to `e2e-route:` — it declares a
51
+ // route deliberately uncovered, removing it from the denominator rather than
52
+ // leaving a team to reach for a mechanism this gate cannot see.
53
+ const EXEMPT_ANNOTATION_PATTERN = /e2e-route-exempt:\s*(\/[^\s'"`,)]*)/g;
50
54
  const GOTO_PATTERN = /\.goto\(\s*(["'`])([^"'`]+)\1/g;
51
55
  const OPEN_LINK_PATTERN = /openLink:\s*["']?([^\s"']+)/g;
52
56
 
@@ -218,6 +222,190 @@ export function mergeThresholds(defaults, overrides) {
218
222
  };
219
223
  }
220
224
 
225
+ /**
226
+ * Route exemptions a project has deliberately declared.
227
+ *
228
+ * The script could already ADD a coverage claim (`e2e-route:`) but had no way
229
+ * to remove one. A team exempting a route therefore reached for a mechanism the
230
+ * gate cannot see — `testIgnore`, a disabled project, a Maestro tag — and the
231
+ * number silently overstated. Detecting those mechanisms is only half a fix: if
232
+ * there is no supported way to say "this route is deliberately uncovered", the
233
+ * workaround stays the only option.
234
+ *
235
+ * Exemptions leave the denominator, and are always PRINTED. A silent exemption
236
+ * would be the same defect wearing a sanctioned label.
237
+ * @param {object} input - Exemption input
238
+ * @param {string[]} input.routes - Enumerated app routes
239
+ * @param {string[]} [input.declared] - Routes exempted in e2e.thresholds.json
240
+ * @param {string[]} [input.annotated] - Routes exempted by `e2e-route-exempt:`
241
+ * @returns {{kept: string[], exempt: string[], unknown: string[]}} Partition
242
+ */
243
+ export function applyExemptions({ routes, declared, annotated }) {
244
+ const asked = [...new Set([...(declared ?? []), ...(annotated ?? [])])];
245
+ const exempt = asked.filter(route => routes.includes(route));
246
+ // An exemption naming a route that does not exist is stale — a renamed or
247
+ // deleted screen whose waiver outlived it. Reported, never silently dropped,
248
+ // because a stale exemption is how a real gap gets permanently excused.
249
+ const unknown = asked.filter(route => !routes.includes(route));
250
+ return {
251
+ kept: routes.filter(route => !exempt.includes(route)),
252
+ exempt,
253
+ unknown,
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Routes exempted by an `e2e-route-exempt: /path` comment in a spec or flow.
259
+ *
260
+ * The comment form exists so an exemption lives next to the thing that would
261
+ * otherwise cover the route, where a reviewer of that file sees it. The
262
+ * thresholds-file form exists for a route no file mentions at all. Both are
263
+ * read; neither is silent.
264
+ * @param {string} root - Project root
265
+ * @returns {string[]} Exempted routes
266
+ */
267
+ export function collectExemptAnnotations(root) {
268
+ const scan = (directories, filePattern) =>
269
+ directories.flatMap(directory => {
270
+ const absolute = path.join(root, directory);
271
+ return listFiles(absolute)
272
+ .filter(file => filePattern.test(file))
273
+ .flatMap(file => [
274
+ ...fs
275
+ .readFileSync(path.join(absolute, file), "utf8")
276
+ .matchAll(EXEMPT_ANNOTATION_PATTERN),
277
+ ])
278
+ .map(match => match[1]);
279
+ });
280
+ return [
281
+ ...scan(["e2e", "tests/e2e"], SPEC_FILE_PATTERN),
282
+ ...scan([".maestro"], FLOW_FILE_PATTERN),
283
+ ];
284
+ }
285
+
286
+ /**
287
+ * Glob-ish patterns Playwright is configured to ignore.
288
+ *
289
+ * Read textually rather than by importing the config: it is TypeScript, may
290
+ * import project code, and executing it to compute a coverage number would be a
291
+ * far larger blast radius than reading it. A pattern this misses costs a false
292
+ * INCLUSION — the old behaviour — so the failure mode of imprecision here is
293
+ * the status quo rather than a new one.
294
+ * @param {string} root - Project root
295
+ * @returns {string[]} Ignore patterns, empty when no config is found
296
+ */
297
+ export function readPlaywrightIgnores(root) {
298
+ const config = [
299
+ "playwright.config.ts",
300
+ "playwright.config.js",
301
+ "playwright.config.mjs",
302
+ ]
303
+ .map(name => path.join(root, name))
304
+ .find(candidate => fs.existsSync(candidate));
305
+ if (!config) {
306
+ return [];
307
+ }
308
+ const source = fs.readFileSync(config, "utf8");
309
+ const block = source.match(
310
+ /testIgnore\s*:\s*(\[[^\]]*\]|["'`][^"'`]*["'`])/u
311
+ );
312
+ if (!block) {
313
+ return [];
314
+ }
315
+ return [...block[1].matchAll(/["'`]([^"'`]+)["'`]/gu)].map(match => match[1]);
316
+ }
317
+
318
+ /**
319
+ * Whether a spec path is excluded by one of Playwright's ignore patterns.
320
+ * @param {string} relativePath - Spec path relative to its scan directory
321
+ * @param {string[]} patterns - Ignore patterns
322
+ * @returns {boolean} True when the spec will not run
323
+ */
324
+ export function isIgnoredSpec(relativePath, patterns) {
325
+ return patterns.some(pattern => globToRegExp(pattern).test(relativePath));
326
+ }
327
+
328
+ /**
329
+ * Translate one glob to an anchored expression.
330
+ *
331
+ * `**` spans directories, `*` does not, and `**\/` must also match nothing at
332
+ * all — `**\/x.spec.ts` names a file at the scan root as well as a nested one.
333
+ * Requiring the slash silently un-ignored every top-level spec, which is the
334
+ * bug this whole function exists to close, so it is the case the tests pin.
335
+ * @param {string} pattern - A glob from Playwright's testIgnore
336
+ * @returns {RegExp} Anchored matcher
337
+ */
338
+ function globToRegExp(pattern) {
339
+ const DOUBLE_SLASH = "\u0000";
340
+ const DOUBLE = "\u0001";
341
+ const SINGLE = "\u0002";
342
+ const marked = pattern
343
+ .replaceAll("**/", DOUBLE_SLASH)
344
+ .replaceAll("**", DOUBLE)
345
+ .replaceAll("*", SINGLE);
346
+ const escaped = marked.replace(/[.+^${}()|[\]\\?]/gu, "\\$&");
347
+ const expression = escaped
348
+ .replaceAll(DOUBLE_SLASH, "(?:.*/)?")
349
+ .replaceAll(DOUBLE, ".*")
350
+ .replaceAll(SINGLE, "[^/]*");
351
+ return new RegExp(`(^|/)${expression}$`, "u");
352
+ }
353
+
354
+ /**
355
+ * Whether a Maestro flow carries a tag the running suite excludes.
356
+ *
357
+ * The exclusion is supplied by the caller (`MAESTRO_EXCLUDE_TAGS`) because the
358
+ * suite decides it at invocation time — the flow file cannot know, and neither
359
+ * can this script by reading the repository.
360
+ * @param {string} source - Flow file contents
361
+ * @param {string[]} excluded - Tags the suite excludes
362
+ * @returns {boolean} True when the flow will not run
363
+ */
364
+ export function isExcludedFlow(source, excluded) {
365
+ if (excluded.length === 0) {
366
+ return false;
367
+ }
368
+ return flowTags(source).some(tag => excluded.includes(tag));
369
+ }
370
+
371
+ /**
372
+ * The tags a Maestro flow declares, and nothing else.
373
+ *
374
+ * Read line-wise because YAML block structure is indentation, and the previous
375
+ * regex form could not express "until this block ends": it allowed zero
376
+ * indentation before `-`, so it ran past the `---` document separator and
377
+ * lifted `openLink` and `tapOn` out of the flow's COMMANDS. A false tag causes
378
+ * a false EXCLUSION, dropping a flow that really runs — the opposite error from
379
+ * the one this gate exists to fix, and just as wrong.
380
+ * @param {string} source - Flow file contents
381
+ * @returns {string[]} Declared tags
382
+ */
383
+ function flowTags(source) {
384
+ const lines = source.split("\n");
385
+ const start = lines.findIndex(line => /^tags:/u.test(line));
386
+ if (start === -1) {
387
+ return [];
388
+ }
389
+ const inline = lines[start].match(/^tags:\s*\[([^\]]*)\]/u);
390
+ if (inline) {
391
+ return inline[1]
392
+ .split(",")
393
+ .map(tag => tag.trim().replace(/^["']|["']$/gu, ""))
394
+ .filter(Boolean);
395
+ }
396
+ const tags = [];
397
+ for (const line of lines.slice(start + 1)) {
398
+ // The block ends at the document separator, at a non-indented line, or at
399
+ // anything that is not a list item. Each of those is what the regex missed.
400
+ const item = line.match(/^\s+-\s*(.+?)\s*$/u);
401
+ if (!item || line.startsWith("---")) {
402
+ break;
403
+ }
404
+ tags.push(item[1].replace(/^["']|["']$/gu, ""));
405
+ }
406
+ return tags;
407
+ }
408
+
221
409
  /**
222
410
  * Pure decision: does each runner's route coverage meet its threshold?
223
411
  * @param {object} input - Evaluation input
@@ -304,15 +492,32 @@ function listFiles(directory) {
304
492
  * @param {(source: string) => string[]} input.extract - Path extractor
305
493
  * @returns {string[]} Every visited path found
306
494
  */
307
- function collectVisitedPaths({ root, directories, filePattern, extract }) {
308
- return directories.flatMap(directory => {
495
+ function collectVisitedPaths({
496
+ root,
497
+ directories,
498
+ filePattern,
499
+ extract,
500
+ skip,
501
+ }) {
502
+ const read = [];
503
+ const skipped = [];
504
+ const visited = directories.flatMap(directory => {
309
505
  const absolute = path.join(root, directory);
310
506
  return listFiles(absolute)
311
507
  .filter(file => filePattern.test(file))
312
- .flatMap(file =>
313
- extract(fs.readFileSync(path.join(absolute, file), "utf8"))
314
- );
508
+ .flatMap(file => {
509
+ const source = fs.readFileSync(path.join(absolute, file), "utf8");
510
+ // A file that will not run contributes no coverage, however many
511
+ // routes it mentions. Presence on disk was the whole bug.
512
+ if (skip?.({ file, source })) {
513
+ skipped.push(`${directory}/${file}`);
514
+ return [];
515
+ }
516
+ read.push(`${directory}/${file}`);
517
+ return extract(source);
518
+ });
315
519
  });
520
+ return { visited, read, skipped };
316
521
  }
317
522
 
318
523
  /**
@@ -330,9 +535,16 @@ function main() {
330
535
  );
331
536
  return;
332
537
  }
333
- const routes = enumerateRoutes(listFiles(appDir));
334
- if (routes.length === 0) {
335
- console.log("[e2e-coverage] OK: no navigable routes nothing to gate.");
538
+ const allRoutes = enumerateRoutes(listFiles(appDir));
539
+ if (allRoutes.length === 0) {
540
+ // An app directory that enumerates ZERO navigable routes is a discovery
541
+ // bug, not an empty app — a renamed tree or a changed file pattern. The
542
+ // old behaviour reported "nothing to gate" and exited 0, so the gate
543
+ // passed loudest exactly when it had measured nothing.
544
+ console.error(
545
+ `[e2e-coverage] FAIL: found an app directory (${path.relative(root, appDir) || "."}) but enumerated no navigable routes. That is a discovery failure, not an empty app — check the route file patterns before trusting any coverage number.`
546
+ );
547
+ process.exit(1);
336
548
  return;
337
549
  }
338
550
 
@@ -347,20 +559,74 @@ function main() {
347
559
  }
348
560
  const thresholds = mergeThresholds(defaultThresholds, overrides);
349
561
 
562
+ const ignores = readPlaywrightIgnores(root);
563
+ const excludedTags = (process.env.MAESTRO_EXCLUDE_TAGS ?? "")
564
+ .split(",")
565
+ .map(tag => tag.trim())
566
+ .filter(Boolean);
567
+
568
+ const playwright = collectVisitedPaths({
569
+ root,
570
+ directories: ["e2e", "tests/e2e"],
571
+ filePattern: SPEC_FILE_PATTERN,
572
+ extract: extractPlaywrightPaths,
573
+ skip: ({ file }) => isIgnoredSpec(file, ignores),
574
+ });
575
+ const maestro = collectVisitedPaths({
576
+ root,
577
+ directories: [".maestro"],
578
+ filePattern: FLOW_FILE_PATTERN,
579
+ extract: extractMaestroPaths,
580
+ skip: ({ source }) => isExcludedFlow(source, excludedTags),
581
+ });
582
+
583
+ const {
584
+ kept: routes,
585
+ exempt,
586
+ unknown,
587
+ } = applyExemptions({
588
+ routes: allRoutes,
589
+ declared: overrides?.exempt,
590
+ annotated: collectExemptAnnotations(root),
591
+ });
592
+
593
+ for (const [runner, collected] of [
594
+ ["playwright", playwright],
595
+ ["maestro", maestro],
596
+ ]) {
597
+ if (collected.skipped.length > 0) {
598
+ console.log(
599
+ `[e2e-coverage] ${runner}: ${collected.skipped.length} file(s) excluded from the run and NOT counted as coverage:\n` +
600
+ collected.skipped.map(file => ` - ${file}`).join("\n")
601
+ );
602
+ }
603
+ }
604
+ if (exempt.length > 0) {
605
+ console.log(
606
+ `[e2e-coverage] ${exempt.length} route(s) deliberately exempt (removed from the denominator):\n` +
607
+ exempt.map(route => ` - ${route}`).join("\n")
608
+ );
609
+ }
610
+ if (unknown.length > 0) {
611
+ console.error(
612
+ `[e2e-coverage] FAIL: ${unknown.length} exemption(s) name a route that does not exist. A waiver that outlived its screen permanently excuses whatever replaces it:\n` +
613
+ unknown.map(route => ` - ${route}`).join("\n")
614
+ );
615
+ process.exit(1);
616
+ return;
617
+ }
618
+ if (routes.length === 0) {
619
+ console.error(
620
+ "[e2e-coverage] FAIL: every route is exempt, so this gate measures nothing. Remove exemptions or disable the gate explicitly with a threshold of 0."
621
+ );
622
+ process.exit(1);
623
+ return;
624
+ }
625
+
350
626
  const result = evaluateE2eCoverage({
351
627
  routes,
352
- playwrightVisited: collectVisitedPaths({
353
- root,
354
- directories: ["e2e", "tests/e2e"],
355
- filePattern: SPEC_FILE_PATTERN,
356
- extract: extractPlaywrightPaths,
357
- }),
358
- maestroVisited: collectVisitedPaths({
359
- root,
360
- directories: [".maestro"],
361
- filePattern: FLOW_FILE_PATTERN,
362
- extract: extractMaestroPaths,
363
- }),
628
+ playwrightVisited: playwright.visited,
629
+ maestroVisited: maestro.visited,
364
630
  thresholds,
365
631
  });
366
632
 
package/package.json CHANGED
@@ -131,7 +131,7 @@
131
131
  "ws": ">=8.21.0"
132
132
  },
133
133
  "name": "@codyswann/lisa",
134
- "version": "3.33.1",
134
+ "version": "3.33.2",
135
135
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
136
136
  "main": "dist/index.js",
137
137
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
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.33.1",
3
+ "version": "3.33.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"