@codyswann/lisa 1.76.5 → 1.77.0

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 (33) hide show
  1. package/all/deletions.json +5 -1
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +21 -1
  4. package/plugins/lisa/hooks/inject-rules.sh +22 -0
  5. package/{all/copy-overwrite/.claude → plugins/lisa}/rules/base-rules.md +4 -3
  6. package/{all/copy-overwrite/.claude → plugins/lisa}/rules/verification.md +10 -0
  7. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  9. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-rails/.claude-plugin/plugin.json +23 -1
  11. package/plugins/lisa-rails/hooks/inject-rules.sh +22 -0
  12. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-typescript/hooks/lint-on-edit.sh +3 -1
  14. package/plugins/src/base/.claude-plugin/plugin.json +4 -0
  15. package/plugins/src/base/hooks/inject-rules.sh +22 -0
  16. package/plugins/src/base/rules/base-rules.md +89 -0
  17. package/plugins/src/base/rules/coding-philosophy.md +428 -0
  18. package/plugins/src/base/rules/intent-routing.md +126 -0
  19. package/plugins/src/base/rules/security-audit-handling.md +30 -0
  20. package/plugins/src/base/rules/verification.md +93 -0
  21. package/plugins/src/rails/.claude-plugin/plugin.json +6 -0
  22. package/plugins/src/rails/hooks/inject-rules.sh +22 -0
  23. package/plugins/src/rails/rules/rails-conventions.md +176 -0
  24. package/plugins/src/typescript/hooks/lint-on-edit.sh +3 -1
  25. package/rails/deletions.json +2 -1
  26. package/plugins/lisa/hooks/enforce-plan-rules.sh +0 -15
  27. package/plugins/lisa/hooks/sync-tasks.sh +0 -107
  28. package/plugins/src/base/hooks/enforce-plan-rules.sh +0 -15
  29. package/plugins/src/base/hooks/sync-tasks.sh +0 -107
  30. /package/{all/copy-overwrite/.claude → plugins/lisa}/rules/coding-philosophy.md +0 -0
  31. /package/{all/copy-overwrite/.claude → plugins/lisa}/rules/intent-routing.md +0 -0
  32. /package/{all/copy-overwrite/.claude → plugins/lisa}/rules/security-audit-handling.md +0 -0
  33. /package/{rails/copy-overwrite/.claude → plugins/lisa-rails}/rules/rails-conventions.md +0 -0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Reads all .md files from the plugin's rules/ directory and injects them
