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
@@ -5,158 +5,68 @@ description: "Python FP principles with anti-over-engineering focus - Simple > C
5
5
 
6
6
  # Python Functional Programming
7
7
 
8
- Core functional programming principles for Python with anti-over-engineering enforcement. Python is a multi-paradigm language with solid FP support but it's not Haskell. This skill teaches you to be functional in spirit, Pythonic in expression.
8
+ FP in spirit, Pythonic in expression. Python is not Haskelldon't fight the language.
9
9
 
10
- ## When to Use This Skill
10
+ ## CRITICAL: Anti-Over-Engineering
11
11
 
12
- - Implementing pure, testable Python functions
13
- - Data transformation pipelines (pandas, polars, ML preprocessing)
14
- - Need FP architectural guidance for Python
15
- - Preventing over-engineering and custom FP utility creation
16
- - Comprehensive testing strategies with pytest
17
- - Evidence-based performance optimization
18
-
19
- ## CRITICAL: Anti-Over-Engineering (PRIMARY FOCUS)
20
-
21
- **Core Principle**: "Simple > Complex | Evidence > Assumptions"
22
-
23
- > **Clarification**: This skill prevents CREATING custom FP utility functions (pipe, compose, curry, monads) to make Python "feel" like Haskell. Using established libraries (toolz, pandas, itertools, etc.) is perfectly fine. FP is a mindset — pure functions, immutability, composition — not a rigid API signature.
24
-
25
- ### Don't Create Custom FP Utilities
12
+ **Never create custom FP utilities** (`pipe`, `compose`, `curry`, custom monads). Use native patterns or established libraries (`toolz`, `itertools`, `functools`).
26
13
 
27
14
  ```python
28
- # DON'T CREATE: pipe() utility
15
+ # DON'T: pipe() utility
29
16
  def pipe(value, *functions):
30
17
  for fn in functions:
31
18
  value = fn(value)
32
19
  return value
33
20
 
34
- # INSTEAD: Native function calls with early returns
21
+ # DO: native early returns
35
22
  def validate_user(user_data: dict) -> dict:
36
- required_check = validate_required(["email", "name"], user_data)
37
- if not required_check["valid"]:
38
- return required_check
39
-
40
- email_check = validate_email(user_data)
41
- if not email_check["valid"]:
42
- return email_check
43
-
23
+ required = validate_required(["email", "name"], user_data)
24
+ if not required["valid"]:
25
+ return required
26
+ email = validate_email(user_data)
27
+ if not email["valid"]:
28
+ return email
44
29
  return validate_name_length(user_data)
45
30
 
46
- # DON'T CREATE: compose() utility
47
- def compose(*fns):
48
- def composed(x):
49
- for fn in reversed(fns):
50
- x = fn(x)
51
- return x
52
- return composed
53
-
54
- # INSTEAD: Direct function calls
55
- def process_data(raw: dict) -> dict:
56
- normalized = normalize(raw)
57
- validated = validate(normalized)
58
- return transform(validated)
59
-
60
- # DON'T CREATE: curry() utility
61
- # INSTEAD: functools.partial or closures
31
+ # DON'T: compose() or curry() utilities
32
+ # DO: direct calls or functools.partial
62
33
  from functools import partial
63
-
64
34
  validate_min_length = partial(validate_length, min_len=3)
65
35
 
66
- # Or closures for more complex cases
67
- def create_validator(rules: list) -> callable:
68
- def validate(value):
69
- errors = [r["message"] for r in rules if not r["check"](value)]
70
- return {"valid": True} if not errors else {"valid": False, "errors": errors}
71
- return validate
72
-
73
- # DON'T CREATE: Custom monads (Maybe, Either, IO)
74
- # INSTEAD: Native error handling with result dicts
36
+ # DON'T: Custom monads (Maybe, Either, IO)
37
+ # DO: result dicts
75
38
  def get_user(user_id: int, db) -> dict:
76
39
  try:
77
- user = db.find(user_id)
78
- return {"success": True, "data": user}
40
+ return {"success": True, "data": db.find(user_id)}
79
41
  except Exception as e:
80
42
  return {"success": False, "error": str(e)}
81
43
  ```
82
44
 
