ima-claude 2.9.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +463 -0
  3. package/dist/cli.js +1064 -0
  4. package/package.json +49 -0
  5. package/platforms/claude/adapter.ts +115 -0
  6. package/platforms/junie/adapter.ts +254 -0
  7. package/platforms/junie/agents-template.md +113 -0
  8. package/platforms/junie/hook-translations.md +84 -0
  9. package/platforms/shared/detector.ts +27 -0
  10. package/platforms/shared/installer.ts +202 -0
  11. package/platforms/shared/types.ts +78 -0
  12. package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
  13. package/plugins/ima-claude/agents/explorer.md +30 -0
  14. package/plugins/ima-claude/agents/implementer.md +30 -0
  15. package/plugins/ima-claude/agents/memory.md +42 -0
  16. package/plugins/ima-claude/agents/reviewer.md +53 -0
  17. package/plugins/ima-claude/agents/tester.md +33 -0
  18. package/plugins/ima-claude/agents/wp-developer.md +46 -0
  19. package/plugins/ima-claude/hooks/README.md +145 -0
  20. package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
  21. package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
  22. package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
  23. package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
  24. package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
  25. package/plugins/ima-claude/hooks/docs_organization.py +104 -0
  26. package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
  27. package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
  28. package/plugins/ima-claude/hooks/hook_logger.py +69 -0
  29. package/plugins/ima-claude/hooks/hooks.json +239 -0
  30. package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
  31. package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
  32. package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
  33. package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
  34. package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
  35. package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
  36. package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
  37. package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
  38. package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
  39. package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
  40. package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
  41. package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
  42. package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
  43. package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
  44. package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
  45. package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
  46. package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
  47. package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
  48. package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
  49. package/plugins/ima-claude/personalities/README.md +45 -0
  50. package/plugins/ima-claude/personalities/enable-40k.md +69 -0
  51. package/plugins/ima-claude/personalities/enable-templars.md +69 -0
  52. package/plugins/ima-claude/skills/.research-summary.md +340 -0
  53. package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
  54. package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
  55. package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
  56. package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
  57. package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
  58. package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
  59. package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
  60. package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
  61. package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
  62. package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
  63. package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
  64. package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
  65. package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
  66. package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
  67. package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
  68. package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
  69. package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
  70. package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
  71. package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
  72. package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
  73. package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
  74. package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
  75. package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
  76. package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
  77. package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
  78. package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
  79. package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
  80. package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
  81. package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
  82. package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
  83. package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
  84. package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
  85. package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
  86. package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
  87. package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
  88. package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
  89. package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
  90. package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
  91. package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
  92. package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
  93. package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
  94. package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
  95. package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
  96. package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
  97. package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
  98. package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
  99. package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
  100. package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
  101. package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
  102. package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
  103. package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
  104. package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
  105. package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
  106. package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
  107. package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
  108. package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
  109. package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
  110. package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
  111. package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
  112. package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
  113. package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
  114. package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
  115. package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
  116. package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
  117. package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
  118. package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
  119. package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
  120. package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
  121. package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
  122. package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
  123. package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
  124. package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
  125. package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
  126. package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
  127. package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
  128. package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
  129. package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
  130. package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
  131. package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
  132. package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
  133. package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
  134. package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
  135. package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
  136. package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
  137. package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
  138. package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
  139. package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
  140. package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
  141. package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
  142. package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
  143. package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
  144. package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
  145. package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
  146. package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
  147. package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
  148. package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
  149. package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
  150. package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
  151. package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
  152. package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
  153. package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
  154. package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
  155. package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
  156. package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
  157. package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
  158. package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
  159. package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
  160. package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
  161. package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
  162. package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
  163. package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
  164. package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
  165. package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
  166. package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
  167. package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
  168. package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
  169. package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
  170. package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
  171. package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
  172. package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
  173. package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
  174. package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
  175. package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
  176. package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
  177. package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
  178. package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
  179. package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
  180. package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
  181. package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
  182. package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
