@codyswann/lisa 4.33.12 → 4.33.13

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 (71) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +53 -4
  2. package/dist/configs/vitest/scratch-setup.d.ts +34 -0
  3. package/dist/configs/vitest/scratch-setup.d.ts.map +1 -1
  4. package/dist/configs/vitest/scratch-setup.js +89 -10
  5. package/dist/configs/vitest/scratch-setup.js.map +1 -1
  6. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  7. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  8. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  9. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  10. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  11. package/dist/core/upstream-evidence-manifest.js +7 -4
  12. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  13. package/dist/opencode/plugin-templates/parity-safety-net.sh +53 -4
  14. package/package.json +4 -4
  15. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa/hooks/parity-safety-net.sh +53 -4
  18. package/plugins/lisa-agy/hooks/parity-safety-net.sh +53 -4
  19. package/plugins/lisa-agy/plugin.json +1 -1
  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/hooks/parity-safety-net.sh +53 -4
  27. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +53 -4
  29. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-agy/plugin.json +1 -1
  32. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  52. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails-agy/plugin.json +1 -1
  57. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  62. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  67. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/src/base/hooks/parity-safety-net.sh +53 -4
  70. package/typescript/copy-contents/.husky/pre-commit +10 -6
  71. package/typescript/copy-contents/.husky/pre-push +46 -16
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
package/package.json CHANGED
@@ -165,7 +165,7 @@
165
165
  "zod-validation-error": "^4.0.0"
166
166
  },
167
167
  "name": "@codyswann/lisa",
168
- "version": "4.33.12",
168
+ "version": "4.33.13",
169
169
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
170
170
  "main": "dist/index.js",
171
171
  "exports": {
@@ -313,7 +313,7 @@
313
313
  "test": "tests"
314
314
  },
315
315
  "types": "./dist/index.d.ts",
316
- "lisaReleaseCommit": "eb21475b4fcf14e7e5f7ded0980aea4f41199447",
317
- "gitHead": "eb21475b4fcf14e7e5f7ded0980aea4f41199447",
318
- "lisaReleaseTag": "v4.33.12"
316
+ "lisaReleaseCommit": "ff18676df240f6a381c4e7f0afed193fef8c0a4f",
317
+ "gitHead": "ff18676df240f6a381c4e7f0afed193fef8c0a4f",
318
+ "lisaReleaseTag": "v4.33.13"
319
319
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
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": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
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.33.12",
3
+ "version": "4.33.13",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"