ima-claude 2.20.0 → 2.26.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 (80) hide show
  1. package/README.md +74 -9
  2. package/dist/cli.js +2 -1
  3. package/package.json +1 -1
  4. package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
  5. package/plugins/ima-claude/agents/explorer.md +29 -15
  6. package/plugins/ima-claude/agents/implementer.md +58 -13
  7. package/plugins/ima-claude/agents/memory.md +19 -19
  8. package/plugins/ima-claude/agents/reviewer.md +84 -34
  9. package/plugins/ima-claude/agents/tester.md +59 -16
  10. package/plugins/ima-claude/agents/wp-developer.md +66 -21
  11. package/plugins/ima-claude/hooks/bootstrap.sh +42 -44
  12. package/plugins/ima-claude/hooks/prompt_coach_digest.md +14 -17
  13. package/plugins/ima-claude/hooks/prompt_coach_system.md +10 -12
  14. package/plugins/ima-claude/personalities/README.md +17 -6
  15. package/plugins/ima-claude/personalities/enable-efficient.md +61 -0
  16. package/plugins/ima-claude/personalities/enable-terse.md +71 -0
  17. package/plugins/ima-claude/skills/agentic-workflows/SKILL.md +35 -71
  18. package/plugins/ima-claude/skills/architect/SKILL.md +54 -168
  19. package/plugins/ima-claude/skills/compound-bridge/SKILL.md +41 -94
  20. package/plugins/ima-claude/skills/design-to-code/SKILL.md +43 -78
  21. package/plugins/ima-claude/skills/discourse/SKILL.md +79 -194
  22. package/plugins/ima-claude/skills/discourse-admin/SKILL.md +41 -103
  23. package/plugins/ima-claude/skills/docs-organize/SKILL.md +63 -203
  24. package/plugins/ima-claude/skills/ember-discourse/SKILL.md +90 -200
  25. package/plugins/ima-claude/skills/espocrm/SKILL.md +14 -23
  26. package/plugins/ima-claude/skills/espocrm-api/SKILL.md +79 -192
  27. package/plugins/ima-claude/skills/functional-programmer/SKILL.md +33 -237
  28. package/plugins/ima-claude/skills/gh-cli/SKILL.md +26 -65
  29. package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +71 -104
  30. package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +32 -22
  31. package/plugins/ima-claude/skills/ima-brand/SKILL.md +18 -23
  32. package/plugins/ima-claude/skills/ima-copywriting/SKILL.md +68 -179
  33. package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +32 -102
  34. package/plugins/ima-claude/skills/ima-editorial-scorecard/SKILL.md +38 -63
  35. package/plugins/ima-claude/skills/ima-editorial-workflow/SKILL.md +69 -114
  36. package/plugins/ima-claude/skills/ima-email-creator/SKILL.md +16 -22
  37. package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +21 -37
  38. package/plugins/ima-claude/skills/ima-git/SKILL.md +81 -0
  39. package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +39 -120
  40. package/plugins/ima-claude/skills/jquery/SKILL.md +107 -233
  41. package/plugins/ima-claude/skills/js-fp/SKILL.md +75 -296
  42. package/plugins/ima-claude/skills/js-fp-api/SKILL.md +52 -162
  43. package/plugins/ima-claude/skills/js-fp-react/SKILL.md +47 -270
  44. package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +55 -209
  45. package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +59 -204
  46. package/plugins/ima-claude/skills/livecanvas/SKILL.md +19 -32
  47. package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +92 -162
  48. package/plugins/ima-claude/skills/mcp-context7/SKILL.md +32 -64
  49. package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +98 -188
  50. package/plugins/ima-claude/skills/mcp-github/SKILL.md +60 -124
  51. package/plugins/ima-claude/skills/mcp-memory/SKILL.md +1 -177
  52. package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +58 -115
  53. package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +32 -87
  54. package/plugins/ima-claude/skills/mcp-serena/SKILL.md +54 -80
  55. package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +40 -63
  56. package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +75 -116
  57. package/plugins/ima-claude/skills/php-authnet/SKILL.md +32 -65
  58. package/plugins/ima-claude/skills/php-fp/SKILL.md +50 -129
  59. package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +25 -73
  60. package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +103 -463
  61. package/plugins/ima-claude/skills/playwright/SKILL.md +69 -220
  62. package/plugins/ima-claude/skills/prompt-starter/SKILL.md +33 -83
  63. package/plugins/ima-claude/skills/prompt-starter/references/code-review.md +38 -0
  64. package/plugins/ima-claude/skills/py-fp/SKILL.md +78 -384
  65. package/plugins/ima-claude/skills/quasar-fp/SKILL.md +54 -255
  66. package/plugins/ima-claude/skills/quickstart/SKILL.md +7 -11
  67. package/plugins/ima-claude/skills/rails/SKILL.md +63 -184
  68. package/plugins/ima-claude/skills/resume-session/SKILL.md +14 -35
  69. package/plugins/ima-claude/skills/rg/SKILL.md +61 -146
  70. package/plugins/ima-claude/skills/ruby-fp/SKILL.md +66 -163
  71. package/plugins/ima-claude/skills/save-session/SKILL.md +10 -39
  72. package/plugins/ima-claude/skills/scorecard/SKILL.md +42 -40
  73. package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +42 -71
  74. package/plugins/ima-claude/skills/skill-creator/SKILL.md +79 -250
  75. package/plugins/ima-claude/skills/task-master/SKILL.md +11 -31
  76. package/plugins/ima-claude/skills/task-planner/SKILL.md +44 -153
  77. package/plugins/ima-claude/skills/task-runner/SKILL.md +61 -143
  78. package/plugins/ima-claude/skills/unit-testing/SKILL.md +59 -134
  79. package/plugins/ima-claude/skills/wp-ddev/SKILL.md +38 -120
  80. package/plugins/ima-claude/skills/wp-local/SKILL.md +26 -108