83
- ### Context-Appropriate Complexity
84
-
85
- ```python
86
- # CLI Script: Simple and direct
87
- def process_file(file_path: str) -> list[str]:
88
- with open(file_path) as f:
89
- return [line.upper() for line in f if line.strip()]
45
+ Match complexity to context: CLI script ≠ production service ≠ data pipeline.
90
46
 
91
- # Production Service: Appropriate error handling
92
- def process_file(file_path: str, logger) -> dict:
93
- try:
94
- with open(file_path) as f:
95
- lines = [line.strip() for line in f if line.strip()]
96
- logger.info("File processed", extra={"path": file_path, "lines": len(lines)})
97
- return {"success": True, "data": [line.upper() for line in lines]}
98
- except OSError as e:
99
- logger.error("File processing failed", extra={"path": file_path, "error": str(e)})
100
- return {"success": False, "error": str(e)}
101
-
102
- # Data Pipeline: Composable transformations
103
- def create_file_processor(transforms: list[callable]):
104
- compiled = [compile_transform(t) for t in transforms]
105
-
106
- def process(file_path: str, logger) -> dict:
107
- with open(file_path) as f:
108
- data = f.read()
109
- for transform in compiled:
110
- data = transform(data)
111
- return {"success": True, "data": data}
112
-
113
- return process
114
- ```
115
-
116
- ## Core FP Patterns (Error-Preventing Essentials)
47
+ ## Core Patterns
117
48
 
118
49
  ### 1. Purity and Side Effect Isolation
119
50
 
120
- **Rule**: Separate business logic from side effects.
51
+ Separate business logic from side effects.
121
52
 
122
53
  ```python
123
- # IMPURE - side effects mixed with logic
124
- def calculate_total(items: list[dict]) -> float:
125
- print("Processing items") # Side effect
126
- global running_total
127
- running_total += sum(i["price"] for i in items) # Mutation
128
- return running_total
129
-
130
- # PURE: business logic
54
+ # Pure: business logic
131
55
  def calculate_total(items: list[dict]) -> float:
132
56
  return sum(item["price"] for item in items)
133
57
 
134
- # ISOLATED: side effects separate
58
+ # Side effects separate
135
59
  def log_and_calculate(items: list[dict], logger) -> float:
136
- total = calculate_total(items) # Pure calculation
137
- logger.info(f"Total: {total}") # Side effect isolated
60
+ total = calculate_total(items)
61
+ logger.info(f"Total: {total}")
138
62
  return total
139
63
  ```
140
64
 
141
- **Benefits**:
142
- - 100% testable with all edge cases
143
- - Predictable behavior and debugging
144
- - Safe for `multiprocessing` (pure functions serialize trivially)
145
- - Enables memoization via `@lru_cache`
65
+ Pure functions: 100% testable, `@lru_cache`-able, safe for `multiprocessing`.
146
66
 
147
67
  ### 2. Composition Over Inheritance
148
68
 
149
- **Rule**: Build complex behavior from simple functions.
150
-
151
69
  ```python
152
- # Class hierarchy approach (avoid)
153
- class BaseValidator:
154
- def validate(self, value): raise NotImplementedError
155
-
156
- class EmailValidator(BaseValidator):
157
- def validate(self, value): ...
158
-
159
- # Function composition (no utilities needed)
160
70
  def validate_required(value) -> bool:
161
71
  return value is not None and value != ""
162
72
 
@@ -166,7 +76,6 @@ def validate_email(value: str) -> bool:
166
76
  def validate_length(min_len: int, max_len: int):
167
77
  return lambda value: min_len <= len(value) <= max_len
168
78
 
169
- # Simple composition without pipe() utility
170
79
  def validate_user_email(email: str) -> dict:
171
80
  if not validate_required(email):
172
81
  return {"valid": False, "error": "Required"}
@@ -177,101 +86,67 @@ def validate_user_email(email: str) -> dict:
177
86
  return {"valid": True}
178
87
  ```
179
88
 
180
- ### 3. Dependency Injection Through Parameters
181
-
182
- **Rule**: Pass dependencies explicitly, avoid global state.
89
+ ### 3. Dependency Injection via Parameters
183
90
 
