@grimoire-cc/cli 0.13.3 → 0.14.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 (50) hide show
  1. package/dist/commands/update.d.ts.map +1 -1
  2. package/dist/commands/update.js +14 -0
  3. package/dist/commands/update.js.map +1 -1
  4. package/dist/enforce.d.ts +3 -1
  5. package/dist/enforce.d.ts.map +1 -1
  6. package/dist/enforce.js +18 -6
  7. package/dist/enforce.js.map +1 -1
  8. package/dist/setup.d.ts.map +1 -1
  9. package/dist/setup.js +47 -0
  10. package/dist/setup.js.map +1 -1
  11. package/dist/summary.d.ts.map +1 -1
  12. package/dist/summary.js +9 -0
  13. package/dist/summary.js.map +1 -1
  14. package/package.json +1 -1
  15. package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
  16. package/packs/dev-pack/grimoire.json +0 -38
  17. package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +69 -65
  18. package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
  19. package/packs/dotnet-pack/grimoire.json +23 -5
  20. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
  21. package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
  22. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
  23. package/packs/go-pack/grimoire.json +19 -0
  24. package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
  25. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
  26. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
  27. package/packs/python-pack/grimoire.json +19 -0
  28. package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
  29. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
  30. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
  31. package/packs/rust-pack/grimoire.json +29 -0
  32. package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
  33. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
  34. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
  35. package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
  36. package/packs/ts-pack/grimoire.json +27 -1
  37. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
  38. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
  39. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
  40. package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
  41. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
  42. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
  43. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
  44. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
  45. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
  46. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
  47. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
  48. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
  49. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
  50. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