3
+ # into the session context via additionalContext.
4
+ # Used by SessionStart and SubagentStart hooks.
5
+ set -euo pipefail
6
+
7
+ RULES_DIR="${CLAUDE_PLUGIN_ROOT}/rules"
8
+
9
+ # Bail silently if no rules directory
10
+ [ -d "$RULES_DIR" ] || exit 0
11
+
12
+ CONTEXT=""
13
+ for file in "$RULES_DIR"/*.md; do
14
+ [ -f "$file" ] || continue
15
+ CONTEXT+="$(cat "$file")"$'\n\n'
16
+ done
17
+
18
+ # Bail if no rules found
19
+ [ -n "$CONTEXT" ] || exit 0
20
+
21
+ # Output as JSON — jq handles escaping
22
+ jq -n --arg ctx "$CONTEXT" '{"additionalContext": $ctx}'
@@ -0,0 +1,176 @@
1
+ # Rails Coding Conventions
2
+
3
+ This rule enforces Rails-specific coding standards for consistency, maintainability, and performance.
4
+
5
+ ## Fat Models, Skinny Controllers
6
+
7
+ Controllers handle HTTP concerns only. Business logic belongs in models or service objects.
8
+
9
+ ```ruby
10
+ # Correct — controller delegates to model
11
+ class OrdersController < ApplicationController
12
+ def create
13
+ @order = Order.place(order_params, current_user)
14
+ redirect_to @order
15
+ end
16
+ end
17
+
18
+ # Wrong — business logic in controller
19
+ class OrdersController < ApplicationController
20
+ def create
21
+ @order = Order.new(order_params)
22
+ @order.user = current_user
23
+ @order.total = @order.line_items.sum(&:price)
24
+ @order.apply_discount(current_user.discount_rate)
25
+ @order.save!
26
+ OrderMailer.confirmation(@order).deliver_later
27
+ redirect_to @order
28
+ end
29
+ end
30
+ ```
31
+
32
+ ## Service Objects
33
+
34
+ Extract complex business logic into service objects when a model method would be too large or spans multiple models.
35
+
36
+ ```ruby
37
+ # app/services/order_placement_service.rb
38
+ class OrderPlacementService
39
+ def initialize(user:, params:)
40
+ @user = user
41
+ @params = params
42
+ end
43
+
44
+ def call
45
+ order = Order.new(@params)
46
+ order.user = @user
47
+ order.calculate_total
48
+ order.save!
49
+ OrderMailer.confirmation(order).deliver_later
50
+ order
51
+ end
52
+ end
53
+ ```
54
+
55
+ ## Concerns
56
+
57
+ Use concerns to share behavior across models or controllers. Keep concerns focused on a single responsibility.
58
+
59
+ ```ruby
60
+ # app/models/concerns/searchable.rb
61
+ module Searchable
62
+ extend ActiveSupport::Concern
63
+
64
+ included do
65
+ scope :search, ->(query) { where("name ILIKE ?", "%#{query}%") }
66
+ end
67
+ end
68
+ ```
69
+
70
+ ## ActiveRecord Patterns
71
+
72
+ ### Scopes over class methods for chainable queries
73
+
74
+ ```ruby
75
+ # Correct — scope
76
+ scope :active, -> { where(active: true) }
77
+ scope :recent, -> { order(created_at: :desc) }
78
+
79
+ # Wrong — class method for simple query
80
+ def self.active
81
+ where(active: true)
82
+ end
83
+ ```
84
+
85
+ ### Validations
86
+
87
+ ```ruby
88
+ # Use built-in validators
89
+ validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
90
+ validates :age, numericality: { greater_than: 0 }
91
+ ```
92
+
93
+ ### Callbacks — use sparingly
94
+
95
+ Prefer explicit service objects over callbacks for complex side effects. Callbacks are acceptable for simple data normalization.
96
+
97
+ ```ruby
98
+ # Acceptable — simple normalization
99
+ before_validation :normalize_email
100
+
101
+ private
102
+
103
+ def normalize_email
104
+ self.email = email&.downcase&.strip
105
+ end
106
+ ```
107
+
108
+ ## N+1 Query Prevention
109
+
110
+ Always use `includes`, `preload`, or `eager_load` to prevent N+1 queries. The Bullet gem is included to detect these in development.
111
+
112
+ ```ruby
113
+ # Correct — eager loading
114
+ @posts = Post.includes(:author, :comments).where(published: true)
115
+
116
+ # Wrong — N+1 query
117
+ @posts = Post.where(published: true)
118
+ @posts.each { |post| post.author.name } # N+1!
119
+ ```
120
+
121
+ ## Strong Parameters
122
+
123
+ Always use strong parameters in controllers. Never use `permit!`.
124
+
125
+ ```ruby
126
+ # Correct
127
+ def order_params
128
+ params.require(:order).permit(:product_id, :quantity, :notes)
129
+ end
130
+
131
+ # Wrong — permits everything
132
+ def order_params
133
+ params.require(:order).permit!
134
+ end
135
+ ```
136
+
137
+ ## Database Migrations
138
+
139
+ - Use `strong_migrations` gem constraints (included via Gemfile.lisa)
140
+ - Never modify `db/schema.rb` directly
141
+ - Always add indexes for foreign keys and commonly queried columns
142
+ - Use `change` method when the migration is reversible; use `up`/`down` when it is not
143
+
144
+ ```ruby
145
+ class AddIndexToOrdersUserId < ActiveRecord::Migration[7.2]
146
+ def change
147
+ add_index :orders, :user_id
148
+ end
149
+ end
150
+ ```
151
+
152
+ ## Testing with RSpec
153
+
154
+ - Use `let` and `let!` for test setup
155
+ - Use `described_class` instead of repeating the class name
156
+ - Use `factory_bot` for test data, not fixtures
157
+ - Use `shoulda-matchers` for model validation tests
158
+ - Keep tests focused — one assertion concept per example
159
+
160
+ ```ruby
161
+ RSpec.describe Order, type: :model do
162
+ describe "validations" do
163
+ it { is_expected.to validate_presence_of(:user) }
164
+ it { is_expected.to validate_numericality_of(:total).is_greater_than(0) }
165
+ end
166
+
167
+ describe ".recent" do
168
+ it "returns orders in descending creation order" do
169
+ old_order = create(:order, created_at: 1.day.ago)
170
+ new_order = create(:order, created_at: 1.hour.ago)
171
+
172
+ expect(described_class.recent).to eq([new_order, old_order])
173
+ end
174
+ end
175
+ end
176
+ ```
@@ -55,10 +55,12 @@ fi
55
55
  # Run ESLint with --fix --quiet --cache on the specific file
56
56
  # --quiet: suppress warnings, only show errors
57
57
  # --cache: use ESLint cache for performance
58
+ # --rule: disable no-unused-vars auto-fix to prevent removing imports that Claude
59
+ # plans to use in a subsequent edit (pre-commit hook still catches them)
58
60
  echo "Running ESLint --fix on: $FILE_PATH"
59
61
 
60
62
  # First pass: attempt auto-fix
61
- OUTPUT=$($PKG_MANAGER eslint --fix --quiet --cache "$FILE_PATH" 2>&1)
63
+ OUTPUT=$($PKG_MANAGER eslint --fix --quiet --cache --rule '@typescript-eslint/no-unused-vars: off' "$FILE_PATH" 2>&1)
62
64
  FIX_EXIT=$?
63
65
 
64
66
  if [ $FIX_EXIT -eq 0 ]; then
@@ -8,6 +8,7 @@
8
8
  ".claude/skills/plan-fix-linter-error",
9
9
  ".claude/skills/plan-lower-code-complexity",
10
10
  ".claude/skills/plan-reduce-max-lines",
11
- ".claude/skills/plan-reduce-max-lines-per-function"
11
+ ".claude/skills/plan-reduce-max-lines-per-function",
12
+ ".claude/rules/rails-conventions.md"
12
13
  ]
13
14
  }
@@ -1,15 +0,0 @@
1
- #!/bin/bash
2
- # Reinjects plan-mode rules on every prompt when Claude is in plan mode.
3
- # Wired as a UserPromptSubmit hook in .claude/settings.json.
4
-
5
- INPUT=$(cat)
6
- PERMISSION_MODE=$(echo "$INPUT" | jq -r '.permission_mode // "default"')
7
-
8
- if [ "$PERMISSION_MODE" = "plan" ]; then
9
- PLAN_RULES="$CLAUDE_PROJECT_DIR/.claude/rules/plan.md"
10
- if [ -f "$PLAN_RULES" ]; then
11
- echo "PLAN MODE RULES (reinforced):"
12
- cat "$PLAN_RULES"
13
- fi
14
- fi
15
- exit 0
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # sync-tasks.sh - Syncs Claude Code tasks to project directories
4
- #
5
- # This hook is triggered on PostToolUse for TaskCreate and TaskUpdate.
6
- # It reads the task metadata to determine the project and syncs
7
- # task JSON files to ./projects/{project}/tasks/{session-id}/
8
- #
9
- # This session-based structure preserves task history across /clear commands,
10
- # preventing overwrites when new sessions create tasks with the same IDs.
11
- #
12
- # Input (via stdin): JSON with tool_name, tool_input, tool_response
13
- #
14
-
15
- # Temporarily disable this hook
16
- exit 0
17
-
18
- set -euo pipefail
19
-
20
- # Read JSON input from stdin
21
- INPUT=$(cat)
22
-
23
- # Extract tool name
24
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
25
-
26
- # Only process TaskCreate and TaskUpdate
27
- if [[ "$TOOL_NAME" != "TaskCreate" && "$TOOL_NAME" != "TaskUpdate" ]]; then
28
- exit 0
29
- fi
30
-
31
- # Try to get project from multiple sources:
32
- # 1. Task metadata (passed in tool_input)
33
- # 2. .claude-active-project marker file
34
-
35
- PROJECT=""
36
-
37
- # Check tool_input metadata for project
38
- PROJECT=$(echo "$INPUT" | jq -r '.tool_input.metadata.project // empty')
39
-
40
- # If no project in metadata, check marker file
41
- if [[ -z "$PROJECT" && -f ".claude-active-project" ]]; then
42
- PROJECT=$(cat .claude-active-project | tr -d '[:space:]')
43
- fi
44
-
45
- # If still no project, skip syncing
46
- if [[ -z "$PROJECT" ]]; then
47
- exit 0
48
- fi
49
-
50
- # Validate project name (kebab-case, no path traversal)
51
- if [[ ! "$PROJECT" =~ ^[a-z0-9-]+$ ]]; then
52
- echo "Warning: Invalid project name '$PROJECT', skipping sync" >&2
53
- exit 0
54
- fi
55
-
56
- # Get task ID
57
- TASK_ID=""
58
- if [[ "$TOOL_NAME" == "TaskCreate" ]]; then
59
- # For TaskCreate, ID is in tool_response.task.id
60
- TASK_ID=$(echo "$INPUT" | jq -r '.tool_response.task.id // empty')
61
- elif [[ "$TOOL_NAME" == "TaskUpdate" ]]; then
62
- # For TaskUpdate, ID is in tool_input
63
- TASK_ID=$(echo "$INPUT" | jq -r '.tool_input.taskId // empty')
64
- fi
65
-
66
- if [[ -z "$TASK_ID" ]]; then
67
- exit 0
68
- fi
69
-
70
- # Find the task file in ~/.claude/tasks/
71
- # Tasks are stored in ~/.claude/tasks/{session-uuid}/{id}.json
72
- CLAUDE_TASKS_DIR="${HOME}/.claude/tasks"
73
- TASK_FILE=""
74
-
75
- # Get session ID from hook input (preferred - 100% accurate)
76
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
77
-
78
- if [[ -n "$SESSION_ID" && -f "${CLAUDE_TASKS_DIR}/${SESSION_ID}/${TASK_ID}.json" ]]; then
79
- # Use session ID directly - guaranteed correct session
80
- TASK_FILE="${CLAUDE_TASKS_DIR}/${SESSION_ID}/${TASK_ID}.json"
81
- else
82
- # Fallback: find most recently modified task file with this ID
83
- # This handles edge cases where session_id isn't available
84
- TASK_FILE=$(find "$CLAUDE_TASKS_DIR" -name "${TASK_ID}.json" -exec stat -f '%m %N' {} \; 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
85
- fi
86
-
87
- if [[ -z "$TASK_FILE" || ! -f "$TASK_FILE" ]]; then
88
- exit 0
89
- fi
90
-
91
- # Require session ID for proper history tracking
92
- if [[ -z "$SESSION_ID" ]]; then
93
- echo "Warning: No session_id available, skipping sync" >&2
94
- exit 0
95
- fi
96
-
97
- # Ensure project tasks directory exists (includes session ID for history preservation)
98
- PROJECT_TASKS_DIR="./projects/${PROJECT}/tasks/${SESSION_ID}"
99
- mkdir -p "$PROJECT_TASKS_DIR"
100
-
101
- # Copy task file to project directory
102
- cp "$TASK_FILE" "${PROJECT_TASKS_DIR}/${TASK_ID}.json"
103
-
104
- # Optionally stage the file for git (non-blocking)
105
- git add "${PROJECT_TASKS_DIR}/${TASK_ID}.json" 2>/dev/null || true
106
-
107
- exit 0
@@ -1,15 +0,0 @@
1
- #!/bin/bash
2
- # Reinjects plan-mode rules on every prompt when Claude is in plan mode.
3
- # Wired as a UserPromptSubmit hook in .claude/settings.json.
4
-
5
- INPUT=$(cat)
6
- PERMISSION_MODE=$(echo "$INPUT" | jq -r '.permission_mode // "default"')
7
-
8
- if [ "$PERMISSION_MODE" = "plan" ]; then
9
- PLAN_RULES="$CLAUDE_PROJECT_DIR/.claude/rules/plan.md"
10
- if [ -f "$PLAN_RULES" ]; then
11
- echo "PLAN MODE RULES (reinforced):"
12
- cat "$PLAN_RULES"
13
- fi
14
- fi
15
- exit 0
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # sync-tasks.sh - Syncs Claude Code tasks to project directories
4
- #
5
- # This hook is triggered on PostToolUse for TaskCreate and TaskUpdate.
6
- # It reads the task metadata to determine the project and syncs
7
- # task JSON files to ./projects/{project}/tasks/{session-id}/
8
- #
9
- # This session-based structure preserves task history across /clear commands,
10
- # preventing overwrites when new sessions create tasks with the same IDs.
11
- #
12
- # Input (via stdin): JSON with tool_name, tool_input, tool_response
13
- #
14
-
15
- # Temporarily disable this hook
16
- exit 0
17
-
18
- set -euo pipefail
19
-
20
- # Read JSON input from stdin
21
- INPUT=$(cat)
22
-
23
- # Extract tool name
24
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
25
-
26
- # Only process TaskCreate and TaskUpdate
27
- if [[ "$TOOL_NAME" != "TaskCreate" && "$TOOL_NAME" != "TaskUpdate" ]]; then
28
- exit 0
29
- fi
30
-
31
- # Try to get project from multiple sources:
32
- # 1. Task metadata (passed in tool_input)
33
- # 2. .claude-active-project marker file
34
-
35
- PROJECT=""
36
-
37
- # Check tool_input metadata for project
38
- PROJECT=$(echo "$INPUT" | jq -r '.tool_input.metadata.project // empty')
39
-
40
- # If no project in metadata, check marker file
41
- if [[ -z "$PROJECT" && -f ".claude-active-project" ]]; then
42
- PROJECT=$(cat .claude-active-project | tr -d '[:space:]')
43
- fi
44
-
45
- # If still no project, skip syncing
46
- if [[ -z "$PROJECT" ]]; then
47
- exit 0
48
- fi
49
-
50
- # Validate project name (kebab-case, no path traversal)
51
- if [[ ! "$PROJECT" =~ ^[a-z0-9-]+$ ]]; then
52
- echo "Warning: Invalid project name '$PROJECT', skipping sync" >&2
53
- exit 0
54
- fi
55
-
56
- # Get task ID
57
- TASK_ID=""
58
- if [[ "$TOOL_NAME" == "TaskCreate" ]]; then
59
- # For TaskCreate, ID is in tool_response.task.id
60
- TASK_ID=$(echo "$INPUT" | jq -r '.tool_response.task.id // empty')
61
- elif [[ "$TOOL_NAME" == "TaskUpdate" ]]; then
62
- # For TaskUpdate, ID is in tool_input
63
- TASK_ID=$(echo "$INPUT" | jq -r '.tool_input.taskId // empty')
64
- fi
65
-
66
- if [[ -z "$TASK_ID" ]]; then
67
- exit 0
68
- fi
69
-
70
- # Find the task file in ~/.claude/tasks/
71
- # Tasks are stored in ~/.claude/tasks/{session-uuid}/{id}.json
72
- CLAUDE_TASKS_DIR="${HOME}/.claude/tasks"
73
- TASK_FILE=""
74
-
75
- # Get session ID from hook input (preferred - 100% accurate)
76
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
77
-
78
- if [[ -n "$SESSION_ID" && -f "${CLAUDE_TASKS_DIR}/${SESSION_ID}/${TASK_ID}.json" ]]; then
79
- # Use session ID directly - guaranteed correct session
80
- TASK_FILE="${CLAUDE_TASKS_DIR}/${SESSION_ID}/${TASK_ID}.json"
81
- else
82
- # Fallback: find most recently modified task file with this ID
83
- # This handles edge cases where session_id isn't available
84
- TASK_FILE=$(find "$CLAUDE_TASKS_DIR" -name "${TASK_ID}.json" -exec stat -f '%m %N' {} \; 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
85
- fi
86
-
87
- if [[ -z "$TASK_FILE" || ! -f "$TASK_FILE" ]]; then
88
- exit 0
89
- fi
90
-
91
- # Require session ID for proper history tracking
92
- if [[ -z "$SESSION_ID" ]]; then
93
- echo "Warning: No session_id available, skipping sync" >&2
94
- exit 0
95
- fi
96
-
97
- # Ensure project tasks directory exists (includes session ID for history preservation)
98
- PROJECT_TASKS_DIR="./projects/${PROJECT}/tasks/${SESSION_ID}"
99
- mkdir -p "$PROJECT_TASKS_DIR"
100
-
101
- # Copy task file to project directory
102
- cp "$TASK_FILE" "${PROJECT_TASKS_DIR}/${TASK_ID}.json"
103
-
104
- # Optionally stage the file for git (non-blocking)
105
- git add "${PROJECT_TASKS_DIR}/${TASK_ID}.json" 2>/dev/null || true
106
-
107
- exit 0