184
91
  ```python
185
- # Hidden dependencies, hard to test
92
+ # Hidden deps untestable
186
93
  def save_user(user_data: dict) -> dict:
187
- hashed = bcrypt.hash(user_data["password"]) # Hidden
188
- return database.save({**user_data, "password": hashed}) # Hidden
94
+ hashed = bcrypt.hash(user_data["password"])
95
+ return database.save({**user_data, "password": hashed})
189
96
 
190
- # Explicit dependencies, fully testable
97
+ # Explicit deps fully testable
191
98
  def save_user(user_data: dict, hasher, database) -> dict:
192
99
  hashed = hasher.hash(user_data["password"])
193
100
  return database.save({**user_data, "password": hashed})
194
101
 
195
- # Factory for repeated use (closure-based DI)
102
+ # Closure-based DI for repeated use
196
103
  def create_user_service(hasher, database):
197
104
  def save(user_data: dict) -> dict:
198
105
  return save_user(user_data, hasher, database)
199
-
200
106
  def find(user_id: int) -> dict:
201
107
  return database.find_by_id(user_id)
202
-
203
108
  return {"save": save, "find": find}
204
109
  ```
205
110
 
206
- ### 4. Immutability Patterns
207
-
208
- **Rule**: Don't mutate input data. Create new structures.
111
+ ### 4. Immutability
209
112
 
210
113
  ```python
211
114
  from dataclasses import dataclass, replace, field
212
115
  from typing import NamedTuple
213
116
 
214
- # Frozen dataclasses — the primary immutable record
215
117
  @dataclass(frozen=True)
216
118
  class User:
217
119
  name: str
218
120
  email: str
219
121
  settings: dict = field(default_factory=dict)
220
122
 
221
- # "Update" via replace() — returns new instance
222
123
  def update_email(user: User, new_email: str) -> User:
223
- return replace(user, email=new_email)
224
-
225
- # NamedTuple — lighter weight alternative
226
- class Point(NamedTuple):
227
- x: float
228
- y: float
229
-
230
- def translate(point: Point, dx: float, dy: float) -> Point:
231
- return Point(point.x + dx, point.y + dy)
124
+ return replace(user, email=new_email) # new instance
232
125
 
233
- # Dict immutability — spread with {**d}
126
+ # Dict/list — spread, don't mutate
234
127
  def update_settings(user: dict, settings: dict) -> dict:
235
128
  return {**user, "settings": {**user.get("settings", {}), **settings}}
236
129
 
237
- # List operations without mutation
238
- def add_item(items: list, new_item) -> list:
239
- return [*items, new_item]
240
-
241
- def remove_item(items: list[dict], item_id: int) -> list[dict]:
242
- return [i for i in items if i["id"] != item_id]
243
-
244
130
  def update_item(items: list[dict], item_id: int, updates: dict) -> list[dict]:
245
131
  return [{**i, **updates} if i["id"] == item_id else i for i in items]
246
132
  ```
247
133
 
248
- > **Important**: `frozen=True` is shallow — it prevents attribute reassignment but doesn't freeze mutable attribute values (like dicts or lists inside). For deep immutability, use `pyrsistent` or keep nested values as tuples/frozensets.
134
+ > `frozen=True` is shallow — nested dicts/lists remain mutable. Use `pyrsistent` or `MappingProxyType` for deep immutability.
249
135
 
250
136
  ## Python-Specific Patterns
251
137
 
252
- ### Comprehensions Over map/filter (Pythonic FP)
138
+ ### Comprehensions (Prefer Over map/filter)
253
139
 
254
140
  ```python
255
- # map/filter style (less Pythonic)
256
- active_names = list(map(
257
- lambda u: u["name"],
258
- filter(lambda u: u["active"], users)
259
- ))
260
-
261
- # Comprehension style (Pythonic FP)
141
+ # Pythonic FP
262
142
  active_names = [u["name"] for u in users if u["active"]]
263
-
264
- # Dict comprehension for transformations
265
143
  name_by_id = {u["id"]: u["name"] for u in users}
266
-
267
- # Generator expression for lazy evaluation (large data)
268
144
  totals = sum(item["price"] for item in items if item["taxable"])
269
145
  ```
