@codyswann/lisa 4.12.0 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +7 -1
  2. package/dist/cli/doctor-two-channel-drift.d.ts +20 -0
  3. package/dist/cli/doctor-two-channel-drift.d.ts.map +1 -0
  4. package/dist/cli/doctor-two-channel-drift.js +190 -0
  5. package/dist/cli/doctor-two-channel-drift.js.map +1 -0
  6. package/dist/cli/doctor-wiki.d.ts +8 -0
  7. package/dist/cli/doctor-wiki.d.ts.map +1 -0
  8. package/dist/cli/doctor-wiki.js +42 -0
  9. package/dist/cli/doctor-wiki.js.map +1 -0
  10. package/dist/cli/doctor.d.ts.map +1 -1
  11. package/dist/cli/doctor.js +7 -30
  12. package/dist/cli/doctor.js.map +1 -1
  13. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  14. package/dist/core/lisa-owned-hash-ledger.js +3 -0
  15. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  16. package/dist/core/two-channel-delivery-scan.d.ts +81 -0
  17. package/dist/core/two-channel-delivery-scan.d.ts.map +1 -0
  18. package/dist/core/two-channel-delivery-scan.js +191 -0
  19. package/dist/core/two-channel-delivery-scan.js.map +1 -0
  20. package/dist/core/two-channel-delivery.d.ts +215 -0
  21. package/dist/core/two-channel-delivery.d.ts.map +1 -0
  22. package/dist/core/two-channel-delivery.js +259 -0
  23. package/dist/core/two-channel-delivery.js.map +1 -0
  24. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  25. package/dist/core/upstream-evidence-manifest.js +18 -5
  26. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  27. package/dist/opencode/plugin-templates/parity-safety-net.sh +7 -1
  28. package/package.json +4 -2
  29. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa/hooks/parity-safety-net.sh +7 -1
  32. package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -1
  33. package/plugins/lisa/rules/reference/falsifiable-checks.md +1 -1
  34. package/plugins/lisa-agy/hooks/parity-safety-net.sh +7 -1
  35. package/plugins/lisa-agy/plugin.json +1 -1
  36. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  39. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +7 -1
  43. package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -1
  44. package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +1 -1
  45. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +7 -1
  47. package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +1 -1
  48. package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -1
  49. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-expo-agy/plugin.json +1 -1
  52. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  62. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  67. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  72. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-rails-agy/plugin.json +1 -1
  77. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  82. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  87. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/src/base/hooks/parity-safety-net.sh +7 -1
  90. package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -1
  91. package/plugins/src/base/rules/reference/falsifiable-checks.md +1 -1
  92. package/scripts/generate-two-channel-couplings.ts +427 -0
  93. package/scripts/two-channel-couplings.json +214 -0
  94. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +19 -20
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
752
752
  # arm A). Unquoted and run-leading invocations get the whole statement back,
753
753
  # so this is a no-op for every real delete.
754
754
  rm_assignment_stmt="$rm_stmt"
755
- rm_walk_text="$(rm_scan_scope "$rm_stmt")"
755
+ rm_walk_status=0
756
+ rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
757
+ if [ "$rm_walk_status" -ne 0 ]; then
758
+ # The substitution above runs in a subshell. A denial inside it cannot stop
759
+ # this parent hook, so carry the failure out explicitly and deny here.
760
+ scan_failed "$rm_walk_status"
761
+ fi
756
762
  while IFS= read -r rm_scope; do
757
763
  set -f
758
764
  seen_rm=0
package/package.json CHANGED
@@ -43,7 +43,9 @@
43
43
  "check:upstream-evidence-manifest": "node scripts/generate-upstream-evidence-manifest.mjs --check",
44
44
  "build:lisa-owned-hash-ledger": "node scripts/generate-lisa-owned-hash-ledger.mjs",
45
45
  "check:lisa-owned-hash-ledger": "node scripts/generate-lisa-owned-hash-ledger.mjs --check",
