@codyswann/lisa 4.33.8 → 4.33.10

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 (92) hide show
  1. package/README.md +22 -0
  2. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -1
  3. package/all/copy-overwrite/scripts/lisa-work-item.mjs +361 -10
  4. package/dist/configs/vitest/base.d.ts +76 -11
  5. package/dist/configs/vitest/base.d.ts.map +1 -1
  6. package/dist/configs/vitest/base.js +121 -14
  7. package/dist/configs/vitest/base.js.map +1 -1
  8. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  9. package/dist/core/lisa-owned-hash-ledger.js +12 -0
  10. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  11. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  12. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  13. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  14. package/dist/core/upstream-evidence-manifest.js +12 -9
  15. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  16. package/dist/migrations/ensure-lisa-postinstall.d.ts +26 -15
  17. package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
  18. package/dist/migrations/ensure-lisa-postinstall.js +43 -20
  19. package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
  20. package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -1
  21. package/package.json +4 -4
  22. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  25. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +1 -1
  26. package/plugins/lisa/hooks/parity-safety-net.sh +44 -1
  27. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  28. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +1 -1
  29. package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -1
  30. package/plugins/lisa-agy/plugin.json +1 -1
  31. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  32. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +1 -1
  33. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  36. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +44 -1
  40. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  41. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +1 -1
  42. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -1
  44. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  45. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +1 -1
  46. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-expo-agy/plugin.json +1 -1
  49. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  54. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  59. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  64. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  69. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-rails-agy/plugin.json +1 -1
  74. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  79. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  84. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/src/base/hooks/parity-safety-net.sh +44 -1
  87. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
  88. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +1 -1
  89. package/scripts/install-claude-plugins.sh +22 -4
  90. package/scripts/two-channel-couplings.json +1 -1
  91. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +96 -12
  92. package/typescript/copy-overwrite/scripts/reconcile-nightly-e2e-tracking.mjs +3 -2
@@ -12,26 +12,37 @@ export declare const APPLY_FAILURE_NOTICE: string;
12
12
  /**
13
13
  * The bootstrap invocation chained into a host project's `postinstall`.
14
14
  *
15
- * Shape: **loud but non-fatal**. It used to end in `2>/dev/null || true`, which
16
- * discarded the apply's stderr and its exit code, so a totally failed apply was
17
- * indistinguishable from success and a repo could silently stop receiving
18
- * template updates (CodySwannGT/lisa#2467). Stderr now flows through and a
19
- * failure adds an explicit warning line.
15
+ * Shape: **loud and fatal**. Two earlier shapes each discarded the apply's exit
16
+ * code. `2>/dev/null || true` discarded its stderr too, so a totally failed
17
+ * apply was byte-for-byte indistinguishable from success and a repo could
18
+ * silently stop receiving template updates (CodySwannGT/lisa#2467). Replacing
19
+ * `true` with `echo` recovered the reason but not the fact: `echo` also exits 0,
20
+ * so a failed apply still reported success and the one warning line scrolled
21
+ * away in a wall of install output (CodySwannGT/lisa#3466). The failure branch
22
+ * now prints the notice and exits 1.
20
23
  *
21
- * It deliberately still exits 0. A postinstall runs inside `npm install` /
22
- * `bun install`; exiting non-zero aborts the install and leaves `node_modules`
23
- * half-built. Apply legitimately cannot run in plenty of environments (no git
24
- * repo, no project config, sandboxed or non-interactive shells), so a hard
25
- * failure would convert a template-sync problem into an install outage across
26
- * the fleet. The durable signal instead lives in the apply receipt that a
27
- * successful apply writes — `lisa doctor` reports a repo whose receipt is
28
- * missing or older than the installed Lisa, so the failure is still findable
29
- * long after the install output has scrolled away.
24
+ * Failing the install is a deliberate trade against the previous reasoning,
25
+ * which was that a non-zero postinstall would convert a template-sync problem
26
+ * into an install outage across the fleet. Two things bound that risk. The
27
+ * `[ -n "$CI" ] ||` guard means this branch never executes when `CI` is set, so
28
+ * no CI pipeline can be broken by it and the exposure is local installs only;
29
+ * and `CI=1` remains the escape hatch for anyone who needs to install past a
30
+ * broken apply. Against that, the apply receipt this failed run did NOT write —
31
+ * previously offered as the durable signal — cannot tell `lisa doctor` that an
32
+ * apply *failed*, only that none has succeeded recently, which is equally true
33
+ * of a project that was never applied or was deliberately skipped. The exit code
34
+ * is the only signal that distinguishes them.
35
+ *
36
+ * The cost, stated plainly: {@link composePostinstall} builds
37
+ * `<lisa> && <the host's own postinstall>`, so a failed apply now also skips
38
+ * whatever the host chained after it (`patch-package` and friends). That is the
39
+ * honest outcome — the install genuinely did not complete — but it is a wider
40
+ * change than the exit code alone.
30
41
  *
31
42
  * Exported so tests can execute the real script under `sh` rather than assert
32
43
  * on a string.
33
44
  */