270
146
 
271
147
  ### Generators for Lazy Pipelines
272
148
 
273
149
  ```python
274
- # Generators are Python's answer to lazy evaluation
275
150
  def read_records(path: str):
276
151
  with open(path) as f:
277
152
  for line in f:
@@ -288,47 +163,34 @@ def transform(records):
288
163
 
289
164
  # Compose lazily — no intermediate lists
290
165
  pipeline = transform(filter_valid(read_records("data.csv")))
291
- results = list(pipeline) # Materializes only when needed
292
166
  ```
293
167
 
294
- ### itertools and functools (The FP Standard Library)
168
+ ### itertools / functools
295
169
 
296
170
  ```python
297
171
  from functools import partial, lru_cache, reduce
298
- from itertools import chain, groupby, islice, takewhile
299
- from operator import itemgetter
172
+ from itertools import groupby
173
+ from operator import itemgetter, mul
300
174
 
301
- # partial — the Pythonic way to "curry"
175
+ # partial — Pythonic curry
302
176
  multiply_by_tax = partial(lambda rate, price: price * (1 + rate), 0.08)
303
177
 
304
- # lru_cache — memoize pure functions (MUST be pure!)
178
+ # lru_cache — memoize pure functions only
305
179
  @lru_cache(maxsize=256)
306
180
  def fibonacci(n: int) -> int:
307
- if n < 2:
308
- return n
309
- return fibonacci(n - 1) + fibonacci(n - 2)
310
-
311
- # operator module — eliminate trivial lambdas
312
- from operator import itemgetter, attrgetter
181
+ return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)
313
182
 
183
+ # operator — eliminate trivial lambdas
314
184
  sorted_by_name = sorted(users, key=itemgetter("name"))
315
- # vs: sorted(users, key=lambda u: u["name"])
316
-
317
- # itertools for complex transformations
318
- from itertools import groupby
319
185
 
320
186
  def group_by_category(items: list[dict]) -> dict:
321
187
  sorted_items = sorted(items, key=itemgetter("category"))
322
- return {
323
- k: list(v)
324
- for k, v in groupby(sorted_items, key=itemgetter("category"))
325
- }
188
+ return {k: list(v) for k, v in groupby(sorted_items, key=itemgetter("category"))}
326
189
  ```
327
190
 
328
- ### Pattern Matching (Python 3.10+)
191
+ ### Pattern Matching (3.10+)
329
192
 
330
193
  ```python
331
- # Structural pattern matching for clean dispatch
332
194
  def process_event(event: dict) -> dict:
333
195
  match event:
334
196
  case {"type": "click", "target": target}:
@@ -339,48 +201,31 @@ def process_event(event: dict) -> dict:
339
201
  return handle_error(code, msg)
340
202
  case _:
341
203
  return {"error": f"Unknown event type: {event.get('type')}"}
342
-
343
- # Pattern matching with guards
344
- def categorize_score(score: int) -> str:
345
- match score:
346
- case n if n >= 90: return "A"
347
- case n if n >= 80: return "B"
348
- case n if n >= 70: return "C"
349
- case _: return "F"
350
204
  ```
351
205
 
352
- ### Type Hints for FP Clarity
206
+ ### Type Hints
353
207
 
354
208
  ```python
355
209
  from typing import TypeVar, Callable, Optional
356
- from collections.abc import Iterable, Iterator
210
+ from collections.abc import Iterable
357
211
 
358
212
  T = TypeVar("T")
359
213
  U = TypeVar("U")
360
214
 
361
- # Type hints make pure function contracts explicit
362
215
  def transform_all(items: Iterable[T], fn: Callable[[T], U]) -> list[U]:
363
216
  return [fn(item) for item in items]
364
217
 
365
- # Union types (3.10+) for result patterns
366
- def divide(a: float, b: float) -> dict:
367
- if b == 0:
368
- return {"success": False, "error": "Division by zero"}
369
- return {"success": True, "data": a / b}
370
-
371
- # Optional signals nullable return
372
218
  def find_user(users: list[dict], user_id: int) -> Optional[dict]:
373
219
  return next((u for u in users if u["id"] == user_id), None)
374
220
  ```