@@ -3,203 +3,118 @@ name: "rg"
3
3
  description: "Ripgrep (rg) - fast recursive search tool. Prefer over grep/find for code search. Respects .gitignore, searches recursively, supports regex. Use for file content search, file listing, pattern matching. Triggers on: ripgrep, rg, search files, find in files, grep, search code, find pattern."
4
4
  ---
5
5
 
6
- # Ripgrep (rg) - Preferred Search Tool
6
+ # Ripgrep (rg)
7
7
 
8
- **Always use `rg` instead of `grep` or `find -name`** - it's faster, has better defaults, and respects `.gitignore`.
8
+ Use `rg` instead of `grep` or `find -name`. Faster, better defaults, respects `.gitignore`.
9
9
 
10
- ## Quick Reference
10
+ ## Basic Search
11
11
 
12
- ### Basic Search
13
12
  ```bash
14
- # Search for pattern in current directory (recursive)
15
- rg "pattern"
16
-
17
- # Search specific file or directory
18
- rg "pattern" src/
19
- rg "pattern" file.ts
20
-
21
- # Case-insensitive search
22
- rg -i "pattern"
23
-
24
- # Word boundary match (whole words only)
25
- rg -w "function"
26
-
27
- # Fixed string (not regex)
28
- rg -F "exact.string.match"
13
+ rg "pattern" # Recursive from cwd
14
+ rg "pattern" src/ # Specific directory
15
+ rg -i "pattern" # Case-insensitive
16
+ rg -w "function" # Whole words only
17
+ rg -F "exact.string.match" # Fixed string (not regex)
29
18
  ```
30
19
 
31
- ### Output Control
32
- ```bash
33
- # Show N lines of context (before and after)
34
- rg -C 3 "pattern"
35
-
36
- # Lines before (-B) or after (-A) only
37
- rg -B 2 -A 5 "pattern"
38
-
39
- # Count matches per file
40
- rg -c "pattern"
41
-
42
- # List files with matches only (no content)
43
- rg -l "pattern"
44
- rg --files-with-matches "pattern"
45
-
46
- # List files WITHOUT matches
47
- rg --files-without-match "pattern"
48
-
49
- # Show only the matched text (not full line)
50
- rg -o "pattern"
51
- ```
20
+ ## Output Control
52
21
 