46
- "check:artifacts": "status=0; bun run check:upstream-evidence-manifest || status=1; bun run check:lisa-owned-hash-ledger || status=1; exit $status",
46
+ "generate:two-channel-couplings": "bun scripts/generate-two-channel-couplings.ts",
47
+ "check:two-channel-couplings": "bun scripts/generate-two-channel-couplings.ts --check",
48
+ "check:artifacts": "status=0; bun run check:upstream-evidence-manifest || status=1; bun run check:lisa-owned-hash-ledger || status=1; bun run check:two-channel-couplings || status=1; exit $status",
47
49
  "refresh:upstream-public-commits": "node scripts/generate-upstream-evidence-manifest.mjs --refresh-public-commits",
48
50
  "check:rules-pairing": "bash scripts/check-rules-pairing.sh",
49
51
  "check:pipeline-status-reads": "node scripts/check-pipeline-status-reads.mjs",
@@ -150,7 +152,7 @@
150
152
  "zod-validation-error": "^4.0.0"
151
153
  },
152
154
  "name": "@codyswann/lisa",
153
- "version": "4.12.0",
155
+ "version": "4.13.1",
154
156
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
155
157
  "main": "dist/index.js",
156
158
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
752
752
  # arm A). Unquoted and run-leading invocations get the whole statement back,
753
753
  # so this is a no-op for every real delete.
754
754
  rm_assignment_stmt="$rm_stmt"
755
- rm_walk_text="$(rm_scan_scope "$rm_stmt")"
755
+ rm_walk_status=0
756
+ rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
757
+ if [ "$rm_walk_status" -ne 0 ]; then
758
+ # The substitution above runs in a subshell. A denial inside it cannot stop
759
+ # this parent hook, so carry the failure out explicitly and deny here.
760
+ scan_failed "$rm_walk_status"
761
+ fi
756
762
  while IFS= read -r rm_scope; do
757
763
  set -f
758
764
  seen_rm=0
@@ -34,7 +34,7 @@ Each has been observed in real runs; each reported success while asserting nothi
34
34
  This form is preferred over the alternatives because it is the only one that both survives `sh` and keeps the full output, which is what you need the moment the status says the gate failed. `set -o pipefail` is correct where it exists but is **not POSIX `sh`** — dash has no such option, and this repository runs hooks under `sh`. `${PIPESTATUS[0]}` is **bash-only** (zsh spells it `$pipestatus[1]`, 1-indexed) and is overwritten by the very next command, so it must be read immediately. Never report a gate as green from a status read through `| tail`, `| head`, or `| tee`.
35
35
  - **Falsify before reporting.** No clean result is reportable until the check has been shown to fail on a deliberate break. **"Mentally reverting" does not count** — reasoning that the assertion *would* fail is precisely the step that lets a non-functional guard ship, because the author already believes it is load-bearing. Run the break.
36
36
  - **Mutation-prove the guard, then read the cardinality.** Neuter the protection **in production code**, run the **whole suite**, count the failures and **read their names**, then revert. **Zero** ⇒ the guard is inert (one pinning a single field shipped with all 50 tests green while the regression walked through). **Many, unrelated** ⇒ over-broad; its next failure will not name the cause. **Exactly one, or several all named for the same regression** ⇒ load-bearing and correctly scoped. A **zero is robust to contention** — load only ever adds failures — so only the non-zero counts need a quiet machine.
37
- - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JS/TS family and nothing else — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
37
+ - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JavaScript and TypeScript family plus Vue, HTML and Svelte — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
38
38
  - **Never scope the probe by filename, and never infer absent coverage from a grep.** Suites split at the line cap, so a protection's tests often sit in a sibling file: a filename-scoped probe reported cardinality 0 on a live security fix that a whole-suite run showed failing 4 tests, every one named for it.
39
39
  - **Assert that no copy is wrong, not that some copy is right.** An existential assertion over duplicated content is satisfied by the correct copy and cannot see the broken one — rewriting one suite's assertions to universal negatives took the same mutation from **0 to 18** failures. Enumerate the property (*every* marker uses an em-dash), never the one known-bad spelling.