375
221
 
376
- ## Data Science / ML Patterns
222
+ ## Data Science / ML
377
223
 
378
- ### pandas pipe() for Transformation Chains
224
+ ### pandas pipe()
379
225
 
380
226
  ```python
381
227
  import pandas as pd
382
228
 
383
- # Each function: DataFrame in, DataFrame out (pure transforms)
384
229
  def clean_nulls(df: pd.DataFrame) -> pd.DataFrame:
385
230
  return df.dropna(subset=["email", "name"])
386
231
 
@@ -392,272 +237,121 @@ def add_age_group(df: pd.DataFrame) -> pd.DataFrame:
392
237
  labels = ["youth", "young_adult", "adult", "senior"]
393
238
  return df.assign(age_group=pd.cut(df["age"], bins=bins, labels=labels))
394
239
 
395
- # Compose via pipe() — FP pipeline, Pythonic syntax
396
- result = (
397
- raw_df
398
- .pipe(clean_nulls)
399
- .pipe(normalize_names)
400
- .pipe(add_age_group)
401
- )
240
+ result = raw_df.pipe(clean_nulls).pipe(normalize_names).pipe(add_age_group)
402
241
  ```
403
242
 
404
- ### Pure Feature Engineering
405
-
406
- ```python
407
- # Pure transformation functions for ML pipelines
408
- def extract_features(df: pd.DataFrame) -> pd.DataFrame:
409
- return df.assign(
410
- word_count=df["text"].str.split().str.len(),
411
- has_url=df["text"].str.contains(r"https?://", regex=True),
412
- text_length=df["text"].str.len(),
413
- )
414
-
415
- # scikit-learn FunctionTransformer for pipeline integration
416
- from sklearn.preprocessing import FunctionTransformer
417
- from sklearn.pipeline import Pipeline
418
-
419
- feature_extractor = FunctionTransformer(extract_features)
420
-
421
- pipeline = Pipeline([
422
- ("features", feature_extractor),
423
- ("scaler", StandardScaler()),
424
- ("model", LogisticRegression()),
425
- ])
426
- ```
427
-
428
- ### Polars (FP-Friendly Alternative to pandas)
243
+ ### Polars (FP-native)
429
244
 
430
245
  ```python
431
246
  import polars as pl
432
247
 
433
- # Polars expressions are lazy and composable by design
434
248
  result = (
435
- pl.scan_csv("data.csv") # Lazy — nothing runs yet
249
+ pl.scan_csv("data.csv")
436
250
  .filter(pl.col("status") == "active")
437
251
  .with_columns(
438
252
  full_name=pl.col("first_name") + " " + pl.col("last_name"),
439
- age_group=pl.when(pl.col("age") < 30).then(pl.lit("young"))
440
- .otherwise(pl.lit("senior")),
253
+ age_group=pl.when(pl.col("age") < 30).then(pl.lit("young")).otherwise(pl.lit("senior")),
441
254
  )
442
255
  .group_by("department")
443
256
  .agg(pl.col("salary").mean().alias("avg_salary"))
444
- .collect() # Execute the entire plan at once
257
+ .collect()
445
258
  )
446
259
  ```
447
260
 
448
261
  ## Result Type Pattern
449
262
 
450
263
  ```python
451
- # Standard result shape — consistent across the codebase
452
264
  def success(data=None) -> dict:
453
265
  return {"success": True, "data": data}
454
266
 
455
267
  def failure(error: str) -> dict:
456
268
  return {"success": False, "error": error}
457
269
 
458
- # Chain results with early return
459
270
  def process_order(order: dict) -> dict:
460
271
  validated = validate_order(order)
461
272
  if not validated["success"]:
462
273
  return validated
463
-
464
274
  priced = calculate_pricing(validated["data"])
465
275
  if not priced["success"]:
466
276
  return priced
467
-
468
277
  return submit_order(priced["data"])
469
278
  ```
470
279
 
471
- ## Testing Essentials (Enabled by Purity)
472
-
473
- **Philosophy**: Pure functions enable testing all edge cases systematically.
280
+ ## Testing
474
281
 