34
- export declare const LISA_INVOCATION = "[ -n \"$CI\" ] || LISA_BOOTSTRAP=1 LISA_POSTINSTALL=1 node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . || echo \"lisa: TEMPLATE APPLY FAILED - this project is NOT receiving Lisa template or guardrail updates. The error is above. Reproduce it with: LISA_POSTINSTALL=1 node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . - then run: node node_modules/@codyswann/lisa/dist/index.js doctor\" >&2";
45
+ export declare const LISA_INVOCATION = "[ -n \"$CI\" ] || LISA_BOOTSTRAP=1 LISA_POSTINSTALL=1 node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . || { echo \"lisa: TEMPLATE APPLY FAILED - this project is NOT receiving Lisa template or guardrail updates. The error is above. Reproduce it with: LISA_POSTINSTALL=1 node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . - then run: node node_modules/@codyswann/lisa/dist/index.js doctor\" >&2; exit 1; }";
35
46
  /**
36
47
  * Migration: ensure Node-based projects chain Lisa into their postinstall script.
37
48
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ensure-lisa-postinstall.d.ts","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAqBlC;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,QAG8C,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,ybAA0H,CAAC;AAuFvJ;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,YAAW,SAAS;IAC9D,QAAQ,CAAC,IAAI,6BAA6B;IAC1C,QAAQ,CAAC,WAAW,qEACgD;IAEpE;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBtD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;CAqC7D"}
1
+ {"version":3,"file":"ensure-lisa-postinstall.d.ts","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAqBlC;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,QAG8C,CAAC;AAYhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,scAAyG,CAAC;AA0FtI;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,YAAW,SAAS;IAC9D,QAAQ,CAAC,IAAI,6BAA6B;IAC1C,QAAQ,CAAC,WAAW,qEACgD;IAEpE;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBtD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;CAqC7D"}
@@ -31,29 +31,49 @@ const LISA_APPLY_COMMAND_AS_POSTINSTALL = `${POSTINSTALL_PREFIX}${LISA_APPLY_COM
31
31
  export const APPLY_FAILURE_NOTICE = "lisa: TEMPLATE APPLY FAILED - this project is NOT receiving Lisa template " +
32
32
  "or guardrail updates. The error is above. Reproduce it with: " +
33
33
  `${LISA_APPLY_COMMAND_AS_POSTINSTALL} - then run: node ${LISA_MARKER} doctor`;
34
+ /**
35
+ * The failure branch: say why, then exit non-zero.
36
+ *
37
+ * `exit 1` rather than a bare `false` because this invocation is routinely
38
+ * chained (`<lisa> && <the host's own postinstall>`); `exit` ends the script at
39
+ * the failure regardless of what a consumer composed around it, so the status
40
+ * cannot be overwritten by a later command in the chain.
41
+ */
42
+ const APPLY_FAILURE_TAIL = ` || { echo "${APPLY_FAILURE_NOTICE}" >&2; exit 1; }`;
34
43
  /**
35
44
  * The bootstrap invocation chained into a host project's `postinstall`.
36
45
  *
37
- * Shape: **loud but non-fatal**. It used to end in `2>/dev/null || true`, which
38
- * discarded the apply's stderr and its exit code, so a totally failed apply was
39
- * indistinguishable from success and a repo could silently stop receiving
40
- * template updates (CodySwannGT/lisa#2467). Stderr now flows through and a
41
- * failure adds an explicit warning line.
46
+ * Shape: **loud and fatal**. Two earlier shapes each discarded the apply's exit
47
+ * code. `2>/dev/null || true` discarded its stderr too, so a totally failed
48
+ * apply was byte-for-byte indistinguishable from success and a repo could
49
+ * silently stop receiving template updates (CodySwannGT/lisa#2467). Replacing
50
+ * `true` with `echo` recovered the reason but not the fact: `echo` also exits 0,
51
+ * so a failed apply still reported success and the one warning line scrolled
52
+ * away in a wall of install output (CodySwannGT/lisa#3466). The failure branch
53
+ * now prints the notice and exits 1.
54
+ *
55
+ * Failing the install is a deliberate trade against the previous reasoning,
56
+ * which was that a non-zero postinstall would convert a template-sync problem
57
+ * into an install outage across the fleet. Two things bound that risk. The
58
+ * `[ -n "$CI" ] ||` guard means this branch never executes when `CI` is set, so
59
+ * no CI pipeline can be broken by it and the exposure is local installs only;
60
+ * and `CI=1` remains the escape hatch for anyone who needs to install past a
61
+ * broken apply. Against that, the apply receipt this failed run did NOT write —
62
+ * previously offered as the durable signal — cannot tell `lisa doctor` that an
63
+ * apply *failed*, only that none has succeeded recently, which is equally true
64
+ * of a project that was never applied or was deliberately skipped. The exit code
65
+ * is the only signal that distinguishes them.
42
66
  *
43
- * It deliberately still exits 0. A postinstall runs inside `npm install` /
44
- * `bun install`; exiting non-zero aborts the install and leaves `node_modules`
45
- * half-built. Apply legitimately cannot run in plenty of environments (no git
46
- * repo, no project config, sandboxed or non-interactive shells), so a hard
47
- * failure would convert a template-sync problem into an install outage across
48
- * the fleet. The durable signal instead lives in the apply receipt that a
49
- * successful apply writes — `lisa doctor` reports a repo whose receipt is
50
- * missing or older than the installed Lisa, so the failure is still findable
51
- * long after the install output has scrolled away.
67
+ * The cost, stated plainly: {@link composePostinstall} builds
68
+ * `<lisa> && <the host's own postinstall>`, so a failed apply now also skips
69
+ * whatever the host chained after it (`patch-package` and friends). That is the
70
+ * honest outcome — the install genuinely did not complete — but it is a wider
71
+ * change than the exit code alone.
52
72
  *
53
73
  * Exported so tests can execute the real script under `sh` rather than assert
54
74
  * on a string.
55
75
  */