53
- ### File Filtering
54
22
  ```bash
55
- # By file type (built-in types)
56
- rg -t ts "pattern" # TypeScript only
57
- rg -t py "pattern" # Python only
58
- rg -t js "pattern" # JavaScript only
59
- rg -t rust "pattern" # Rust only
60
-
61
- # Exclude file type
62
- rg -T js "pattern" # Exclude JavaScript
63
-
64
- # By glob pattern
65
- rg -g "*.vue" "pattern" # Only .vue files
66
- rg -g "!*.test.ts" "pattern" # Exclude test files
67
- rg -g "src/**/*.ts" "pattern" # TypeScript in src/
68
-
69
- # Multiple globs
70
- rg -g "*.ts" -g "*.vue" "pattern"
23
+ rg -C 3 "pattern" # 3 lines context before+after
24
+ rg -B 2 -A 5 "pattern" # 2 before, 5 after
25
+ rg -c "pattern" # Count matches per file
26
+ rg -l "pattern" # Files with matches only
27
+ rg --files-without-match "pat" # Files without matches
28
+ rg -o "pattern" # Matched text only (not full line)
71
29
  ```
72
30
 
73
- ### List Files (No Search)
74
- ```bash
75
- # List all files that would be searched
76
- rg --files
77
-
78
- # List files matching glob
79
- rg --files -g "*.ts"
80
-
81
- # List files in specific directory
82
- rg --files src/components/
83
- ```
31
+ ## File Filtering
84
32
 
85
- ### Bypass Filters
86
33
  ```bash
87
- # -u levels (unrestricted):
88
- rg -u "pattern" # Ignore .gitignore
89
- rg -uu "pattern" # + search hidden files
90
- rg -uuu "pattern" # + search binary files
91
-
92
- # Specific overrides
93
- rg --hidden "pattern" # Include hidden files
94
- rg --no-ignore "pattern" # Ignore all ignore files
34
+ rg -t ts "pattern" # TypeScript only
35
+ rg -t py "pattern" # Python only
36
+ rg -T js "pattern" # Exclude JavaScript
37
+ rg -g "*.vue" "pattern" # By glob
38
+ rg -g "!*.test.ts" "pattern" # Exclude test files
39
+ rg -g "src/**/*.ts" "pattern" # Scoped glob
40
+ rg -g "*.ts" -g "*.vue" "pat" # Multiple globs
95
41
  ```
96
42
 
97
- ### Advanced Patterns
43
+ ## List Files (No Search)
44
+
98
45
  ```bash
99
- # Multiline search
100
- rg -U "start.*\n.*end"
46
+ rg --files # All files that would be searched
47
+ rg --files -g "*.ts" # Files matching glob
48
+ rg --files src/components/ # Files in directory
49
+ ```
101
50
 
102
- # PCRE2 regex (lookahead/lookbehind)
103
- rg -P "(?<=prefix)pattern(?=suffix)"
51
+ ## Bypass Filters
104
52
 
105
- # Replace in output (preview, doesn't modify files)
106
- rg "old" -r "new"
53
+ ```bash
54
+ rg -u "pattern" # Ignore .gitignore
55
+ rg -uu "pattern" # + include hidden files
56
+ rg -uuu "pattern" # + include binary files
57
+ rg --hidden "pat" # Include hidden files only
58
+ ```
107
59
 
108
- # With capture groups
109
- rg "fn (\w+)" -r "function $1"
60
+ ## Advanced
110
61
 
111
- # JSON output (for scripting)
112
- rg --json "pattern"
62
+ ```bash
63
+ rg -U "start.*\n.*end" # Multiline
64
+ rg -P "(?<=prefix)pattern(?=suffix)" # PCRE2 (lookahead/lookbehind)
65
+ rg "old" -r "new" # Replace preview (no file modification)
66
+ rg "fn (\w+)" -r "function $1" # Capture groups in replace
67
+ rg --json "pattern" # JSON output for scripting
113
68
  ```
114
69
 
115
70
  ## Common Recipes
116
71
 
