@codyswann/lisa 1.85.3 → 1.85.4

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.
package/package.json CHANGED
@@ -78,7 +78,7 @@
78
78
  "lodash": ">=4.18.1"
79
79
  },
80
80
  "name": "@codyswann/lisa",
81
- "version": "1.85.3",
81
+ "version": "1.85.4",
82
82
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
83
83
  "main": "dist/index.js",
84
84
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "1.85.3",
3
+ "version": "1.85.4",
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": "1.85.3",
3
+ "version": "1.85.4",
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-expo",
3
- "version": "1.85.3",
3
+ "version": "1.85.4",
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-nestjs",
3
- "version": "1.85.3",
3
+ "version": "1.85.4",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "1.85.3",
3
+ "version": "1.85.4",
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": "1.85.3",
3
+ "version": "1.85.4",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -24,24 +24,76 @@ cd "$PROJECT_ROOT"
24
24
  # explicit `lisa:update` (npx @codyswann/lisa@latest .) or the project's own
25
25
  # postinstall script (defaults.scripts.postinstall in package.lisa.json).
26
26
 
27
- # Strip the hooks key from .claude/settings.json if .claude/hooks/ is now empty/absent
28
- # (hooks moved to plugin.json; all .claude/hooks/*.sh scripts are deleted by lisa update)
27
+ # Strip only hook entries that reference deleted .claude/hooks/*.sh scripts
28
+ # (hooks moved to plugin.json; file-path hooks would produce "No such file or directory" errors).
29
+ # Preserve inline command hooks (e.g. `command -v entire ...`, `echo ...`) and stack-template hooks
30
+ # from rails/merge/.claude/settings.json.
29
31
  SETTINGS_FILE="$PROJECT_ROOT/.claude/settings.json"
30
- HOOKS_DIR="$PROJECT_ROOT/.claude/hooks"
31
32
  if [ -f "$SETTINGS_FILE" ] && command -v python3 >/dev/null 2>&1; then
32
- if [ ! -d "$HOOKS_DIR" ] || [ -z "$(ls -A "$HOOKS_DIR" 2>/dev/null)" ]; then
33
- python3 - "$SETTINGS_FILE" <<'PYEOF'
33
+ python3 - "$SETTINGS_FILE" <<'PYEOF'
34
34
  import json, sys
35
35
  path = sys.argv[1]
36
36
  with open(path) as f:
37
37
  d = json.load(f)
38
- if "hooks" in d:
39
- del d["hooks"]
38
+
39
+ hooks = d.get("hooks")
40
+ if not isinstance(hooks, dict):
41
+ sys.exit(0)
42
+
43
+ def is_stale(entry):
44
+ # Stale = hook entry whose command references the deleted .claude/hooks/ dir.
45
+ if not isinstance(entry, dict):
46
+ return False
47
+ cmd = entry.get("command", "")
48
+ return isinstance(cmd, str) and "$CLAUDE_PROJECT_DIR/.claude/hooks/" in cmd
49
+
50
+ changed = False
51
+ new_hooks = {}
52
+ for category, matchers in hooks.items():
53
+ if not isinstance(matchers, list):
54
+ new_hooks[category] = matchers
55
+ continue
56
+ new_matchers = []
57
+ for matcher in matchers:
58
+ if not isinstance(matcher, dict):
59
+ new_matchers.append(matcher)
60
+ continue
61
+ if "hooks" not in matcher:
62
+ new_matchers.append(matcher)
63
+ continue
64
+
65
+ entries = matcher.get("hooks")
66
+ if isinstance(entries, list):
67
+ kept = [e for e in entries if not is_stale(e)]
68
+ if len(kept) != len(entries):
69
+ changed = True
70
+ if kept:
71
+ new_matcher = dict(matcher)
72
+ new_matcher["hooks"] = kept
73
+ new_matchers.append(new_matcher)
74
+ elif entries:
75
+ # drop matcher only when pruning emptied a previously non-empty hooks list
76
+ changed = True
77
+ else:
78
+ # preserve pre-existing empty matcher blocks unchanged
79
+ new_matchers.append(matcher)
80
+ else:
81
+ new_matchers.append(matcher)
82
+ if new_matchers:
83
+ new_hooks[category] = new_matchers
84
+ else:
85
+ # drop empty category
86
+ changed = True
87
+
88
+ if changed:
89
+ if new_hooks:
90
+ d["hooks"] = new_hooks
91
+ else:
92
+ del d["hooks"]
40
93
  with open(path, "w") as f:
41
94
  json.dump(d, f, indent=2)
42
95
  f.write("\n")
43
96
  PYEOF
44
- fi
45
97
  fi
46
98
 
47
99
  # Install plugins only when claude CLI is available