40
40
  - **Reconstruct a red state; never narrate one you did not run.** Evidence describing a failing or dirty state must come from a run you actually observed — an evidence file once claimed "working tree clean" while listing two untracked scripts in the same file. To observe a state that no longer exists, check that commit out in a **throwaway detached-HEAD worktree** and run it there. Writing down what the output would have said is fabrication, however confident the reasoning.
@@ -170,7 +170,7 @@ Practical consequence: an inert-guard finding is safe to report from a busy mach
170
170
 
171
171
  Everything above is a *manual* probe, and it is language-agnostic: neuter a shell guard, run the suite, read the names, and the yardstick works exactly as it does for TypeScript.
172
172
 
173
- The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JS/TS family, plus HTML and Svelte, which delegate to it. There is **no shell parser**. So:
173
+ The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JavaScript and TypeScript family, plus Vue, HTML and Svelte, which delegate to it. There is **no shell parser**. So:
174
174
 
175
175
  - A guard whose logic lives in `.sh` produces **no mutants under any `mutate` list**. It is outside the gate by construction, not by a selection decision, and widening the patterns cannot bring it in.
176
176
  - Adding a `.sh` path to `mutate` does not measure it and does not quietly no-op. Stryker throws out of instrumentation — `Unable to parse …. No parser registered for .sh!` — and the run aborts before a single mutant is tried, so **one such entry destroys the score of every other guard in the list.** Measured, not inferred.
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
752
752
  # arm A). Unquoted and run-leading invocations get the whole statement back,
753
753
  # so this is a no-op for every real delete.
754
754
  rm_assignment_stmt="$rm_stmt"
755
- rm_walk_text="$(rm_scan_scope "$rm_stmt")"
755
+ rm_walk_status=0
756
+ rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
757
+ if [ "$rm_walk_status" -ne 0 ]; then
758
+ # The substitution above runs in a subshell. A denial inside it cannot stop
759
+ # this parent hook, so carry the failure out explicitly and deny here.
760
+ scan_failed "$rm_walk_status"
761
+ fi
756
762
  while IFS= read -r rm_scope; do
757
763
  set -f
758
764
  seen_rm=0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
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.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
752
752
  # arm A). Unquoted and run-leading invocations get the whole statement back,
753
753
  # so this is a no-op for every real delete.
754
754
  rm_assignment_stmt="$rm_stmt"
755
- rm_walk_text="$(rm_scan_scope "$rm_stmt")"
755
+ rm_walk_status=0
756
+ rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
757
+ if [ "$rm_walk_status" -ne 0 ]; then
758
+ # The substitution above runs in a subshell. A denial inside it cannot stop
759
+ # this parent hook, so carry the failure out explicitly and deny here.
760
+ scan_failed "$rm_walk_status"
761
+ fi
756
762
  while IFS= read -r rm_scope; do
757
763
  set -f
758
764
  seen_rm=0
@@ -34,7 +34,7 @@ Each has been observed in real runs; each reported success while asserting nothi
34
34
  This form is preferred over the alternatives because it is the only one that both survives `sh` and keeps the full output, which is what you need the moment the status says the gate failed. `set -o pipefail` is correct where it exists but is **not POSIX `sh`** — dash has no such option, and this repository runs hooks under `sh`. `${PIPESTATUS[0]}` is **bash-only** (zsh spells it `$pipestatus[1]`, 1-indexed) and is overwritten by the very next command, so it must be read immediately. Never report a gate as green from a status read through `| tail`, `| head`, or `| tee`.
35
35
  - **Falsify before reporting.** No clean result is reportable until the check has been shown to fail on a deliberate break. **"Mentally reverting" does not count** — reasoning that the assertion *would* fail is precisely the step that lets a non-functional guard ship, because the author already believes it is load-bearing. Run the break.