117
- ### Find Function/Class Definitions
118
72
  ```bash
119
- # JavaScript/TypeScript
120
- rg "^(export )?(async )?(function|const|class) \w+"
121
- rg "^export (default )?(function|class)"
73
+ # Function/class definitions
74
+ rg "^(export )?(async )?(function|const|class) \w+" # JS/TS
75
+ rg "^(async )?def \w+|^class \w+" # Python
76
+ rg "^(public |private |protected )?(static )?function \w+" # PHP
122
77
 
123
- # Python
124
- rg "^(async )?def \w+|^class \w+"
125
-
126
- # PHP
127
- rg "^(public |private |protected )?(static )?(function) \w+"
128
- ```
129
-
130
- ### Find Imports/Requires
131
- ```bash
78
+ # Imports
132
79
  rg "^import .+ from"
133
80
  rg "require\(['\"]"
134
- ```
135
81
 
136
- ### Find TODOs/FIXMEs
137
- ```bash
82
+ # TODOs
138
83
  rg "TODO|FIXME|HACK|XXX" -g "!node_modules"
139
- ```
140
84
 
141
- ### Find Files by Extension
142
- ```bash
143
- rg --files -g "*.tsx" # All TSX files
144
- rg --files -g "*.{ts,tsx}" # TS and TSX
145
- rg --files -g "!*.test.*" # Exclude test files
146
- ```
85
+ # Files by extension
86
+ rg --files -g "*.{ts,tsx}"
87
+ rg --files -g "!*.test.*"
147
88
 
148
- ### Search and Replace Preview
149
- ```bash
150
- # See what would change (doesn't modify files)
89
+ # Search-replace preview
151
90
  rg "oldFunction" -r "newFunction" --passthru
152
91
  ```
153
92
 
154
93
  ## vs grep/find
155
94
 
156
- | Task | grep/find (avoid) | rg (prefer) |
157
- |------|-------------------|-------------|
158
- | Search text | `grep -r "pattern" .` | `rg "pattern"` |
95
+ | Task | avoid | prefer |
96
+ |------|-------|--------|
97
+ | Search text | `grep -r "pat" .` | `rg "pat"` |
159
98
  | Find files | `find . -name "*.ts"` | `rg --files -g "*.ts"` |
160
- | Case insensitive | `grep -ri "pattern" .` | `rg -i "pattern"` |
99
+ | Case insensitive | `grep -ri "pat" .` | `rg -i "pat"` |
161
100
  | Whole word | `grep -rw "word" .` | `rg -w "word"` |
162
- | Show context | `grep -r -C 3 "pattern" .` | `rg -C 3 "pattern"` |
163
- | List files only | `grep -rl "pattern" .` | `rg -l "pattern"` |
164
-
165
- ## Key Advantages
166
-
167
- 1. **Speed**: 2-10x faster than grep on large codebases
168
- 2. **Smart defaults**: Respects `.gitignore`, skips binary/hidden files
169
- 3. **Recursive by default**: No `-r` flag needed
170
- 4. **Better regex**: Rust regex engine, optional PCRE2
171
- 5. **Built-in file types**: `-t ts`, `-t py`, etc.
172
- 6. **Colored output**: Easy to read in terminal
101
+ | Context | `grep -r -C 3 "pat" .` | `rg -C 3 "pat"` |
102
+ | Files only | `grep -rl "pat" .` | `rg -l "pat"` |
173
103
 
174
104
  ## Configuration
175
105
 
176
- Create `~/.ripgreprc` for persistent settings:
177
106
  ```shell
178
- # Smart case (case-insensitive unless uppercase used)
107
+ # ~/.ripgreprc
179
108
  --smart-case
180
-
181
- # Max line length for display
182
109
  --max-columns=150
183
110
  --max-columns-preview
184
-
185
- # Include hidden files by default
186
- # --hidden
187
-
188
- # Custom file type
189
111
  --type-add
190
112
  web:*.{html,css,js,ts,vue}
191
113
  ```
192
114
 
193
- Set the config file path:
194
115
  ```bash
195
116
  export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"
196
- ```
197
-
198
- ## Type List
199
-
200
- View all built-in types:
201
- ```bash
202
- rg --type-list
117
+ rg --type-list # View all built-in types
203
118
  ```
204
119
 
205
120
  Common types: `ts`, `js`, `py`, `rust`, `go`, `java`, `php`, `ruby`, `css`, `html`, `json`, `yaml`, `md`, `sh`
@@ -5,31 +5,17 @@ description: "Functional Ruby patterns - Enumerable as FP toolkit, lambdas, free
5
5
 
6
6
  # Ruby FP
7
7
 
