@codyswann/lisa 3.68.0 → 3.69.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 (102) hide show
  1. package/all/copy-overwrite/scripts/lisa-gates.mjs +13 -7
  2. package/all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh +20 -2
  3. package/dist/codex/scripts/format-on-edit.sh +28 -0
  4. package/dist/codex/scripts/lint-on-edit.sh +28 -0
  5. package/dist/codex/scripts/lisa-edit-gate.sh +152 -0
  6. package/dist/codex/scripts/rubocop-on-edit.sh +28 -0
  7. package/dist/codex/scripts/sg-scan-on-edit.sh +28 -0
  8. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  9. package/dist/core/lisa-owned-hash-ledger.js +2 -0
  10. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  11. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  12. package/dist/core/upstream-evidence-manifest.js +33 -9
  13. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  14. package/package.json +1 -1
  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/debug-hook.sh +13 -3
  18. package/plugins/lisa/hooks/sonar-secrets.sh +20 -2
  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/sonar-secrets.sh +20 -2
  27. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cursor/hooks/sonar-secrets.sh +20 -2
  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/hooks/lisa-edit-gate.sh +152 -0
  42. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-copilot/hooks/lisa-edit-gate.sh +152 -0
  45. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-cursor/hooks/lisa-edit-gate.sh +152 -0
  47. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  55. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails/hooks/lisa-edit-gate.sh +152 -0
  60. package/plugins/lisa-rails/hooks/rubocop-on-edit.sh +23 -0
  61. package/plugins/lisa-rails/hooks/sg-scan-on-edit.sh +23 -0
  62. package/plugins/lisa-rails-agy/plugin.json +1 -1
  63. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-copilot/hooks/lisa-edit-gate.sh +152 -0
  65. package/plugins/lisa-rails-copilot/hooks/rubocop-on-edit.sh +23 -0
  66. package/plugins/lisa-rails-copilot/hooks/sg-scan-on-edit.sh +23 -0
  67. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails-cursor/hooks/lisa-edit-gate.sh +152 -0
  69. package/plugins/lisa-rails-cursor/hooks/rubocop-on-edit.sh +23 -0
  70. package/plugins/lisa-rails-cursor/hooks/sg-scan-on-edit.sh +23 -0
  71. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript/hooks/format-on-edit.sh +23 -0
  74. package/plugins/lisa-typescript/hooks/lint-on-edit.sh +23 -0
  75. package/plugins/lisa-typescript/hooks/lisa-edit-gate.sh +152 -0
  76. package/plugins/lisa-typescript/hooks/sg-scan-on-edit.sh +23 -0
  77. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  78. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-typescript-copilot/hooks/format-on-edit.sh +23 -0
  80. package/plugins/lisa-typescript-copilot/hooks/lint-on-edit.sh +23 -0
  81. package/plugins/lisa-typescript-copilot/hooks/lisa-edit-gate.sh +152 -0
  82. package/plugins/lisa-typescript-copilot/hooks/sg-scan-on-edit.sh +23 -0
  83. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-typescript-cursor/hooks/format-on-edit.sh +23 -0
  85. package/plugins/lisa-typescript-cursor/hooks/lint-on-edit.sh +23 -0
  86. package/plugins/lisa-typescript-cursor/hooks/lisa-edit-gate.sh +152 -0
  87. package/plugins/lisa-typescript-cursor/hooks/sg-scan-on-edit.sh +23 -0
  88. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  91. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/src/base/hooks/debug-hook.sh +13 -3
  94. package/plugins/src/base/hooks/sonar-secrets.sh +20 -2
  95. package/plugins/src/nestjs/hooks/lisa-edit-gate.sh +152 -0
  96. package/plugins/src/rails/hooks/lisa-edit-gate.sh +152 -0
  97. package/plugins/src/rails/hooks/rubocop-on-edit.sh +23 -0
  98. package/plugins/src/rails/hooks/sg-scan-on-edit.sh +23 -0
  99. package/plugins/src/typescript/hooks/format-on-edit.sh +23 -0
  100. package/plugins/src/typescript/hooks/lint-on-edit.sh +23 -0
  101. package/plugins/src/typescript/hooks/lisa-edit-gate.sh +152 -0
  102. package/plugins/src/typescript/hooks/sg-scan-on-edit.sh +23 -0