36
36
  - **Mutation-prove the guard, then read the cardinality.** Neuter the protection **in production code**, run the **whole suite**, count the failures and **read their names**, then revert. **Zero** ⇒ the guard is inert (one pinning a single field shipped with all 50 tests green while the regression walked through). **Many, unrelated** ⇒ over-broad; its next failure will not name the cause. **Exactly one, or several all named for the same regression** ⇒ load-bearing and correctly scoped. A **zero is robust to contention** — load only ever adds failures — so only the non-zero counts need a quiet machine.
37
- - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JS/TS family and nothing else — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
37
+ - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JavaScript and TypeScript family plus Vue, HTML and Svelte — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
38
38
  - **Never scope the probe by filename, and never infer absent coverage from a grep.** Suites split at the line cap, so a protection's tests often sit in a sibling file: a filename-scoped probe reported cardinality 0 on a live security fix that a whole-suite run showed failing 4 tests, every one named for it.
39
39
  - **Assert that no copy is wrong, not that some copy is right.** An existential assertion over duplicated content is satisfied by the correct copy and cannot see the broken one — rewriting one suite's assertions to universal negatives took the same mutation from **0 to 18** failures. Enumerate the property (*every* marker uses an em-dash), never the one known-bad spelling.
40
40
  - **Reconstruct a red state; never narrate one you did not run.** Evidence describing a failing or dirty state must come from a run you actually observed — an evidence file once claimed "working tree clean" while listing two untracked scripts in the same file. To observe a state that no longer exists, check that commit out in a **throwaway detached-HEAD worktree** and run it there. Writing down what the output would have said is fabrication, however confident the reasoning.
@@ -170,7 +170,7 @@ Practical consequence: an inert-guard finding is safe to report from a busy mach
170
170
 
171
171
  Everything above is a *manual* probe, and it is language-agnostic: neuter a shell guard, run the suite, read the names, and the yardstick works exactly as it does for TypeScript.
172
172
 
173
- The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JS/TS family, plus HTML and Svelte, which delegate to it. There is **no shell parser**. So:
173
+ The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JavaScript and TypeScript family, plus Vue, HTML and Svelte, which delegate to it. There is **no shell parser**. So:
174
174
 
175
175
  - A guard whose logic lives in `.sh` produces **no mutants under any `mutate` list**. It is outside the gate by construction, not by a selection decision, and widening the patterns cannot bring it in.
176
176
  - Adding a `.sh` path to `mutate` does not measure it and does not quietly no-op. Stryker throws out of instrumentation — `Unable to parse …. No parser registered for .sh!` — and the run aborts before a single mutant is tried, so **one such entry destroys the score of every other guard in the list.** Measured, not inferred.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -752,7 +752,13 @@ while IFS= read -r rm_stmt; do
752
752
  # arm A). Unquoted and run-leading invocations get the whole statement back,
753
753
  # so this is a no-op for every real delete.
754
754
  rm_assignment_stmt="$rm_stmt"
755
- rm_walk_text="$(rm_scan_scope "$rm_stmt")"
755
+ rm_walk_status=0
756
+ rm_walk_text="$(rm_scan_scope "$rm_stmt")" || rm_walk_status=$?
757
+ if [ "$rm_walk_status" -ne 0 ]; then
758
+ # The substitution above runs in a subshell. A denial inside it cannot stop
759
+ # this parent hook, so carry the failure out explicitly and deny here.
760
+ scan_failed "$rm_walk_status"
761
+ fi
756
762
  while IFS= read -r rm_scope; do
757
763
  set -f
758
764
  seen_rm=0
@@ -175,7 +175,7 @@ Practical consequence: an inert-guard finding is safe to report from a busy mach
175
175
 
176
176
  Everything above is a *manual* probe, and it is language-agnostic: neuter a shell guard, run the suite, read the names, and the yardstick works exactly as it does for TypeScript.
177
177
 