8
- Ruby is OOP-first, but its FP toolkit is excellent. The goal is **functional core, imperative shell** — pure methods for logic, OOP shell only where the framework demands it.
8
+ Ruby is OOP-first, but its FP toolkit is excellent. Goal: **functional core, imperative shell** — pure methods for logic, OOP shell only where the framework demands it.
9
9
 
10
- ## When to Use This Skill
10
+ ## Core Rules
11
11
 
12
- - Writing standalone Ruby scripts
13
- - Building Ruby utilities or service objects
14
- - Refactoring procedural Ruby toward composable units
15
- - Any Ruby code where framework conventions don't dictate the pattern
16
-
17
- ## Core Philosophy
18
-
19
- > Ruby's Enumerable is a functional toolkit hiding inside an OOP language.
20
-
21
- The architect's lens applied to Ruby:
22
- - **Pure methods** for logic (no instance variable mutation in calculation methods)
12
+ - **Pure methods** for logic — no instance variable mutation in calculation methods
23
13
  - **Enumerable over explicit loops** — `map/select/reduce` not `each` + accumulator
24
- - **Lambdas** for first-class functions (strict arity, `lambda?` is true, `return` is scoped)
14
+ - **Lambdas** (`->`) for first-class functions strict arity, scoped `return`
25
15
  - **freeze** for value objects and constants
26
16
  - **Functional core** isolated from I/O, database, external state
27
17
 
28
- Ruby will never be Haskell. Don't fight the language — compose with it.
29
-
30
- ## Enumerable: The FP Toolkit
31
-
32
- These are your primary tools. Prefer them over `each` + mutation.
18
+ ## Enumerable: Primary Tools
33
19
 
34
20
  ```ruby
35
21
  users = [
@@ -38,61 +24,40 @@ users = [
38
24
  { name: 'Carol', age: 25, active: true }
39
25
  ]
40
26
 
41
- # select filter
42
- adults = users.select { |u| u[:age] >= 18 }
43
-
44
- # map transform
45
- names = users.map { |u| u[:name] }
46
-
47
- # reduce accumulate
48
- total_age = users.reduce(0) { |sum, u| sum + u[:age] }
49
-
50
- # filter_map — select + transform in one pass (Ruby 2.7+, preferred)
51
- active_names = users.filter_map { |u| u[:name] if u[:active] }
52
-
53
- # flat_map — map + flatten
54
- tags = posts.flat_map { |p| p[:tags] }
55
-
56
- # each_with_object — build a hash/array without external mutation
57
- index = users.each_with_object({}) { |u, h| h[u[:name]] = u[:age] }
58
-
59
- # group_by — partition
60
- by_status = users.group_by { |u| u[:active] ? :active : :inactive }
27
+ adults = users.select { |u| u[:age] >= 18 }
28
+ names = users.map { |u| u[:name] }
29
+ total_age = users.reduce(0) { |sum, u| sum + u[:age] }
30
+ active_names = users.filter_map { |u| u[:name] if u[:active] } # Ruby 2.7+, preferred
31
+ tags = posts.flat_map { |p| p[:tags] }
32
+ index = users.each_with_object({}) { |u, h| h[u[:name]] = u[:age] }
33
+ by_status = users.group_by { |u| u[:active] ? :active : :inactive }
61
34
 
62
- # Chaining — functional pipeline
35
+ # Functional pipeline
63
36
  result = users
64
37
  .select { |u| u[:active] }
65
38
  .map { |u| u.merge(display_name: u[:name].upcase) }
66
39
  .sort_by { |u| u[:age] }
67
40
  ```
68
41
 
69
- **Rule**: If you find yourself writing `results = []; collection.each { |x| results << x if ... }`, reach for `filter_map` instead.
42
+ If writing `results = []; collection.each { |x| results << x if ... }` use `filter_map` instead.
70
43
 
71
44
  ## Lambdas vs Procs
72
45
 