475
282
  ```python
476
283
  import pytest
477
284
 
478
- # Parametrized testing — the FP way
479
285
  @pytest.mark.parametrize("price,rate,expected", [
480
286
  (100.0, 0.1, 10.0),
481
287
  (50.0, 0.2, 10.0),
482
288
  (0.0, 0.1, 0.0),
483
- (100.0, 0.0, 0.0),
484
289
  ])
485
290
  def test_calculate_discount(price, rate, expected):
486
291
  assert calculate_discount(price, rate) == expected
487
292
 
488
- # Edge case testing — pure functions handle all inputs
489
- @pytest.mark.parametrize("invalid_input", [None, "", [], {}, 0])
490
- def test_validate_required_rejects_invalid(invalid_input):
491
- assert not validate_required(invalid_input)
492
-
493
- # Testing result chains
494
- def test_process_order_validation_failure():
495
- result = process_order({"items": []})
496
- assert not result["success"]
497
- assert "empty" in result["error"].lower()
498
-
499
- # Frozen dataclass testability
500
293
  def test_update_email_returns_new_instance():
501
294
  user = User(name="Alice", email="old@test.com")
502
295
  updated = update_email(user, "new@test.com")
503
296
  assert updated.email == "new@test.com"
504
- assert user.email == "old@test.com" # Original unchanged
297
+ assert user.email == "old@test.com" # original unchanged
505
298
  ```
506
299
 
507
- ## Performance Patterns (Evidence-Based)
508
-
509
- **IMPORTANT**: Optimize only when needed with evidence.
510
-
511
- ### Memoization for Expensive Pure Functions
300
+ ## Performance (Evidence-Based Only)
512
301
 
513
302
  ```python
514
- from functools import lru_cache
515
-
516
- # ONLY memoize pure functions with hashable arguments
303
+ # Memoize pure functions with hashable args
517
304
  @lru_cache(maxsize=128)
518
305
  def parse_template(template: str) -> dict:
519
- # Expensive parsing — cached after first call
520
306
  return heavy_parse(template)
521
307
 
522
- # For unhashable args, manually cache
523
- _cache = {}
524
- def get_config(key: str) -> dict:
525
- if key not in _cache:
526
- _cache[key] = load_config(key)
527
- return _cache[key]
528
- ```
529
-
530
- ### Generator Pipelines for Large Data
531
-
532
- ```python
533
- # Memory-efficient: processes one record at a time
308
+ # Batch generator for large data
534
309
  def process_large_file(path: str):
535
- records = read_records(path) # Generator
536
- valid = filter_valid(records) # Generator
537
- transformed = transform(valid) # Generator
538
-
539
- # Only materializes in batches
310
+ pipeline = transform(filter_valid(read_records(path)))
540
311
  batch = []
541
- for record in transformed:
312
+ for record in pipeline:
542
313
  batch.append(record)
543
314
  if len(batch) >= 1000:
544
315
  yield batch
545
316
  batch = []
546
317
  if batch:
547
318
  yield batch
548
- ```
549
-
550
- ### multiprocessing with Pure Functions
551
-
552
- ```python
553
- from multiprocessing import Pool
554
319
 
555
320
  # Pure functions parallelize trivially
556
- def process_item(item: dict) -> dict:
557
- return {**item, "score": compute_score(item)}
558
-
559
- # No shared state, no locks, no bugs
321
+ from multiprocessing import Pool
560
322
  with Pool(4) as pool:
561
323
  results = pool.map(process_item, items)
562
324
  ```
563
325
 
564
- ## Python-Specific Gotchas
565
-
566
- ### Recursion Limit
326
+ ## Gotchas
567
327
 
328
+ **Recursion limit** — no tail call optimization. Use `reduce` for large inputs:
568
329
  ```python
569
- # Python has a 1000-call recursion limit. No tail call optimization.
570
- # DON'T: recursive algorithms for large inputs
571
- def factorial(n):
572
- return 1 if n <= 1 else n * factorial(n - 1) # Blows up at n > 1000
573
-
574
- # DO: iterative or reduce
575
330
  from functools import reduce
576
331
  from operator import mul
577
-
578
332
  def factorial(n: int) -> int:
579
333
  return reduce(mul, range(1, n + 1), 1)
580
334
  ```