56
- export const LISA_INVOCATION = `${CI_GUARD_PREFIX}${BOOTSTRAP_PREFIX}${POSTINSTALL_PREFIX}${LISA_APPLY_COMMAND} || echo "${APPLY_FAILURE_NOTICE}" >&2`;
76
+ export const LISA_INVOCATION = `${CI_GUARD_PREFIX}${BOOTSTRAP_PREFIX}${POSTINSTALL_PREFIX}${LISA_APPLY_COMMAND}${APPLY_FAILURE_TAIL}`;
57
77
  /**
58
78
  * Project types that do not use Node.js postinstall hooks (e.g. Rails).
59
79
  * Projects detected as only these types are skipped by this migration.
@@ -72,10 +92,11 @@ async function readPackageJson(projectDir) {
72
92
  * replaces it rather than chaining a second copy in front of it.
73
93
  *
74
94
  * The optional tail covers, in order: the failure-swallowing legacy form
75
- * (`2>/dev/null || true`), the current loud-but-non-fatal form
76
- * (`|| echo "..." >&2`), and a bare invocation with no tail at all. Guard
77
- * prefixes are optional and repeatable because older Lisa versions introduced
78
- * them one at a time — `LISA_POSTINSTALL=1` being the newest
95
+ * (`2>/dev/null || true`), the loud-but-still-exit-0 form
96
+ * (`|| echo "..." >&2`, CodySwannGT/lisa#3466), the current loud-and-fatal form
97
+ * (`|| { echo "..." >&2; exit 1; }`), and a bare invocation with no tail at
98
+ * all. Guard prefixes are optional and repeatable because older Lisa versions
99
+ * introduced them one at a time — `LISA_POSTINSTALL=1` being the newest
79
100
  * (CodySwannGT/lisa#3066).
80
101
  *
81
102
  * **Every alternative here is permanent.** There is no cutover date: this text
@@ -89,7 +110,9 @@ async function readPackageJson(projectDir) {
89
110
  */