73
- Use `lambda` (or `->`) for reusable, composable functions. Avoid bare `proc` for logic units.
74
-
75
46
  ```ruby
76
- # Lambda strict arity, scoped return, first-class function
77
- validate_age = ->(age) { age.is_a?(Integer) && age >= 0 && age <= 150 }
47
+ validate_age = ->(age) { age.is_a?(Integer) && age >= 0 && age <= 150 }
78
48
  normalize_email = ->(email) { email.to_s.strip.downcase }
79
49
 
80
50
  # Compose with >> (Ruby 2.6+)
81
51
  process_user = normalize_email >> method(:save_user)
82
52
 
83
- # Use as argument (higher-order functions)
84
- def transform_all(items, transformer)
85
- items.map(&transformer)
86
- end
87
-
53
+ # Higher-order functions
54
+ def transform_all(items, transformer) = items.map(&transformer)
88
55
  transform_all(emails, normalize_email)
89
56
 
90
- # Method references — convert instance/class methods to callables
91
- validator = method(:validate_age)
57
+ # Method references
92
58
  emails.map(&method(:normalize_email))
93
59
  ```
94
60
 
95
- **Lambda vs Proc differences that matter:**
96
61
  | | Lambda | Proc |
97
62
  |-|--------|------|
98
63
  | Arity | Strict (raises ArgumentError) | Loose (fills nil) |
@@ -102,27 +67,23 @@ emails.map(&method(:normalize_email))
102
67
  ## Immutability with freeze
103
68
 
104
69
  ```ruby
105
- # Freeze constants
106
70
  VALID_STATUSES = %w[active inactive pending].freeze
107
71
  DEFAULT_CONFIG = { timeout: 30, retries: 3 }.freeze
108
72
 
109
- # Value object pattern frozen struct
110
- UserRecord = Data.define(:name, :email, :age) # Ruby 3.2+
73
+ # Value object — Ruby 3.2+
74
+ UserRecord = Data.define(:name, :email, :age)
111
75
  user = UserRecord.new(name: 'Alice', email: 'alice@example.com', age: 30)
112
- # user is immutable — no setters
113
76
 
114
- # For older Ruby, use Struct with freeze
77
+ # Older Ruby
115
78
  Config = Struct.new(:host, :port, :timeout).new('localhost', 5432, 30).freeze
116
79
  ```
117
80
 
118
- **Rule**: Any hash/array used as a constant gets `.freeze`. Value objects use `Data.define` (Ruby 3.2+) or frozen `Struct`.
81
+ Any hash/array constant gets `.freeze`. Value objects use `Data.define` (3.2+) or frozen `Struct`.
119
82
 
120
83
  ## Functional Core / Imperative Shell
121
84
 
122
- The central pattern. Separate pure logic from I/O and external state.
123
-
124
85
  ```ruby
125
- # PURE CORE — no I/O, no DB, fully testable, no side effects
86
+ # PURE CORE — no I/O, fully testable
126
87
  module UserLogic
127
88
  def self.validate(attrs)
128
89
  errors = []
@@ -133,10 +94,7 @@ module UserLogic
133
94
  end
134
95
 
135
96
  def self.normalize(attrs)
136
- attrs.merge(
137
- name: attrs[:name].to_s.strip,
138
- email: attrs[:email].to_s.strip.downcase
139
- )
97
+ attrs.merge(name: attrs[:name].to_s.strip, email: attrs[:email].to_s.strip.downcase)
140
98
  end
141
99
 
142
100
  def self.prepare_for_save(raw_attrs)
@@ -150,21 +108,15 @@ end
150
108
  def create_user(raw_params)
151
109
  result = UserLogic.prepare_for_save(raw_params)
152
110
  return { success: false, errors: result[:errors] } unless result[:ok]
153
-
154
- user = User.create!(result[:attrs]) # database side effect here only
111
+ user = User.create!(result[:attrs])
155
112
  { success: true, user: user }
156
113
  end
157
114
  ```
158
115
 
159
- ## Pure Method Guidelines
160
-
161
- A method is pure when:
162
- 1. Same inputs always return same output
163
- 2. No mutation of instance variables during computation
164
- 3. No I/O (logging, DB, network) inside the calculation
116
+ ## Pure Method: Good vs Bad
165
117
 
166
118
  ```ruby
167
- # BAD — mutates state during calculation
119
+ # BAD — mutates instance variables during calculation
168
120
  def calculate_totals
169
121
  @subtotal = @items.sum { |i| i[:price] * i[:qty] }
170
122
  @tax = @subtotal * 0.08
@@ -182,155 +134,106 @@ end
182
134
  ## Composition Patterns
183
135
 
184
136
  ```ruby