package/package.json CHANGED
@@ -143,7 +143,7 @@
143
143
  "zod-validation-error": "^4.0.0"
144
144
  },
145
145
  "name": "@codyswann/lisa",
146
- "version": "3.68.0",
146
+ "version": "3.69.1",
147
147
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
148
148
  "main": "dist/index.js",
149
149
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -4,14 +4,24 @@
4
4
  # This script is a no-op when CLAUDE_DEBUG is not set or set to 0
5
5
  ##
6
6
 
7
+ # Read JSON input from stdin BEFORE the no-op check can exit.
8
+ #
9
+ # A hook that exits before consuming stdin closes the read end while its
10
+ # caller's write is still in flight, and the CALLER's write raises EPIPE. The
11
+ # hook exits 0 and looks healthy; the harness takes the failure. Measured on
12
+ # this path at 30 EPIPE in 30 invocations once the payload exceeds the pipe
13
+ # buffer, and ~0.5% at the real payload size (CodySwannGT/lisa#2949).
14
+ #
15
+ # This hook is the worst placement of that defect in the tree, because the
16
+ # early exit is the path it takes on every event of every session that has not
17
+ # set CLAUDE_DEBUG — which is all of them.
18
+ INPUT=$(cat)
19
+
7
20
  # Exit immediately if debug mode is not enabled
8
21
  if [[ "${CLAUDE_DEBUG:-0}" != "1" ]]; then
9
22
  exit 0
10
23
  fi
11
24
 
12
- # Read JSON input from stdin
13
- INPUT=$(cat)
14
-
15
25
  # Parse hook event info using jq
16
26
  HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"')
17
27
  TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "N/A"')
@@ -38,6 +38,26 @@
38
38
  # Usage: sonar-secrets.sh <vendor-event-name> # hook payload on stdin
39
39
  set -uo pipefail
40
40
 
41
+ # Read the payload FIRST, before anything here can stand aside.
42
+ #
43
+ # The caller pipes the hook envelope into our stdin. A path that exits before
44
+ # consuming it closes the read end while the caller's write is still in flight,
45
+ # and the caller's write raises EPIPE — a failure in the harness, produced by a
46
+ # hook that had nothing to say and exited 0 saying it. The evidence lands
47
+ # entirely on the writing side, which is why it read as a mystery for so long.
48
+ #
49
+ # It is a race, so it only fires when this process wins. Measured against the
50
+ # real payload at a 1-minute load average of 82: 3 EPIPE in 600 invocations of
51
+ # the no-event path (0.50%), and 30 in 30 once the payload exceeds the pipe
52
+ # buffer. Rare enough to look like a real failure, frequent enough to keep
53
+ # costing re-runs (CodySwannGT/lisa#2949).
54
+ #
55
+ # Reading first is the fix that also covers real callers, rather than only the
56
+ # tests: every exit below now happens after stdin has reached EOF. The same
57
+ # obligation is discharged as an explicit `cat >/dev/null` drain in
58
+ # install-pkgs.sh and setup-jira-cli.sh, which never use the payload at all.
59
+ payload="$(cat)"
60
+
41
61
  event="${1:-}"
42
62
  [[ -n "$event" ]] || exit 0
43
63
 
@@ -50,8 +70,6 @@ command -v sonar >/dev/null 2>&1 || exit 0
50
70
  # is reviewable, not in whatever shell happened to launch the agent.
51
71
  [[ "${LISA_SONAR_HOOK:-on}" == "off" ]] && exit 0
52
72
 
53
- payload="$(cat)"
54
-
55
73
  # The vendor signals its verdict as JSON on stdout and always exits 0, so the
56
74
  # exit code carries no information and the reason text is the only channel.
57
75
  inactive_marker='secret scanning is inactive'
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -38,6 +38,26 @@
38
38
  # Usage: sonar-secrets.sh <vendor-event-name> # hook payload on stdin