178
- The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JS/TS family, plus HTML and Svelte, which delegate to it. There is **no shell parser**. So:
178
+ The **automated** mutation gate is not language-agnostic, and the difference is easy to miss because both end in a green result. Stryker's instrumenter is per-language — the JavaScript and TypeScript family, plus Vue, HTML and Svelte, which delegate to it. There is **no shell parser**. So:
179
179
 
180
180
  - A guard whose logic lives in `.sh` produces **no mutants under any `mutate` list**. It is outside the gate by construction, not by a selection decision, and widening the patterns cannot bring it in.
181
181
  - Adding a `.sh` path to `mutate` does not measure it and does not quietly no-op. Stryker throws out of instrumentation — `Unable to parse …. No parser registered for .sh!` — and the run aborts before a single mutant is tried, so **one such entry destroys the score of every other guard in the list.** Measured, not inferred.
@@ -39,7 +39,7 @@ Each has been observed in real runs; each reported success while asserting nothi
39
39
  This form is preferred over the alternatives because it is the only one that both survives `sh` and keeps the full output, which is what you need the moment the status says the gate failed. `set -o pipefail` is correct where it exists but is **not POSIX `sh`** — dash has no such option, and this repository runs hooks under `sh`. `${PIPESTATUS[0]}` is **bash-only** (zsh spells it `$pipestatus[1]`, 1-indexed) and is overwritten by the very next command, so it must be read immediately. Never report a gate as green from a status read through `| tail`, `| head`, or `| tee`.
40
40
  - **Falsify before reporting.** No clean result is reportable until the check has been shown to fail on a deliberate break. **"Mentally reverting" does not count** — reasoning that the assertion *would* fail is precisely the step that lets a non-functional guard ship, because the author already believes it is load-bearing. Run the break.
41
41
  - **Mutation-prove the guard, then read the cardinality.** Neuter the protection **in production code**, run the **whole suite**, count the failures and **read their names**, then revert. **Zero** ⇒ the guard is inert (one pinning a single field shipped with all 50 tests green while the regression walked through). **Many, unrelated** ⇒ over-broad; its next failure will not name the cause. **Exactly one, or several all named for the same regression** ⇒ load-bearing and correctly scoped. A **zero is robust to contention** — load only ever adds failures — so only the non-zero counts need a quiet machine.
42
- - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JS/TS family and nothing else — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
42
+ - **The automated mutation gate is language-scoped; the manual probe above is not.** Stryker instruments the JavaScript and TypeScript family plus Vue, HTML and Svelte — there is **no shell parser**, so a guard whose logic lives in `.sh` yields no mutants under **any** `mutate` list. Adding one does not measure it: it aborts the whole run (`No parser registered for .sh!` — measured, not inferred) and takes every other guard's score with it. So a mutation gate that stays grey or green on a shell-guard change **measured nothing about that guard**, and reading it as coverage is the fifth failure mode above wearing a config file. Shell guards are covered by **driving tests only**: execute the script against a payload table and assert the blocked/allowed verdict, with a control on **both** sides — a refusal case *and* an allowed case. A shell guard nothing executes has no bite evidence at all, whatever the gate printed.
43
43
  - **Never scope the probe by filename, and never infer absent coverage from a grep.** Suites split at the line cap, so a protection's tests often sit in a sibling file: a filename-scoped probe reported cardinality 0 on a live security fix that a whole-suite run showed failing 4 tests, every one named for it.
44
44
  - **Assert that no copy is wrong, not that some copy is right.** An existential assertion over duplicated content is satisfied by the correct copy and cannot see the broken one — rewriting one suite's assertions to universal negatives took the same mutation from **0 to 18** failures. Enumerate the property (*every* marker uses an em-dash), never the one known-bad spelling.
45
45
  - **Reconstruct a red state; never narrate one you did not run.** Evidence describing a failing or dirty state must come from a run you actually observed — an evidence file once claimed "working tree clean" while listing two untracked scripts in the same file. To observe a state that no longer exists, check that commit out in a **throwaway detached-HEAD worktree** and run it there. Writing down what the output would have said is fabrication, however confident the reasoning.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.12.0",
3
+ "version": "4.13.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"