185
- # Method chaining on custom objects — return self or new instance
137
+ # Immutable pipeline
186
138
  class Pipeline
187
- def initialize(steps = [])
188
- @steps = steps.freeze
189
- end
190
-
191
- def add(step)
192
- Pipeline.new(@steps + [step]) # returns new instance, immutable
193
- end
194
-
195
- def call(input)
196
- @steps.reduce(input) { |data, step| step.call(data) }
197
- end
139
+ def initialize(steps = []) = @steps = steps.freeze
140
+ def add(step) = Pipeline.new(@steps + [step])
141
+ def call(input) = @steps.reduce(input) { |data, step| step.call(data) }
198
142
  end
199
143
 
200
- # Lambda composition with >>
144
+ # Lambda composition
201
145
  sanitize = ->(s) { s.strip.downcase }
202
146
  validate = ->(s) { raise "empty" if s.empty?; s }
203
147
  normalize = sanitize >> validate
204
148
 
205
149
  # Callable objects (duck-typed lambdas)
206
- class Validator
207
- def call(value)
208
- value.is_a?(String) && !value.empty?
209
- end
210
- end
211
-
212
150
  validators = [Validator.new, ->(v) { v.length < 255 }]
213
151
  valid = validators.all? { |v| v.call(input) }
214
152
  ```
215
153
 
216
- ## Anti-Patterns to Avoid
154
+ ## Anti-Patterns
217
155
 
218
156
  ```ruby
219
- # BAD — accumulator mutation inside each
157
+ # BAD — accumulator mutation
220
158
  result = []
221
159
  items.each { |item| result << transform(item) if item[:active] }
222
160
  # GOOD
223
161
  result = items.filter_map { |item| transform(item) if item[:active] }
224
162
 
225
163
  # BAD — output parameter mutation
226
- def process(items, output)
227
- items.each { |i| output << i * 2 }
228
- end
229
- # GOOD — return new value
230
- def process(items)
231
- items.map { |i| i * 2 }
232
- end
164
+ def process(items, output) = items.each { |i| output << i * 2 }
165
+ # GOOD
166
+ def process(items) = items.map { |i| i * 2 }
233
167
 
234
- # BAD — complex logic inside a class with side effects mixed in
168
+ # BAD — mixed concerns
235
169
  def calculate_and_save
236
- total = @items.sum(&:price)
237
- @total = total # side effect
238
- DB.save(total) # side effect
239
- total
170
+ total = @items.sum(&:price); @total = total; DB.save(total); total
240
171
  end
241
- # GOOD — separate concerns
242
- total = calculate_total(@items) # pure
243
- update_total(total) # side effect isolated
172
+ # GOOD
173
+ total = calculate_total(@items) # pure
174
+ update_total(total) # side effect isolated
244
175
 
245
176
  # BAD — bare proc for reusable function
246
- adder = proc { |a, b| a + b } # loose arity, weird return behavior
177
+ adder = proc { |a, b| a + b }
247
178
  # GOOD
248
179
  adder = ->(a, b) { a + b }
249
180
  ```
250
181
 
251
- ## When to Use Classes
182
+ ## When to Use Classes vs Modules
252
183
 
253
- OOP is fine for: stateful objects that model real entities (User, Order, Connection), framework integration (ActiveRecord models, controllers), objects that encapsulate a lifecycle.
184
+ **Use classes for:** stateful entities (User, Order, Connection), framework integration (ActiveRecord), objects with lifecycle.
254
185
 
255
- Use modules with class methods (or plain lambdas/methods) for: pure logic, utilities, transformations, validators.
186
+ **Use modules with class methods for:** pure logic, utilities, transformations, validators.
256
187
 
257
188
  ```ruby
258
- # Module for pure logic — no instances needed
259
189
  module PriceCalculator
260
- def self.discount(price, tier)
261
- rates = { bronze: 0.05, silver: 0.10, gold: 0.20 }.freeze
262
- price * (1 - rates.fetch(tier, 0))
263
- end
190
+ RATES = { bronze: 0.05, silver: 0.10, gold: 0.20 }.freeze
191
+ def self.discount(price, tier) = price * (1 - RATES.fetch(tier, 0))
264
192
  end