39
39
  set -uo pipefail
40
40
 
41
+ # Read the payload FIRST, before anything here can stand aside.
42
+ #
43
+ # The caller pipes the hook envelope into our stdin. A path that exits before
44
+ # consuming it closes the read end while the caller's write is still in flight,
45
+ # and the caller's write raises EPIPE — a failure in the harness, produced by a
46
+ # hook that had nothing to say and exited 0 saying it. The evidence lands
47
+ # entirely on the writing side, which is why it read as a mystery for so long.
48
+ #
49
+ # It is a race, so it only fires when this process wins. Measured against the
50
+ # real payload at a 1-minute load average of 82: 3 EPIPE in 600 invocations of
51
+ # the no-event path (0.50%), and 30 in 30 once the payload exceeds the pipe
52
+ # buffer. Rare enough to look like a real failure, frequent enough to keep
53
+ # costing re-runs (CodySwannGT/lisa#2949).
54
+ #
55
+ # Reading first is the fix that also covers real callers, rather than only the
56
+ # tests: every exit below now happens after stdin has reached EOF. The same
57
+ # obligation is discharged as an explicit `cat >/dev/null` drain in
58
+ # install-pkgs.sh and setup-jira-cli.sh, which never use the payload at all.
59
+ payload="$(cat)"
60
+
41
61
  event="${1:-}"
42
62
  [[ -n "$event" ]] || exit 0
43
63
 
@@ -50,8 +70,6 @@ command -v sonar >/dev/null 2>&1 || exit 0
50
70
  # is reviewable, not in whatever shell happened to launch the agent.
51
71
  [[ "${LISA_SONAR_HOOK:-on}" == "off" ]] && exit 0
52
72
 
53
- payload="$(cat)"
54
-
55
73
  # The vendor signals its verdict as JSON on stdout and always exits 0, so the
56
74
  # exit code carries no information and the reason text is the only channel.
57
75
  inactive_marker='secret scanning is inactive'
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.68.0",
3
+ "version": "3.69.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -38,6 +38,26 @@
38
38
  # Usage: sonar-secrets.sh <vendor-event-name> # hook payload on stdin
39
39
  set -uo pipefail
40
40
 
41
+ # Read the payload FIRST, before anything here can stand aside.
42
+ #
43
+ # The caller pipes the hook envelope into our stdin. A path that exits before
44
+ # consuming it closes the read end while the caller's write is still in flight,
45
+ # and the caller's write raises EPIPE — a failure in the harness, produced by a
46
+ # hook that had nothing to say and exited 0 saying it. The evidence lands
47
+ # entirely on the writing side, which is why it read as a mystery for so long.
48
+ #
49
+ # It is a race, so it only fires when this process wins. Measured against the
50
+ # real payload at a 1-minute load average of 82: 3 EPIPE in 600 invocations of
51
+ # the no-event path (0.50%), and 30 in 30 once the payload exceeds the pipe
52
+ # buffer. Rare enough to look like a real failure, frequent enough to keep
53
+ # costing re-runs (CodySwannGT/lisa#2949).
54
+ #
55
+ # Reading first is the fix that also covers real callers, rather than only the
56
+ # tests: every exit below now happens after stdin has reached EOF. The same
57
+ # obligation is discharged as an explicit `cat >/dev/null` drain in
58
+ # install-pkgs.sh and setup-jira-cli.sh, which never use the payload at all.
59
+ payload="$(cat)"
60
+
41
61
  event="${1:-}"
42
62
  [[ -n "$event" ]] || exit 0
43
63
 
@@ -50,8 +70,6 @@ command -v sonar >/dev/null 2>&1 || exit 0
50
70
  # is reviewable, not in whatever shell happened to launch the agent.
51
71
  [[ "${LISA_SONAR_HOOK:-on}" == "off" ]] && exit 0
52
72
 
53
- payload="$(cat)"
54
-
55
73
  # The vendor signals its verdict as JSON on stdout and always exits 0, so the
56
74
  # exit code carries no information and the reason text is the only channel.