90
111
  const LISA_INVOCATION_RE = new RegExp('(?:(?:\\[ -n "\\$CI" \\] \\|\\| )|(?:LISA_BOOTSTRAP=1 )|(?:LISA_POSTINSTALL=1 ))*' +
91
112
  "node node_modules/@codyswann/lisa/dist/index\\.js --yes --skip-git-check \\." +
92
- '(?: 2>/dev/null \\|\\| true| \\|\\| echo "[^"]*" >&2)?');
113
+ "(?: 2>/dev/null \\|\\| true" +
114
+ '| \\|\\| \\{ echo "[^"]*" >&2; exit 1; \\}' +
115
+ '| \\|\\| echo "[^"]*" >&2)?');
93
116
  /**
94
117
  * Compose the new postinstall, prepending the Lisa invocation to any existing command.
95
118
  * If the existing script already contains a legacy Lisa invocation (no CI guard),
@@ -1 +1 @@
1
- {"version":3,"file":"ensure-lisa-postinstall.js","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ensure-lisa-postinstall.js","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"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"}
@@ -1687,7 +1687,50 @@ fi
1687
1687
 
1688
1688
  # 13. Dropping or truncating a database / schema / table (Lisa-only guard —
1689
1689
  # upstream has no SQL protection; keep).
1690
- if matches '\b(drop[[:space:]]+(database|schema|table)|truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+)\b'; then
1690
+ #
1691
+ # The two arms used to be asymmetric, and only the lopsided one produced false
1692
+ # positives (issue #3530). The `drop` arm requires a following keyword —
1693
+ # `drop database|schema|table` — so `drop the ball` never matched it. The
1694
+ # `truncate` arm made that keyword OPTIONAL:
1695
+ #
1696
+ # truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+
1697
+ #
1698
+ # which reduces to "`truncate`, whitespace, any identifier-ish word", so
1699
+ # `truncate the board`, `truncate results` and `truncate output` all matched.
1700
+ # A session was refused for a bug-report TITLE describing a connection that
1701
+ # truncates results — and a title has no escape hatch, because the remedy this
1702
+ # hook prints at the wall (write the text to a file, pass it BY PATH) covers a
1703
+ # body, while the title flag of an issue-filing CLI is necessarily inline. The
1704
+ # only exit left was to reword, which is what trains people to route around a
1705
+ # guard.
1706
+ #
1707
+ # The fix keeps the coverage and drops the English, in three arms.
1708
+ #
1709
+ # ARM 1 — `drop`, byte-identical to before. It was already correct, and a
1710
+ # rewrite that unified the arms could only make it permissive.
1711
+ readonly SQL_DROP='\bdrop[[:space:]]+(database|schema|table)\b'
1712
+ # ARM 2 — `truncate` carrying its own evidence that it is a statement rather
1713
+ # than a verb: the TABLE keyword (what the drop arm demands), a
1714
+ # schema-qualified name, a quoted identifier, a trailing `;`, or one of the
1715
+ # tail keywords that only ever follow a TRUNCATE. English has none of these.
1716
+ readonly SQL_TRUNCATE_TAIL='(cascade|restrict|restart[[:space:]]+identity|continue[[:space:]]+identity)([^[:alnum:]_]|$)'
1717
+ readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum:]_."`]+|(only[[:space:]]+)?([[:alnum:]_]+\.[[:alnum:]_."`]+|["`][[:alnum:]_. ]+["`]|[[:alnum:]_]+[[:space:]]*(;|'"$SQL_TRUNCATE_TAIL"')))'
1718
+ # ARM 3 — the permissive-dialect `TRUNCATE <name>` with none of that evidence.
1719
+ # Deleting this shape is what a lazy fix does: it passes every prose scenario
1720
+ # while silently removing the coverage a regression fixture already asserts
1721
+ # (`mysql -e 'TRUNCATE sessions'`). So it is recognized by CONTEXT instead —
1722
+ # the same command must also invoke a command-line database client. Prose in a
1723
+ # tracker filing or a commit message invokes none, which is exactly the
1724
+ # difference the text scan could not see on its own.
1725
+ readonly SQL_CLIENT='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]|$)'
1726
+ readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
1727
+ # Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
1728
+ # no qualification and no recognized client in the same command goes unmatched
1729
+ # — the same trade the `drop` arm has always made for `drop the ball`. No DROP
1730
+ # shape is weakened, and the guard still fails in the safe direction.
1731
+ if matches "$SQL_DROP" \
1732
+ || matches "$SQL_TRUNCATE_EVIDENCE" \
1733
+ || { matches "$SQL_CLIENT" && matches "$SQL_TRUNCATE_BARE"; }; then
1691
1734
  block "destructive SQL (DROP/TRUNCATE) detected"
1692
1735
  fi
1693
1736
 
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.8",
168
+ "version": "4.33.10",
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": "463ea1625bf43084967f87bee3daa9ab4225be85",
317
- "gitHead": "463ea1625bf43084967f87bee3daa9ab4225be85",
318
- "lisaReleaseTag": "v4.33.8"
316
+ "lisaReleaseCommit": "74fcfd1ce7a337cd684e03739b6dd8bef59094d2",
317
+ "gitHead": "74fcfd1ce7a337cd684e03739b6dd8bef59094d2",
318
+ "lisaReleaseTag": "v4.33.10"
319
319
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -652,7 +652,7 @@ result: **27 of 27** open items carrying the claimed role in this repository had
652
652
  a merged pull request — the claimed lane reported 27 things in flight when the
653
653
  real number was one.
654
654
 
655
- Three properties make the command safe to run unconditionally:
655
+ Four properties make the command safe to run unconditionally:
656
656
 
657
657
  1. **The terminal role is RESOLVED, never assumed.** `lifecycleContract` reads
658
658
  `github.labels.build.done` (or the Jira/Linear equivalent), which is
@@ -666,7 +666,22 @@ Three properties make the command safe to run unconditionally:
666
666
  a real one afterwards. Cross-repository references do not count: a downstream
667
667
  consumer's PR mentioning an upstream issue is not evidence the upstream issue
668
668
  shipped.
669
- 3. **It is idempotent**, so re-running after a retry converges rather than
669
+ 3. **It weighs the merged pull request's BASE BRANCH**, the same way step (c)
670
+ above does, and for the same reason. A merged pull request is not evidence
671
+ on its own: the writer resolves `<baseRefName>` through `.lisa.config.json`
672
+ `deploy.branches` and applies only the role that base earned. A merge into
673
+ the production deploy branch is terminal and closes the item; a merge into a
674
+ branch mapping to a lower env records that env's role and leaves the item
675
+ open; a merge into a branch the project does not deploy at all — an
676
+ integration/stack branch — records nothing, closes nothing, and reports the
677
+ base it observed. **Measured**: on a stacked-PR queue the previous writer
678
+ stamped the production terminal role and closed every ticket in the batch at
679
+ the moment its PR landed on the stacking branch, and one of those false
680
+ completions then made push gate 1 refuse a follow-up commit on the same
681
+ ticket, because there was no longer an open work item to bind. When the
682
+ command reports `work-item NOT completed`, that is the correct answer for a
683
+ stacked merge — re-run it after the stack reaches the deploy branch.
684
+ 4. **It is idempotent**, so re-running after a retry converges rather than
670
685
  accumulating.
671
686
 
672
687
  It also removes the claimed role rather than only adding the terminal one.
@@ -539,7 +539,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
539
539
 
540
540
  The ordering is the point. Announcing before verifying leaves a public "Build complete" on an item whose lifecycle state was never confirmed, and a reader has no way to tell that claim apart from a verified one — the same indistinguishability the readback exists to prevent. On a failed readback, comment the Error instead.
541
541
 
542
- `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. Use the manual commands above for intermediate env transitions, which must not close the issue.
542
+ `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. It also decides for itself whether `$DONE` is terminal here: it resolves each merged PR's base branch through `.lisa.config.json` `deploy.branches`, so a merge into an intermediate env branch records that env's role WITHOUT closing the issue, and a merge into a branch the project does not deploy records nothing and says which base it saw. The manual commands above remain available for an intermediate env transition performed without merge evidence.
543
543
 
544
544
  For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever the lifecycle left it) — humans take it from there.
545
545
 
@@ -1687,7 +1687,50 @@ fi
1687
1687
 
1688
1688
  # 13. Dropping or truncating a database / schema / table (Lisa-only guard —
1689
1689
  # upstream has no SQL protection; keep).
1690
- if matches '\b(drop[[:space:]]+(database|schema|table)|truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+)\b'; then
1690
+ #
1691
+ # The two arms used to be asymmetric, and only the lopsided one produced false
1692
+ # positives (issue #3530). The `drop` arm requires a following keyword —
1693
+ # `drop database|schema|table` — so `drop the ball` never matched it. The
1694
+ # `truncate` arm made that keyword OPTIONAL:
1695
+ #
1696
+ # truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+
1697
+ #
1698
+ # which reduces to "`truncate`, whitespace, any identifier-ish word", so
1699
+ # `truncate the board`, `truncate results` and `truncate output` all matched.
1700
+ # A session was refused for a bug-report TITLE describing a connection that
1701
+ # truncates results — and a title has no escape hatch, because the remedy this
1702
+ # hook prints at the wall (write the text to a file, pass it BY PATH) covers a
1703
+ # body, while the title flag of an issue-filing CLI is necessarily inline. The
1704
+ # only exit left was to reword, which is what trains people to route around a
1705
+ # guard.
1706
+ #
1707
+ # The fix keeps the coverage and drops the English, in three arms.
1708
+ #
1709
+ # ARM 1 — `drop`, byte-identical to before. It was already correct, and a
1710
+ # rewrite that unified the arms could only make it permissive.
1711
+ readonly SQL_DROP='\bdrop[[:space:]]+(database|schema|table)\b'
1712
+ # ARM 2 — `truncate` carrying its own evidence that it is a statement rather
1713
+ # than a verb: the TABLE keyword (what the drop arm demands), a
1714
+ # schema-qualified name, a quoted identifier, a trailing `;`, or one of the
1715
+ # tail keywords that only ever follow a TRUNCATE. English has none of these.
1716
+ readonly SQL_TRUNCATE_TAIL='(cascade|restrict|restart[[:space:]]+identity|continue[[:space:]]+identity)([^[:alnum:]_]|$)'
1717
+ readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum:]_."`]+|(only[[:space:]]+)?([[:alnum:]_]+\.[[:alnum:]_."`]+|["`][[:alnum:]_. ]+["`]|[[:alnum:]_]+[[:space:]]*(;|'"$SQL_TRUNCATE_TAIL"')))'
1718
+ # ARM 3 — the permissive-dialect `TRUNCATE <name>` with none of that evidence.
1719
+ # Deleting this shape is what a lazy fix does: it passes every prose scenario
1720
+ # while silently removing the coverage a regression fixture already asserts
1721
+ # (`mysql -e 'TRUNCATE sessions'`). So it is recognized by CONTEXT instead —
1722
+ # the same command must also invoke a command-line database client. Prose in a
1723
+ # tracker filing or a commit message invokes none, which is exactly the
1724
+ # difference the text scan could not see on its own.
1725
+ readonly SQL_CLIENT='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]|$)'
1726
+ readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
1727
+ # Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
1728
+ # no qualification and no recognized client in the same command goes unmatched
1729
+ # — the same trade the `drop` arm has always made for `drop the ball`. No DROP
1730
+ # shape is weakened, and the guard still fails in the safe direction.
1731
+ if matches "$SQL_DROP" \
1732
+ || matches "$SQL_TRUNCATE_EVIDENCE" \
1733
+ || { matches "$SQL_CLIENT" && matches "$SQL_TRUNCATE_BARE"; }; then
1691
1734
  block "destructive SQL (DROP/TRUNCATE) detected"
1692
1735
  fi
1693
1736
 
@@ -652,7 +652,7 @@ result: **27 of 27** open items carrying the claimed role in this repository had
652
652
  a merged pull request — the claimed lane reported 27 things in flight when the
653
653
  real number was one.
654
654
 
655
- Three properties make the command safe to run unconditionally:
655
+ Four properties make the command safe to run unconditionally:
656
656
 
657
657
  1. **The terminal role is RESOLVED, never assumed.** `lifecycleContract` reads
658
658
  `github.labels.build.done` (or the Jira/Linear equivalent), which is
@@ -666,7 +666,22 @@ Three properties make the command safe to run unconditionally:
666
666
  a real one afterwards. Cross-repository references do not count: a downstream
667
667
  consumer's PR mentioning an upstream issue is not evidence the upstream issue
668
668
  shipped.
669
- 3. **It is idempotent**, so re-running after a retry converges rather than
669
+ 3. **It weighs the merged pull request's BASE BRANCH**, the same way step (c)
670
+ above does, and for the same reason. A merged pull request is not evidence
671
+ on its own: the writer resolves `<baseRefName>` through `.lisa.config.json`
672
+ `deploy.branches` and applies only the role that base earned. A merge into
673
+ the production deploy branch is terminal and closes the item; a merge into a
674
+ branch mapping to a lower env records that env's role and leaves the item
675
+ open; a merge into a branch the project does not deploy at all — an
676
+ integration/stack branch — records nothing, closes nothing, and reports the
677
+ base it observed. **Measured**: on a stacked-PR queue the previous writer
678
+ stamped the production terminal role and closed every ticket in the batch at
679
+ the moment its PR landed on the stacking branch, and one of those false
680
+ completions then made push gate 1 refuse a follow-up commit on the same
681
+ ticket, because there was no longer an open work item to bind. When the
682
+ command reports `work-item NOT completed`, that is the correct answer for a
683
+ stacked merge — re-run it after the stack reaches the deploy branch.
684
+ 4. **It is idempotent**, so re-running after a retry converges rather than
670
685
  accumulating.
671
686
 
672
687
  It also removes the claimed role rather than only adding the terminal one.
@@ -539,7 +539,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
539
539
 
540
540
  The ordering is the point. Announcing before verifying leaves a public "Build complete" on an item whose lifecycle state was never confirmed, and a reader has no way to tell that claim apart from a verified one — the same indistinguishability the readback exists to prevent. On a failed readback, comment the Error instead.
541
541
 
542
- `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. Use the manual commands above for intermediate env transitions, which must not close the issue.
542
+ `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. It also decides for itself whether `$DONE` is terminal here: it resolves each merged PR's base branch through `.lisa.config.json` `deploy.branches`, so a merge into an intermediate env branch records that env's role WITHOUT closing the issue, and a merge into a branch the project does not deploy records nothing and says which base it saw. The manual commands above remain available for an intermediate env transition performed without merge evidence.
543
543
 
544
544
  For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever the lifecycle left it) — humans take it from there.
545
545
 
@@ -1687,7 +1687,50 @@ fi
1687
1687
 
1688
1688
  # 13. Dropping or truncating a database / schema / table (Lisa-only guard —
1689
1689
  # upstream has no SQL protection; keep).
1690
- if matches '\b(drop[[:space:]]+(database|schema|table)|truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+)\b'; then
1690
+ #
1691
+ # The two arms used to be asymmetric, and only the lopsided one produced false
1692
+ # positives (issue #3530). The `drop` arm requires a following keyword —
1693
+ # `drop database|schema|table` — so `drop the ball` never matched it. The
1694
+ # `truncate` arm made that keyword OPTIONAL:
1695
+ #
1696
+ # truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+
1697
+ #
1698
+ # which reduces to "`truncate`, whitespace, any identifier-ish word", so
1699
+ # `truncate the board`, `truncate results` and `truncate output` all matched.
1700
+ # A session was refused for a bug-report TITLE describing a connection that
1701
+ # truncates results — and a title has no escape hatch, because the remedy this
1702
+ # hook prints at the wall (write the text to a file, pass it BY PATH) covers a
1703
+ # body, while the title flag of an issue-filing CLI is necessarily inline. The
1704
+ # only exit left was to reword, which is what trains people to route around a
1705
+ # guard.
1706
+ #
1707
+ # The fix keeps the coverage and drops the English, in three arms.
1708
+ #
1709
+ # ARM 1 — `drop`, byte-identical to before. It was already correct, and a
1710
+ # rewrite that unified the arms could only make it permissive.
1711
+ readonly SQL_DROP='\bdrop[[:space:]]+(database|schema|table)\b'
1712
+ # ARM 2 — `truncate` carrying its own evidence that it is a statement rather
1713
+ # than a verb: the TABLE keyword (what the drop arm demands), a
1714
+ # schema-qualified name, a quoted identifier, a trailing `;`, or one of the
1715
+ # tail keywords that only ever follow a TRUNCATE. English has none of these.
1716
+ readonly SQL_TRUNCATE_TAIL='(cascade|restrict|restart[[:space:]]+identity|continue[[:space:]]+identity)([^[:alnum:]_]|$)'
1717
+ readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum:]_."`]+|(only[[:space:]]+)?([[:alnum:]_]+\.[[:alnum:]_."`]+|["`][[:alnum:]_. ]+["`]|[[:alnum:]_]+[[:space:]]*(;|'"$SQL_TRUNCATE_TAIL"')))'
1718
+ # ARM 3 — the permissive-dialect `TRUNCATE <name>` with none of that evidence.
1719
+ # Deleting this shape is what a lazy fix does: it passes every prose scenario
1720
+ # while silently removing the coverage a regression fixture already asserts
1721
+ # (`mysql -e 'TRUNCATE sessions'`). So it is recognized by CONTEXT instead —
1722
+ # the same command must also invoke a command-line database client. Prose in a
1723
+ # tracker filing or a commit message invokes none, which is exactly the
1724
+ # difference the text scan could not see on its own.
1725
+ readonly SQL_CLIENT='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]|$)'
1726
+ readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
1727
+ # Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
1728
+ # no qualification and no recognized client in the same command goes unmatched
1729
+ # — the same trade the `drop` arm has always made for `drop the ball`. No DROP
1730
+ # shape is weakened, and the guard still fails in the safe direction.
1731
+ if matches "$SQL_DROP" \
1732
+ || matches "$SQL_TRUNCATE_EVIDENCE" \
1733
+ || { matches "$SQL_CLIENT" && matches "$SQL_TRUNCATE_BARE"; }; then
1691
1734
  block "destructive SQL (DROP/TRUNCATE) detected"
1692
1735
  fi
1693
1736
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.8",
3
+ "version": "4.33.10",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -652,7 +652,7 @@ result: **27 of 27** open items carrying the claimed role in this repository had
652
652
  a merged pull request — the claimed lane reported 27 things in flight when the
653
653
  real number was one.
654
654
 
655
- Three properties make the command safe to run unconditionally:
655
+ Four properties make the command safe to run unconditionally:
656
656
 
657
657
  1. **The terminal role is RESOLVED, never assumed.** `lifecycleContract` reads
658
658
  `github.labels.build.done` (or the Jira/Linear equivalent), which is
@@ -666,7 +666,22 @@ Three properties make the command safe to run unconditionally:
666
666
  a real one afterwards. Cross-repository references do not count: a downstream
667
667
  consumer's PR mentioning an upstream issue is not evidence the upstream issue
668
668
  shipped.
669
- 3. **It is idempotent**, so re-running after a retry converges rather than
669
+ 3. **It weighs the merged pull request's BASE BRANCH**, the same way step (c)
670
+ above does, and for the same reason. A merged pull request is not evidence
671
+ on its own: the writer resolves `<baseRefName>` through `.lisa.config.json`
672
+ `deploy.branches` and applies only the role that base earned. A merge into
673
+ the production deploy branch is terminal and closes the item; a merge into a
674
+ branch mapping to a lower env records that env's role and leaves the item
675
+ open; a merge into a branch the project does not deploy at all — an
676
+ integration/stack branch — records nothing, closes nothing, and reports the
677
+ base it observed. **Measured**: on a stacked-PR queue the previous writer
678
+ stamped the production terminal role and closed every ticket in the batch at
679
+ the moment its PR landed on the stacking branch, and one of those false
680
+ completions then made push gate 1 refuse a follow-up commit on the same
681
+ ticket, because there was no longer an open work item to bind. When the
682
+ command reports `work-item NOT completed`, that is the correct answer for a
683
+ stacked merge — re-run it after the stack reaches the deploy branch.
684
+ 4. **It is idempotent**, so re-running after a retry converges rather than
670
685
  accumulating.
671
686
 
672
687
  It also removes the claimed role rather than only adding the terminal one.
@@ -539,7 +539,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
539
539
 
540
540
  The ordering is the point. Announcing before verifying leaves a public "Build complete" on an item whose lifecycle state was never confirmed, and a reader has no way to tell that claim apart from a verified one — the same indistinguishability the readback exists to prevent. On a failed readback, comment the Error instead.
541
541
 
542
- `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. Use the manual commands above for intermediate env transitions, which must not close the issue.
542
+ `node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. It also decides for itself whether `$DONE` is terminal here: it resolves each merged PR's base branch through `.lisa.config.json` `deploy.branches`, so a merge into an intermediate env branch records that env's role WITHOUT closing the issue, and a merge into a branch the project does not deploy records nothing and says which base it saw. The manual commands above remain available for an intermediate env transition performed without merge evidence.
543
543
 
544
544
  For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever the lifecycle left it) — humans take it from there.
545
545
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
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.8",
3
+ "version": "4.33.10",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"