581
335
 
582
- ### Mutable Default Arguments
583
-
336
+ **Mutable defaults** — never use mutable default args:
584
337
  ```python
585
- # NEVER use mutable defaults
586
- def add_item(item, items=[]): # BUG: shared mutable default
587
- items.append(item)
588
- return items
589
-
590
- # INSTEAD: None sentinel
591
338
  def add_item(item, items=None):
592
- items = items if items is not None else []
593
- return [*items, item] # Return new list, don't mutate
594
- ```
595
-
596
- ### Shallow vs Deep Copy
597
-
598
- ```python
599
- # frozen=True is SHALLOW
600
- @dataclass(frozen=True)
601
- class Config:
602
- settings: dict # This dict is still mutable!
603
-
604
- config = Config(settings={"debug": True})
605
- # config.settings = {} # FrozenInstanceError
606
- config.settings["debug"] = False # This WORKS — shallow freeze
607
-
608
- # For deep immutability: use tuples, frozensets, or pyrsistent
609
- from types import MappingProxyType
610
-
611
- def freeze_dict(d: dict):
612
- return MappingProxyType(d) # Read-only view
339
+ return [*(items or []), item]
613
340
  ```
614
341
 
615
- ## Quality Gates (Pre-Implementation Checklist)
616
-
617
- 1. **"Can this be pure?"** - Separate business logic from side effects
618
- 2. **"Can this use native patterns?"** - Comprehensions, generators, functools, itertools
619
- 3. **"Can this be simplified?"** - Choose simple solution over complex abstraction
620
- 4. **"Is this complexity justified?"** - Evidence-based complexity decisions
621
- 5. **"Is this testable?"** - Pure functions enable comprehensive testing
622
- 6. **"Are type hints used?"** - Type hints on all public function signatures
623
-
624
- ## When to Load Reference Files
625
-
626
- ### Deep Principles and Explanations
627
- **File**: `references/core-principles.md`
628
- **Load when**:
629
- - Learning mode or explaining WHY behind patterns
630
- - Making architectural decisions
631
- - Need complete Result Type patterns
632
- - Anti-pattern recognition details
633
- - Python-specific FP philosophy deep-dive
634
-
635
- ### Testing Methodology
636
- **File**: `references/testing-patterns.md`
637
- **Load when**:
638
- - Building comprehensive test suites with pytest
639
- - Improving test coverage
640
- - Edge case analysis and boundary testing
641
- - Testing data pipelines and ML code
642
- - Property-based testing with Hypothesis
643
-
644
- ### Working Examples
645
- **Directory**: `examples/`
646
- **Load when**:
647
- - Need complete working code
648
- - Integration examples
649
- - Learning implementation patterns
650
-
651
- ## Integration with Domain Skills
652
-
653
- This core skill provides the foundation for:
342
+ **Shallow freeze** `frozen=True` doesn't freeze nested dicts. Use `MappingProxyType` or `pyrsistent` for deep immutability.
654
343
 
655
- - **py-fp-django**: Django patterns with FP principles (future)
656
- - **py-fp-fastapi**: FastAPI patterns with FP principles (future)
657
- - **py-fp-ml**: ML/data science patterns with FP principles (future)
344
+ ## Quality Gates
658
345
 
659
- Each domain skill references this core and adds domain-specific patterns.
346
+ 1. Can this be pure? separate logic from side effects
347
+ 2. Can this use native patterns? — comprehensions, generators, functools, itertools
348
+ 3. Is this complexity justified? — evidence-based decisions only
349
+ 4. Are type hints used? — all public function signatures
660
350
 
661
- ## Philosophy
351
+ ## References
662
352
 
663
- *"Pure functions, Pythonic patterns, type hints, appropriate complexity, and comprehensive testing for maintainable, predictable code. Be functional in spirit, Pythonic in expression — don't fight the language, work with it."*
353
+ | Resource | Load when |
354
+ |----------|-----------|
355
+ | `references/core-principles.md` | Learning mode, architecture decisions, anti-pattern recognition |
356
+ | `references/testing-patterns.md` | Building test suites, Hypothesis property-based testing |
357
+ | `examples/` | Complete working code, integration examples |