57
75
  inactive_marker='secret scanning is inactive'
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,152 @@
1
+ #!/bin/bash
2
+ # This file is managed by Lisa.
3
+ # Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ # =============================================================================
5
+ # Edit-time gate façade
6
+ # =============================================================================
7
+ # Sourced by every on-edit hook. Answers one question before the hook runs
8
+ # anything: has the project declared what proves this property at this moment,
9
+ # and if so what should run instead of Lisa's written-in tool?
10
+ #
11
+ # WHY THIS EXISTS. Edit time is the highest-frequency enforcement surface Lisa
12
+ # owns and was the one surface with no configurability at all. The scripts
13
+ # resolved a RUNNER (`./node_modules/.bin/oxlint`, else `bunx`/`npx`) and
14
+ # hardcoded the TOOL, which is the inversion the gate registry exists to fix.
15
+ # Worse, they exit 2 — refusing the edit — when the written-in binary or config
16
+ # filename is absent, so a project that lints correctly with something else had
17
+ # every agent write refused until it installed Lisa's choice.
18
+ #
19
+ # THE CONTRACT, and the two halves that matter equally:
20
+ #
21
+ # declared -> the project's task runs and the hook's written-in tool is
22
+ # never consulted, so a missing binary cannot refuse the edit.
23
+ # undeclared -> NOTHING changes. The resolver is not even invoked when no
24
+ # registry is installed, and the hook falls through to exactly
25
+ # the command it ran before. That is the overwhelming majority
26
+ # of projects, and it is the case a façade silently breaks.
27
+ #
28
+ # ALL OR NOTHING FOR A MULTI-PROPERTY SCRIPT. `lisa_edit_gate_tasks` takes every
29
+ # property its caller proves and resolves only when EVERY one is declared. The
30
+ # Rails hook runs `rubocop -a` and then a second pass for unfixable errors — its
31
+ # own header calls it both formatter and linter — so standing down on `code-style`
32
+ # alone would silently stop proving `format-conformance`. Same shape as
33
+ # `lisa_gate_covers test-correctness coverage-adequacy` in the pre-push hook.
34
+ #
35
+ # HOW THE DECLARED TASK LEARNS WHICH FILE WAS EDITED. It does not take an
36
+ # argument: `<runner> <task>` with no arguments is the invocation every other
37
+ # Lisa façade uses, and adding a positional here would make the declaration
38
+ # mean something different at this moment than at every other one. The path is
39
+ # exported as LISA_EDITED_FILE instead, so a project that wants a per-file run
40
+ # can scope its own task and one that does not gets a whole-project run.
41
+ # =============================================================================
42
+
43
+ # Where the gate registry may be installed, in resolution order.
44
+ lisa_edit_gate_registry() {
45
+ for _lisa_candidate in \
46
+ "node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-gates.mjs" \
47
+ "scripts/lisa-gates.mjs" \
48
+ "all/copy-overwrite/scripts/lisa-gates.mjs"
49
+ do
50
+ if [ -f "$_lisa_candidate" ]; then
51
+ printf '%s' "$_lisa_candidate"
52
+ unset _lisa_candidate
53
+ return 0
54
+ fi
55
+ done
56
+ unset _lisa_candidate
57
+ return 1
58
+ }
59
+
60
+ # lisa_edit_gate_tasks <moment> <gate-id>...
61
+ #
62
+ # Prints one `<runner> <task>` line per DISTINCT declared task and returns 0,
63
+ # but only when every named gate resolves to a runnable task at <moment>.
64
+ # Returns 1 and prints nothing otherwise — including when no registry is
65
+ # installed, when node is unavailable, and when the resolver itself fails.
66
+ #
67
+ # Fail-safe in the direction that keeps proving things: any doubt returns 1 and
68
+ # the caller runs its built-in. A resolver that could not answer must never read
69
+ # as "the project has this covered".
70
+ lisa_edit_gate_tasks() {
71
+ _lisa_moment="$1"
72
+ shift
73
+ [ -n "$_lisa_moment" ] || { unset _lisa_moment; return 1; }
74
+ [ "$#" -gt 0 ] || { unset _lisa_moment; return 1; }
75
+ # NO SETTINGS FILE, NO SPAWN. Checked before anything else because this is
76
+ # the path essentially every project is on, and it runs on EVERY agent
77
+ # edit: asking the resolver would cost a node process per keystroke-sized
78
+ # write to answer "nothing is declared", which the absent file already
79
+ # answers. The equivalence control compares which COMMAND runs and would
80
+ # not have caught the cost.
81
+ [ -f .lisa.config.json ] || { unset _lisa_moment; return 1; }
82
+ command -v node >/dev/null 2>&1 || { unset _lisa_moment; return 1; }
83
+
84
+ _lisa_registry="$(lisa_edit_gate_registry)" || {
85
+ unset _lisa_moment _lisa_registry
86
+ return 1
87
+ }
88
+
89
+ # EXPORTED, not prefixed. An assignment prefix reaches only the FIRST
90
+ # command of a pipeline, and the filter that reads it is the second — so
91
+ # the prefixed form resolved an empty want-list and every script fell
92
+ # silently back to its built-in, which is a façade that looks wired and is
93
+ # not.
94
+ LISA_GATE_IDS="$*"
95
+ export LISA_GATE_IDS
96
+ _lisa_out="$(
97
+ node "$_lisa_registry" list \
98
+ --moment="$_lisa_moment" --json 2>/dev/null | node -e '
99
+ let raw = "";
100
+ process.stdin.on("data", chunk => { raw += chunk }).on("end", () => {
101
+ let resolved;
102
+ try { resolved = JSON.parse(raw || "[]"); } catch { process.exit(1); }
103
+ const wanted = (process.env.LISA_GATE_IDS || "").split(" ").filter(Boolean);
104
+ const commands = [];
105
+ for (const id of wanted) {
106
+ const hit = resolved.find(gate => gate.id === id);
107
+ // `run` mode with a task is the only shape that PROVES anything here. An
108
+ // awaited gate proves nothing at edit time and an intercepted one is
109
+ // Lisa running it elsewhere; both must leave the built-in in charge.
110
+ if (!hit || hit.mode !== "run" || !hit.command) process.exit(1);
111
+ if (!commands.includes(hit.command)) commands.push(hit.command);
112
+ }
113
+ if (commands.length === 0) process.exit(1);
114
+ process.stdout.write(commands.join("\n"));
115
+ });
116
+ '
117
+ )" || {
118
+ unset _lisa_moment _lisa_registry _lisa_out
119
+ return 1
120
+ }
121
+
122
+ [ -n "$_lisa_out" ] || {
123
+ unset _lisa_moment _lisa_registry _lisa_out
124
+ return 1
125
+ }
126
+ printf '%s' "$_lisa_out"
127
+ unset _lisa_moment _lisa_registry _lisa_out
128
+ return 0
129
+ }
130
+
131
+ # lisa_edit_gate_run <edited-file> <commands>
132
+ #
133
+ # Runs each resolved command with LISA_EDITED_FILE exported. Exits 2 on the
134
+ # first failure, which is the same refusal the built-in path uses, so a
135
+ # declared task that finds a real problem still stops the agent.
136
+ lisa_edit_gate_run() {
137
+ _lisa_file="$1"
138
+ shift
139
+ LISA_EDITED_FILE="$_lisa_file"
140
+ export LISA_EDITED_FILE
141
+ printf '%s\n' "$*" | while IFS= read -r _lisa_command; do
142
+ [ -n "$_lisa_command" ] || continue
143
+ echo "Running declared gate task: $_lisa_command"
144
+ # Deliberate word split: the resolver validated both halves as plain
145
+ # words, so no shell metacharacter reaches argv.
146
+ # shellcheck disable=SC2086
147
+ $_lisa_command || exit 2
148
+ done
149
+ _lisa_status=$?
150
+ unset _lisa_file _lisa_command
151
+ return $_lisa_status
152
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.68.0",
3
+ "version": "3.69.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": "3.68.0",
3
+ "version": "3.69.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"