@@ -0,0 +1,185 @@
1
+ """
2
+ Pure Functions in Python — Working Examples
3
+
4
+ Demonstrates core FP patterns: purity, immutability, composition,
5
+ and the functional core / imperative shell architecture.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field, replace
10
+ from functools import lru_cache, partial, reduce
11
+ from operator import itemgetter
12
+ from typing import NamedTuple
13
+
14
+
15
+ # === Immutable Data Structures ===
16
+
17
+ @dataclass(frozen=True)
18
+ class User:
19
+ name: str
20
+ email: str
21
+ role: str = "user"
22
+ settings: tuple = () # Tuple for deep immutability
23
+
24
+
25
+ class Point(NamedTuple):
26
+ x: float
27
+ y: float
28
+
29
+
30
+ # === Pure Functions: Calculations ===
31
+
32
+ def calculate_total(items: list[dict]) -> float:
33
+ return sum(item["price"] * item.get("quantity", 1) for item in items)
34
+
35
+
36
+ def apply_discount(total: float, rate: float) -> float:
37
+ clamped_rate = max(0.0, min(1.0, rate))
38
+ return round(total * (1 - clamped_rate), 2)
39
+
40
+
41
+ def calculate_tax(subtotal: float, tax_rate: float) -> float:
42
+ return round(subtotal * tax_rate, 2)
43
+
44
+
45
+ # === Pure Functions: Validation ===
46
+
47
+ def validate_required(value) -> bool:
48
+ return value is not None and value != ""
49
+
50
+
51
+ def validate_email(value: str) -> bool:
52
+ return isinstance(value, str) and "@" in value and "." in value.split("@")[-1]
53
+
54
+
55
+ def validate_length(min_len: int, max_len: int):
56
+ return lambda value: isinstance(value, str) and min_len <= len(value) <= max_len
57
+
58
+
59
+ def validate_user_email(email: str) -> dict:
60
+ if not validate_required(email):
61
+ return {"valid": False, "error": "Required"}
62
+ if not validate_email(email):
63
+ return {"valid": False, "error": "Invalid email format"}
64
+ if not validate_length(5, 254)(email):
65
+ return {"valid": False, "error": "Email length out of range"}
66
+ return {"valid": True}
67
+
68
+
69
+ # === Pure Functions: Transformations ===
70
+
71
+ def normalize_name(name: str) -> str:
72
+ return " ".join(name.strip().split()).title()
73
+
74
+
75
+ def update_user_email(user: User, new_email: str) -> User:
76
+ return replace(user, email=new_email)
77
+
78
+
79
+ def add_item(items: list[dict], new_item: dict) -> list[dict]:
80
+ return [*items, new_item]
81
+
82
+
83
+ def remove_item(items: list[dict], item_id: int) -> list[dict]:
84
+ return [item for item in items if item["id"] != item_id]
85
+
86
+
87
+ def update_item(items: list[dict], item_id: int, updates: dict) -> list[dict]:
88
+ return [{**item, **updates} if item["id"] == item_id else item for item in items]
89
+
90
+
91
+ # === Memoization (Pure Functions Only) ===
92
+
93
+ @lru_cache(maxsize=128)
94
+ def fibonacci(n: int) -> int:
95
+ if n < 2:
96
+ return n
97
+ return fibonacci(n - 1) + fibonacci(n - 2)
98
+
99
+
100
+ # === Composition Without Utilities ===
101
+
102
+ def process_order(order: dict, discount_table: dict, tax_rate: float) -> dict:
103
+ """Pure order processing — no I/O, no side effects."""
104
+ if not order.get("items"):
105
+ return {"success": False, "error": "Order must have items"}
106
+
107
+ subtotal = calculate_total(order["items"])
108
+ discount_rate = discount_table.get(order.get("discount_code", ""), 0.0)
109
+ discounted = apply_discount(subtotal, discount_rate)
110
+ tax = calculate_tax(discounted, tax_rate)
111
+ total = round(discounted + tax, 2)
112
+
113
+ return {
114
+ "success": True,
115
+ "data": {
116
+ "subtotal": subtotal,
117
+ "discount": round(subtotal - discounted, 2),
118
+ "tax": tax,
119
+ "total": total,
120
+ },
121
+ }
122
+
123
+
124
+ # === Higher-Order Functions ===
125
+
126
+ def create_validator(rules: list[dict]) -> callable:
127
+ """Factory: creates a reusable validator from rules."""
128
+ def validate(value):
129
+ errors = [rule["message"] for rule in rules if not rule["check"](value)]
130
+ return {"valid": True} if not errors else {"valid": False, "errors": errors}
131
+ return validate
132
+
133
+
134
+ # Pre-configured validators
135
+ validate_username = create_validator([
136
+ {"check": validate_required, "message": "Username is required"},
137
+ {"check": validate_length(3, 50), "message": "Username must be 3-50 characters"},
138
+ {"check": lambda v: v.isalnum(), "message": "Username must be alphanumeric"},
139
+ ])
140
+
141
+
142
+ # === Partial Application ===
143
+
144
+ multiply = lambda a, b: a * b
145
+ double = partial(multiply, 2)
146
+ triple = partial(multiply, 3)
147
+
148
+
149
+ # === Grouping and Sorting (Using operator module) ===
150
+
151
+ def group_by_key(items: list[dict], key: str) -> dict[str, list[dict]]:
152
+ """Group items by a key value. Pure function."""
153
+ sorted_items = sorted(items, key=itemgetter(key))
154
+ result = {}
155
+ for item in sorted_items:
156
+ group = item[key]
157
+ result[group] = [*result.get(group, []), item]
158
+ return result
159
+
160
+
161
+ # === Generator Pipeline ===
162
+
163
+ def read_lines(text: str):
164
+ for line in text.splitlines():
165
+ stripped = line.strip()
166
+ if stripped:
167
+ yield stripped
168
+
169
+
170
+ def filter_comments(lines):
171
+ for line in lines:
172
+ if not line.startswith("#"):
173
+ yield line
174
+
175
+
176
+ def parse_key_value(lines):
177
+ for line in lines:
178
+ if "=" in line:
179
+ key, _, value = line.partition("=")
180
+ yield key.strip(), value.strip()
181
+
182
+
183
+ def parse_config(text: str) -> dict:
184
+ """Parse a simple key=value config from text. Pure, lazy pipeline."""
185
+ return dict(parse_key_value(filter_comments(read_lines(text))))
@@ -0,0 +1,244 @@
1
+ """Tests for pure function examples — no mocks needed."""
2
+ from __future__ import annotations
3
+
4
+ import pytest
5
+
6
+ # Assume pure_functions is importable (adjust path as needed)
7
+ from examples.pure_functions import (
8
+ User,
9
+ add_item,
10
+ apply_discount,
11
+ calculate_tax,
12
+ calculate_total,
13
+ create_validator,
14
+ double,
15
+ fibonacci,
16
+ group_by_key,
17
+ normalize_name,
18
+ parse_config,
19
+ process_order,
20
+ remove_item,
21
+ triple,
22
+ update_item,
23
+ update_user_email,
24
+ validate_email,
25
+ validate_length,
26
+ validate_required,
27
+ validate_user_email,
28
+ validate_username,
29
+ )
30
+
31
+
32
+ # === Calculation Tests ===
33
+
34
+ class TestCalculateTotal:
35
+ @pytest.mark.parametrize("items,expected", [
36
+ ([{"price": 10, "quantity": 2}, {"price": 5, "quantity": 1}], 25.0),
37
+ ([{"price": 100}], 100.0), # Default quantity = 1
38
+ ([], 0.0),
39
+ ])
40
+ def test_valid_inputs(self, items, expected):
41
+ assert calculate_total(items) == expected
42
+
43
+
44
+ class TestApplyDiscount:
45
+ @pytest.mark.parametrize("total,rate,expected", [
46
+ (100.0, 0.1, 90.0),
47
+ (100.0, 0.0, 100.0),
48
+ (100.0, 1.0, 0.0),
49
+ (100.0, -0.1, 100.0), # Clamped to 0
50
+ (100.0, 1.5, 0.0), # Clamped to 1
51
+ (0.0, 0.5, 0.0),
52
+ ])
53
+ def test_discount_calculation(self, total, rate, expected):
54
+ assert apply_discount(total, rate) == expected
55
+
56
+
57
+ class TestCalculateTax:
58
+ @pytest.mark.parametrize("subtotal,rate,expected", [
59
+ (100.0, 0.08, 8.0),
60
+ (100.0, 0.0, 0.0),
61
+ (9.99, 0.0825, 0.82),
62
+ ])
63
+ def test_tax_calculation(self, subtotal, rate, expected):
64
+ assert calculate_tax(subtotal, rate) == expected
65
+
66
+
67
+ # === Validation Tests ===
68
+
69
+ class TestValidateRequired:
70
+ @pytest.mark.parametrize("value,expected", [
71
+ ("hello", True),
72
+ ("", False),
73
+ (None, False),
74
+ (0, True), # Zero is a valid value
75
+ (False, True), # False is a valid value
76
+ ])
77
+ def test_required_validation(self, value, expected):
78
+ assert validate_required(value) == expected
79
+
80
+
81
+ class TestValidateEmail:
82
+ @pytest.mark.parametrize("value,expected", [
83
+ ("user@example.com", True),
84
+ ("a@b.c", True),
85
+ ("invalid", False),
86
+ ("@no-local.com", False),
87
+ ("no-domain@", False),
88
+ ("", False),
89
+ (123, False), # Non-string
90
+ ])
91
+ def test_email_validation(self, value, expected):
92
+ assert validate_email(value) == expected
93
+
94
+
95
+ class TestValidateUserEmail:
96
+ def test_valid_email(self):
97
+ result = validate_user_email("user@example.com")
98
+ assert result["valid"] is True
99
+
100
+ def test_empty_email(self):
101
+ result = validate_user_email("")
102
+ assert result["valid"] is False
103
+ assert result["error"] == "Required"
104
+
105
+ def test_invalid_format(self):
106
+ result = validate_user_email("notanemail")
107
+ assert result["valid"] is False
108
+ assert "format" in result["error"].lower()
109
+
110
+
111
+ # === Immutability Tests ===
112
+
113
+ class TestImmutability:
114
+ def test_frozen_dataclass_prevents_mutation(self):
115
+ user = User(name="Alice", email="alice@test.com")
116
+ with pytest.raises(AttributeError):
117
+ user.name = "Bob"
118
+
119
+ def test_update_returns_new_instance(self):
120
+ original = User(name="Alice", email="old@test.com")
121
+ updated = update_user_email(original, "new@test.com")
122
+ assert updated.email == "new@test.com"
123
+ assert original.email == "old@test.com"
124
+ assert original is not updated
125
+
126
+ def test_add_item_does_not_mutate(self):
127
+ original = [{"id": 1, "name": "A"}]
128
+ result = add_item(original, {"id": 2, "name": "B"})
129
+ assert len(result) == 2
130
+ assert len(original) == 1 # Unchanged
131
+
132
+ def test_remove_item_does_not_mutate(self):
133
+ original = [{"id": 1}, {"id": 2}, {"id": 3}]
134
+ result = remove_item(original, 2)
135
+ assert len(result) == 2
136
+ assert len(original) == 3 # Unchanged
137
+
138
+ def test_update_item_does_not_mutate(self):
139
+ original = [{"id": 1, "name": "Old"}]
140
+ result = update_item(original, 1, {"name": "New"})
141
+ assert result[0]["name"] == "New"
142
+ assert original[0]["name"] == "Old" # Unchanged
143
+
144
+
145
+ # === Composition Tests ===
146
+
147
+ class TestProcessOrder:
148
+ def test_valid_order(self):
149
+ order = {"items": [{"price": 100, "quantity": 1}]}
150
+ result = process_order(order, discount_table={}, tax_rate=0.1)
151
+ assert result["success"] is True
152
+ assert result["data"]["subtotal"] == 100.0
153
+ assert result["data"]["tax"] == 10.0
154
+ assert result["data"]["total"] == 110.0
155
+
156
+ def test_order_with_discount(self):
157
+ order = {
158
+ "items": [{"price": 100, "quantity": 1}],
159
+ "discount_code": "SAVE10",
160
+ }
161
+ result = process_order(order, discount_table={"SAVE10": 0.1}, tax_rate=0.0)
162
+ assert result["success"] is True
163
+ assert result["data"]["total"] == 90.0
164
+
165
+ def test_empty_order_fails(self):
166
+ result = process_order({"items": []}, discount_table={}, tax_rate=0.1)
167
+ assert result["success"] is False
168
+ assert "items" in result["error"].lower()
169
+
170
+
171
+ # === Higher-Order Function Tests ===
172
+
173
+ class TestCreateValidator:
174
+ def test_all_rules_pass(self):
175
+ result = validate_username("alice42")
176
+ assert result["valid"] is True
177
+
178
+ def test_empty_value(self):
179
+ result = validate_username("")
180
+ assert result["valid"] is False
181
+ assert any("required" in e.lower() for e in result["errors"])
182
+
183
+ def test_too_short(self):
184
+ result = validate_username("ab")
185
+ assert result["valid"] is False
186
+
187
+
188
+ # === Partial Application Tests ===
189
+
190
+ class TestPartialApplication:
191
+ def test_double(self):
192
+ assert double(5) == 10
193
+
194
+ def test_triple(self):
195
+ assert triple(5) == 15
196
+
197
+
198
+ # === Memoization Tests ===
199
+
200
+ class TestFibonacci:
201
+ @pytest.mark.parametrize("n,expected", [
202
+ (0, 0), (1, 1), (2, 1), (10, 55), (20, 6765),
203
+ ])
204
+ def test_fibonacci(self, n, expected):
205
+ assert fibonacci(n) == expected
206
+
207
+
208
+ # === Generator Pipeline Tests ===
209
+
210
+ class TestParseConfig:
211
+ def test_simple_config(self):
212
+ text = """
213
+ # Database settings
214
+ host = localhost
215
+ port = 5432
216
+ name = mydb
217
+ """
218
+ result = parse_config(text)
219
+ assert result == {"host": "localhost", "port": "5432", "name": "mydb"}
220
+
221
+ def test_empty_input(self):
222
+ assert parse_config("") == {}
223
+
224
+ def test_comments_and_blanks_skipped(self):
225
+ text = "# comment\n\nkey = value\n# another comment"
226
+ result = parse_config(text)
227
+ assert result == {"key": "value"}
228
+
229
+
230
+ # === Grouping Tests ===
231
+
232
+ class TestGroupByKey:
233
+ def test_group_items(self):
234
+ items = [
235
+ {"name": "A", "category": "x"},
236
+ {"name": "B", "category": "y"},
237
+ {"name": "C", "category": "x"},
238
+ ]
239
+ result = group_by_key(items, "category")
240
+ assert len(result["x"]) == 2
241
+ assert len(result["y"]) == 1
242
+
243
+ def test_empty_list(self):
244
+ assert group_by_key([], "category") == {}