@@ -0,0 +1,243 @@
1
+ ---
2
+ name: grimoire.unit-testing-rust
3
+ description: "Rust unit testing specialist. Patterns and best practices for the built-in test framework, mockall, and proptest. Use when writing tests for .rs files, or asking about Rust testing patterns, test modules, mocking traits, property-based testing, integration tests."
4
+ ---
5
+
6
+ # Rust Unit Testing
7
+
8
+ Focused guidance for writing clean, idiomatic unit tests in Rust projects.
9
+
10
+ ## Framework Selection
11
+
12
+ ### Detection
13
+
14
+ 1. Check existing test files first — always match what the project uses
15
+ 2. Check `Cargo.toml` for `mockall`, `proptest`, `rstest` in `[dev-dependencies]`
16
+ 3. Check existing `#[cfg(test)] mod tests` blocks for assertion style
17
+
18
+ ### Decision Table
19
+
20
+ | Condition | Use | Reason |
21
+ |-----------|-----|--------|
22
+ | Project has existing tests | **Match existing** | Consistency is paramount |
23
+ | Any Rust project | **Built-in `#[test]`** | Always available, zero deps |
24
+ | Need trait mocking | **mockall** | Derive macros for mock generation |
25
+ | Need parameterized tests | **rstest** | `#[rstest]` with fixtures and cases |
26
+ | Need property-based | **proptest** | QuickCheck-style property testing |
27
+ | User explicitly requests | **Requested** | Respect user preference |
28
+
29
+ ## Naming Conventions
30
+
31
+ Use `test_method_scenario_expected` with snake_case:
32
+
33
+ ```rust
34
+ // Pattern: test_method_scenario_expected
35
+ fn test_get_user_with_invalid_id_returns_not_found() { ... }
36
+ fn test_calculate_total_with_discount_applies_percentage() { ... }
37
+ fn test_parse_config_with_missing_fields_returns_error() { ... }
38
+ ```
39
+
40
+ ## Patterns
41
+
42
+ ### AAA with built-in tests
43
+
44
+ ```rust
45
+ #[cfg(test)]
46
+ mod tests {
47
+ use super::*;
48
+
49
+ #[test]
50
+ fn process_order_with_valid_order_returns_id() {
51
+ // Arrange
52
+ let mut mock_repo = MockOrderRepository::new();
53
+ mock_repo.expect_save()
54
+ .returning(|_| Ok(Order { id: "123".into() }));
55
+ let service = OrderService::new(Box::new(mock_repo));
56
+
57
+ // Act
58
+ let result = service.process_order(&valid_order());
59
+
60
+ // Assert
61
+ assert_eq!(result.unwrap().id, "123");
62
+ }
63
+
64
+ #[test]
65
+ fn process_order_with_invalid_order_returns_error() {
66
+ // Arrange
67
+ let service = OrderService::new(Box::new(MockOrderRepository::new()));
68
+
69
+ // Act
70
+ let result = service.process_order(&invalid_order());
71
+
72
+ // Assert
73
+ assert!(matches!(result, Err(ServiceError::Validation(_))));
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### Parameterized with rstest
79
+
80
+ ```rust
81
+ use rstest::rstest;
82
+
83
+ #[rstest]
84
+ #[case(0, 100.0)]
85
+ #[case(10, 90.0)]
86
+ #[case(50, 50.0)]
87
+ fn apply_discount_calculates_correctly(#[case] discount: u32, #[case] expected: f64) {
88
+ assert_eq!(apply_discount(100.0, discount), expected);
89
+ }
90
+ ```
91
+
92
+ ### rstest Fixtures
93
+
94
+ ```rust
95
+ use rstest::*;
96
+
97
+ #[fixture]
98
+ fn service() -> OrderService {
99
+ let repo = InMemoryOrderRepository::new();
100
+ OrderService::new(Box::new(repo))
101
+ }
102
+
103
+ #[rstest]
104
+ fn process_order_with_valid_order_succeeds(service: OrderService) {
105
+ let result = service.process_order(&valid_order());
106
+ assert!(result.is_ok());
107
+ }
108
+ ```
109
+
110
+ ### Error Testing
111
+
112
+ ```rust
113
+ // Check Result is Err
114
+ #[test]
115
+ fn divide_by_zero_returns_error() {
116
+ let result = divide(1, 0);
117
+ assert!(result.is_err());
118
+ }
119
+
120
+ // Match specific error variant
121
+ #[test]
122
+ fn parse_invalid_input_returns_parse_error() {
123
+ let result = parse("bad input");
124
+ assert!(matches!(result, Err(ParseError::InvalidFormat(_))));
125
+ }
126
+
127
+ // Check error message
128
+ #[test]
129
+ fn validate_empty_name_returns_error_message() {
130
+ let err = validate(User { name: "".into() }).unwrap_err();
131
+ assert_eq!(err.to_string(), "name cannot be empty");
132
+ }
133
+
134
+ // Should panic
135
+ #[test]
136
+ #[should_panic(expected = "index out of bounds")]
137
+ fn access_out_of_bounds_panics() {
138
+ let v = vec![1, 2, 3];
139
+ let _ = v[10];
140
+ }
141
+ ```
142
+
143
+ ### Async Testing (tokio)
144
+
145
+ ```rust
146
+ #[tokio::test]
147
+ async fn fetch_user_returns_data() {
148
+ // Arrange
149
+ let client = MockHttpClient::new();
150
+ client.expect_get().returning(|_| Ok(user_response()));
151
+
152
+ // Act
153
+ let user = fetch_user(&client, "123").await.unwrap();
154
+
155
+ // Assert
156
+ assert_eq!(user.name, "Alice");
157
+ }
158
+ ```
159
+
160
+ ## Mocking
161
+
162
+ ### mockall
163
+
164
+ ```rust
165
+ use mockall::*;
166
+ use mockall::predicate::*;
167
+
168
+ #[automock]
169
+ trait OrderRepository {
170
+ fn save(&self, order: &Order) -> Result<Order, RepositoryError>;
171
+ fn find_by_id(&self, id: &str) -> Result<Option<Order>, RepositoryError>;
172
+ }
173
+
174
+ // Usage in tests
175
+ let mut mock = MockOrderRepository::new();
176
+ mock.expect_save()
177
+ .with(predicate::always())
178
+ .times(1)
179
+ .returning(|order| Ok(Order { id: "123".into(), ..order.clone() }));
180
+ mock.expect_find_by_id()
181
+ .with(eq("123"))
182
+ .returning(|_| Ok(Some(Order { id: "123".into(), ..Default::default() })));
183
+ ```
184
+
185
+ ### Manual fakes (for simple cases)
186
+
187
+ ```rust
188
+ // Test-only implementation
189
+ struct FakeOrderRepository {
190
+ orders: Vec<Order>,
191
+ }
192
+
193
+ impl OrderRepository for FakeOrderRepository {
194
+ fn save(&self, order: &Order) -> Result<Order, RepositoryError> {
195
+ Ok(Order { id: "fake-id".into(), ..order.clone() })
196
+ }
197
+ fn find_by_id(&self, id: &str) -> Result<Option<Order>, RepositoryError> {
198
+ Ok(self.orders.iter().find(|o| o.id == id).cloned())
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### What NOT to mock
204
+
205
+ - Structs used as data containers
206
+ - Pure functions with no side effects
207
+ - The module under test itself
208
+ - Standard library types
209
+
210
+ Mock only at system boundaries: external APIs, databases, file system, time.
211
+
212
+ ## File Conventions
213
+
214
+ - Unit tests: `#[cfg(test)] mod tests` at bottom of source file
215
+ - Integration tests: `tests/` directory (each file is a separate crate)
216
+ - Test fixtures: `tests/fixtures/` or inline
217
+ - Run: `cargo test` (all), `cargo test -- test_name` (specific)
218
+ - Run with output: `cargo test -- --nocapture`
219
+
220
+ ## Package Setup
221
+
222
+ ```toml
223
+ # Cargo.toml
224
+ [dev-dependencies]
225
+ mockall = "0.13" # trait mocking
226
+ rstest = "0.22" # parameterized tests, fixtures
227
+ proptest = "1.5" # property-based testing
228
+ tokio = { version = "1", features = ["test-util", "macros", "rt"] } # async tests
229
+ ```
230
+
231
+ ## Authoritative Sources
232
+
233
+ - The Rust Book — Testing: https://doc.rust-lang.org/book/ch11-00-testing.html
234
+ - mockall: https://docs.rs/mockall
235
+ - rstest: https://docs.rs/rstest
236
+ - proptest: https://proptest-rs.github.io/proptest
237
+ - Kent Beck — Canon TDD: https://tidyfirst.substack.com/p/canon-tdd
238
+ - Martin Fowler — Mocks Aren't Stubs: https://martinfowler.com/articles/mocksArentStubs.html
239
+
240
+ ## Reference Materials
241
+
242
+ - **[Anti-Patterns](reference/anti-patterns.md)** — Common testing mistakes and how to fix them
243
+ - **[TDD Workflow Patterns](reference/tdd-workflow-patterns.md)** — Red-Green-Refactor, Transformation Priority Premise, when to use TDD
@@ -0,0 +1,244 @@
1
+ # Testing Anti-Patterns
2
+
3
+ Common testing mistakes that reduce test value and increase maintenance cost. These are language-agnostic — they apply to any test framework.
4
+
5
+ ## Table of Contents
6
+
7
+ - [The Liar](#the-liar)
8
+ - [The Giant](#the-giant)
9
+ - [Excessive Setup](#excessive-setup)
10
+ - [The Slow Poke](#the-slow-poke)
11
+ - [The Peeping Tom](#the-peeping-tom)
12
+ - [The Mockery](#the-mockery)
13
+ - [The Inspector](#the-inspector)
14
+ - [The Flaky Test](#the-flaky-test)
15
+ - [The Cargo Culter](#the-cargo-culter)
16
+ - [The Hard Test](#the-hard-test)
17
+
18
+ ## The Liar
19
+
20
+ **What it is:** A test that passes but doesn't actually verify the behavior it claims to test. It gives false confidence.
21
+
22
+ **How to spot it:**
23
+ - Test name says "validates input" but assertions only check the return type
24
+ - Assertions are too loose (`assert result is not None` instead of checking the actual value)
25
+ - Test catches exceptions broadly and passes regardless
26
+
27
+ **Fix:** Ensure assertions directly verify the specific behavior described in the test name. Every assertion should fail if the behavior breaks.
28
+
29
+ ```python
30
+ # Bad — passes even if discount logic is completely wrong
31
+ def test_apply_discount():
32
+ result = apply_discount(100, 10)
33
+ assert result is not None
34
+
35
+ # Good — fails if the calculation is wrong
36
+ def test_apply_discount_with_10_percent_returns_90():
37
+ result = apply_discount(100, 10)
38
+ assert result == 90.0
39
+ ```
40
+
41
+ ## The Giant
42
+
43
+ **What it is:** A single test that verifies too many things. When it fails, you can't tell which behavior broke.
44
+
45
+ **How to spot it:**
46
+ - Test has more than 8-10 assertions
47
+ - Test name uses "and" (e.g., "creates user and sends email and updates cache")
48
+ - Multiple Act phases in one test
49
+
50
+ **Fix:** Split into focused tests, each verifying one logical concept. Multiple assertions are fine if they verify aspects of the same behavior.
51
+
52
+ ```typescript
53
+ // Bad — three unrelated behaviors in one test
54
+ test('user registration works', () => {
55
+ const user = register({ name: 'Alice', email: 'alice@test.com' });
56
+ expect(user.id).toBeDefined();
57
+ expect(emailService.send).toHaveBeenCalled();
58
+ expect(cache.set).toHaveBeenCalledWith(`user:${user.id}`, user);
59
+ expect(auditLog.entries).toHaveLength(1);
60
+ });
61
+
62
+ // Good — separate tests for each behavior
63
+ test('register with valid data creates user with id', () => { ... });
64
+ test('register with valid data sends welcome email', () => { ... });
65
+ test('register with valid data caches the user', () => { ... });
66
+ test('register with valid data writes audit log entry', () => { ... });
67
+ ```
68
+
69
+ ## Excessive Setup
70
+
71
+ **What it is:** Tests that require dozens of lines of setup before the actual test logic. Often signals that the code under test has too many dependencies.
72
+
73
+ **How to spot it:**
74
+ - Arrange section is 20+ lines
75
+ - Multiple mocks configured with complex behaviors
76
+ - Shared setup methods that configure things most tests don't need
77
+
78
+ **Fix:** Use factory methods/builders for test data. Consider whether the code under test needs refactoring to reduce dependencies. Only set up what the specific test needs.
79
+
80
+ ```go
81
+ // Bad — every test sets up the entire world
82
+ func TestProcessOrder(t *testing.T) {
83
+ db := setupDatabase()
84
+ cache := setupCache()
85
+ logger := setupLogger()
86
+ emailClient := setupEmailClient()
87
+ validator := NewValidator(db)
88
+ processor := NewProcessor(cache)
89
+ service := NewOrderService(db, cache, logger, emailClient, validator, processor)
90
+ // ... 10 more lines of setup
91
+ result, err := service.ProcessOrder(ctx, order)
92
+ assert.NoError(t, err)
93
+ }
94
+
95
+ // Good — factory method hides irrelevant details
96
+ func TestProcessOrder_WithValidOrder_Succeeds(t *testing.T) {
97
+ service := newTestOrderService(t)
98
+ result, err := service.ProcessOrder(ctx, validOrder())
99
+ assert.NoError(t, err)
100
+ assert.Equal(t, "processed", result.Status)
101
+ }
102
+ ```
103
+
104
+ ## The Slow Poke
105
+
106
+ **What it is:** Tests that are slow because they use real I/O, network calls, or sleeps. Slow tests get run less frequently and slow down the feedback loop.
107
+
108
+ **How to spot it:**
109
+ - `time.Sleep()`, `Thread.sleep()`, `setTimeout` in tests
110
+ - Real HTTP calls, database connections, file system operations
111
+ - Test suite takes more than a few seconds for unit tests
112
+
113
+ **Fix:** Mock external dependencies. Use fake implementations for I/O. Replace time-based waits with event-based synchronization.
114
+
115
+ ## The Peeping Tom
116
+
117
+ **What it is:** Tests that access private/internal state to verify behavior instead of testing through the public interface.
118
+
119
+ **How to spot it:**
120
+ - Reflection to access private fields
121
+ - Testing internal method calls instead of observable results
122
+ - Assertions on implementation details (internal data structures, private counters)
123
+
124
+ **Fix:** Test through the public API. If you can't verify behavior through the public interface, the class may need a design change (e.g., expose a query method or extract a collaborator).
125
+
126
+ ## The Mockery
127
+
128
+ **What it is:** Tests that mock so heavily that they're testing mock configurations rather than real behavior. Every dependency is mocked, including simple value objects.
129
+
130
+ **How to spot it:**
131
+ - More mock setup lines than actual test logic
132
+ - Mocking concrete classes, value objects, or data structures
133
+ - Test passes but the real system fails because mocks don't match reality
134
+
135
+ **Fix:** Only mock at system boundaries (external services, databases, clocks). Use real implementations for in-process collaborators when practical.
136
+
137
+ ## The Inspector
138
+
139
+ **What it is:** Tests that verify exact method calls and their order rather than outcomes. They break whenever the implementation changes, even if behavior is preserved.
140
+
141
+ **How to spot it:**
142
+ - `verify(mock, times(1)).method()` for every mock interaction
143
+ - Assertions on call order
144
+ - Test breaks when you refactor without changing behavior
145
+
146
+ **Fix:** Verify state (the result) rather than interactions (how it got there). Only verify interactions for side effects that ARE the behavior (e.g., "email was sent").
147
+
148
+ ```java
149
+ // Bad — breaks if implementation changes sort algorithm
150
+ verify(sorter, times(1)).quickSort(any());
151
+ verify(sorter, never()).mergeSort(any());
152
+
153
+ // Good — verifies the outcome
154
+ assertThat(result).isSortedAccordingTo(naturalOrder());
155
+ ```
156
+
157
+ ## The Flaky Test
158
+
159
+ **What it is:** Tests that pass and fail intermittently without code changes. They erode trust in the test suite.
160
+
161
+ **Common causes:**
162
+ - Time-dependent logic (`new Date()`, `time.Now()`)
163
+ - Random data without fixed seeds
164
+ - Shared mutable state between tests
165
+ - Race conditions in async tests
166
+ - Dependency on test execution order
167
+
168
+ **Fix:** Inject time as a dependency. Use fixed seeds for randomness. Ensure test isolation. Use proper async synchronization.
169
+
170
+ ## The Cargo Culter
171
+
172
+ **What it is:** Writing tests to hit a coverage percentage target rather than to verify behavior. The tests exist to satisfy a metric, not to provide confidence.
173
+
174
+ **How to spot it:**
175
+ - Tests that assert trivially obvious things (e.g., `assert user.name == user.name`)
176
+ - Every private method has a corresponding test accessed via reflection
177
+ - 100% coverage but bugs still escape to production
178
+ - Test suite takes minutes to pass but developers don't trust it
179
+
180
+ **Fix:** Coverage is a diagnostic tool, not a goal. Use it to find untested gaps, not as a number to optimize. High 80s–90% emerges naturally from disciplined TDD. A test that only exists to push coverage up is worse than no test — it adds maintenance cost without adding confidence.
181
+
182
+ ```python
183
+ # Bad — written for coverage, not for confidence
184
+ def test_user_has_name():
185
+ user = User(name="Alice")
186
+ assert user.name is not None # This verifies nothing meaningful
187
+
188
+ # Good — written to verify a business rule
189
+ def test_user_with_empty_name_raises_validation_error():
190
+ with pytest.raises(ValidationError, match="name cannot be empty"):
191
+ User(name="")
192
+ ```
193
+
194
+ > See: https://martinfowler.com/bliki/TestCoverage.html
195
+
196
+ ## The Hard Test
197
+
198
+ **What it is:** Not an anti-pattern in the test itself, but a signal from the test about the production code. When a test is painful, complex, or requires elaborate setup, the production code has a design problem.
199
+
200
+ **How to spot it:**
201
+ - Need to mock 5+ dependencies to test one class
202
+ - Need to access private internals to verify behavior
203
+ - Test requires a complex sequence of operations just to get to the state under test
204
+ - You find yourself thinking "testing this would be too hard"
205
+
206
+ **What it signals:**
207
+ - Too many responsibilities in one class (SRP violation)
208
+ - Hidden dependencies or tight coupling
209
+ - Poor separation of concerns
210
+ - Untestable architecture (e.g., side effects embedded in business logic)
211
+
212
+ **Fix:** Resist the urge to skip the test or work around it with clever mocking. Instead, fix the production code design. Extract classes, inject dependencies, separate concerns. A hard test is a free design review — take the feedback.
213
+
214
+ ```python
215
+ # Hard to test — service does too much
216
+ class OrderService:
217
+ def process(self, order):
218
+ db = Database() # hidden dependency
219
+ email = EmailClient() # hidden dependency
220
+ self._validate(order)
221
+ db.save(order)
222
+ email.send_confirmation(order)
223
+ self._update_inventory(order) # another responsibility
224
+
225
+ # Easy to test — dependencies explicit, concerns separated
226
+ class OrderService:
227
+ def __init__(self, repo: OrderRepository, notifier: Notifier):
228
+ self._repo = repo
229
+ self._notifier = notifier
230
+
231
+ def process(self, order: Order) -> OrderResult:
232
+ self._validate(order)
233
+ saved = self._repo.save(order)
234
+ self._notifier.notify(saved)
235
+ return saved
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Further Reading
241
+
242
+ - xUnit Patterns (Meszaros): http://xunitpatterns.com
243
+ - Codepipes testing anti-patterns: https://blog.codepipes.com/testing/software-testing-antipatterns.html
244
+ - Google SWE Book — Test Doubles: https://abseil.io/resources/swe-book/html/ch13.html