265
193
 
266
- # Class for stateful lifecycle
267
194
  class ImportJob
268
- def initialize(source_db, config)
269
- @source_db = source_db
270
- @config = config
271
- end
272
-
273
- def run
274
- records = fetch_records # I/O
275
- processed = process(records) # pure
276
- persist(processed) # I/O
277
- end
278
-
195
+ def initialize(source_db, config) = (@source_db, @config = source_db, config)
196
+ def run = persist(process(fetch_records))
279
197
  private
280
-
281
- def process(records)
282
- records
283
- .select { |r| valid?(r) }
284
- .map { |r| transform(r) }
285
- end
198
+ def process(records) = records.select { |r| valid?(r) }.map { |r| transform(r) }
286
199
  end
287
200
  ```
288
201
 
289
- ## Security Notes for Standalone Scripts
290
-
291
- - **Never interpolate external input into shell commands** — use `Open3.capture3` with array args, not `system("cmd #{input}")`
292
- - **Never interpolate input into SQL** — use parameterized queries or the driver's escape method
293
- - **ENV for credentials** — never hardcode; raise if missing: `ENV.fetch('DB_PASSWORD')`
202
+ ## Security (Standalone Scripts)
294
203
 
295
204
  ```ruby
296
205
  # BAD — shell injection
297
206
  system("convert #{filename} output.png")
298
-
299
207
  # GOOD — array form, no shell expansion
300
- require 'open3'
301
208
  stdout, stderr, status = Open3.capture3('convert', filename, 'output.png')
302
209
 
303
210
  # BAD — hardcoded credential
304
211
  client = Mysql2::Client.new(password: 'secret123')
305
-
306
212
  # GOOD — fail loudly if not set
307
213
  client = Mysql2::Client.new(password: ENV.fetch('MYSQL_PASSWORD'))
308
214
  ```
309
215
 
310
- ## Quick Reference: When to Reach for What
216
+ - Never interpolate external input into shell commands
217
+ - Never interpolate input into SQL — use parameterized queries
218
+ - Use `ENV.fetch` for credentials — raises KeyError if missing
219
+
220
+ ## Quick Reference
311
221
 
312
222
  | Need | Use |
313
223
  |------|-----|
314
- | Transform a collection | `map` |
315
- | Filter a collection | `select` / `reject` |
316
- | Transform + filter in one pass | `filter_map` |
317
- | Accumulate to a single value | `reduce` / `inject` |
318
- | Build a hash from a collection | `each_with_object` / `to_h` |
319
- | Group by a property | `group_by` |
224
+ | Transform collection | `map` |
225
+ | Filter collection | `select` / `reject` |
226
+ | Transform + filter | `filter_map` |
227
+ | Accumulate to single value | `reduce` / `inject` |
228
+ | Build hash from collection | `each_with_object` / `to_h` |
229
+ | Group by property | `group_by` |
320
230
  | Reusable function | `lambda` / `->` |
321
231
  | Compose functions | `>>` operator |
322
232
  | Immutable constant | `.freeze` |
323
233
  | Immutable value object | `Data.define` (Ruby 3.2+) or frozen `Struct` |
324
234
  | Pure logic module | `module Foo; def self.method...` |
325
235
 
326
- ## When to Load Reference Files
327
-
328
- ### FP Patterns Deep Dive
329
- **File**: [`references/patterns.md`](references/patterns.md)
330
- **Load when**: Need advanced composition, lazy enumerables, currying, memoization
331
- **Contains**: Lazy evaluation, `Comparable`/`Enumerable` mixin, memoization patterns, full pipeline example
236
+ ## Reference Files
332
237
 
333
- ### Security Examples
334
- **File**: [`references/security.md`](references/security.md)
335
- **Load when**: Working with external input, SQL, shell commands, file operations
336
- **Contains**: SQL parameterization, shell safety, input validation, ENV credential management
238
+ - **[`references/patterns.md`](references/patterns.md)** — Lazy enumerables, currying, memoization, advanced composition. Load when: need advanced FP patterns.
239
+ - **[`references/security.md`](references/security.md)** — SQL parameterization, shell safety, input validation. Load when: working with external input.