@polderlabs/bizar 2.3.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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -0
  3. package/cli/audit.mjs +144 -0
  4. package/cli/banner.mjs +41 -0
  5. package/cli/bin.mjs +186 -0
  6. package/cli/copy.mjs +508 -0
  7. package/cli/export.mjs +87 -0
  8. package/cli/init.mjs +147 -0
  9. package/cli/install.mjs +390 -0
  10. package/cli/plan-templates.mjs +523 -0
  11. package/cli/plan.mjs +2087 -0
  12. package/cli/prompts.mjs +163 -0
  13. package/cli/update.mjs +273 -0
  14. package/cli/utils.mjs +153 -0
  15. package/config/AGENTS.md +282 -0
  16. package/config/agents/baldr.md +148 -0
  17. package/config/agents/forseti.md +112 -0
  18. package/config/agents/frigg.md +101 -0
  19. package/config/agents/heimdall.md +157 -0
  20. package/config/agents/hermod.md +144 -0
  21. package/config/agents/mimir.md +115 -0
  22. package/config/agents/odin.md +309 -0
  23. package/config/agents/quick.md +78 -0
  24. package/config/agents/semble-search.md +44 -0
  25. package/config/agents/thor.md +97 -0
  26. package/config/agents/tyr.md +96 -0
  27. package/config/agents/vidarr.md +100 -0
  28. package/config/agents/vor.md +140 -0
  29. package/config/commands/audit.md +1 -0
  30. package/config/commands/explain.md +1 -0
  31. package/config/commands/init.md +1 -0
  32. package/config/commands/learn.md +1 -0
  33. package/config/commands/pr-review.md +1 -0
  34. package/config/commands/tailscale-serve.md +96 -0
  35. package/config/hooks/README.md +29 -0
  36. package/config/hooks/post-tool-use.md +16 -0
  37. package/config/hooks/pre-tool-use.md +16 -0
  38. package/config/opencode.json +52 -0
  39. package/config/opencode.json.template +52 -0
  40. package/config/rules/general.md +8 -0
  41. package/config/rules/git.md +11 -0
  42. package/config/rules/javascript.md +10 -0
  43. package/config/rules/python.md +10 -0
  44. package/config/rules/testing.md +10 -0
  45. package/config/skills/bizar/README.md +9 -0
  46. package/config/skills/bizar/SKILL.md +187 -0
  47. package/config/skills/cpp-coding-standards/README.md +28 -0
  48. package/config/skills/cpp-coding-standards/SKILL.md +634 -0
  49. package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
  50. package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
  51. package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
  52. package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  53. package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  54. package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  55. package/config/skills/cpp-testing/README.md +28 -0
  56. package/config/skills/cpp-testing/SKILL.md +304 -0
  57. package/config/skills/cpp-testing/agents/openai.yaml +4 -0
  58. package/config/skills/cpp-testing/references/coverage.md +370 -0
  59. package/config/skills/cpp-testing/references/framework-compare.md +175 -0
  60. package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  61. package/config/skills/cpp-testing/references/mocking.md +364 -0
  62. package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
  63. package/config/skills/embedded-esp-idf/README.md +41 -0
  64. package/config/skills/embedded-esp-idf/SKILL.md +439 -0
  65. package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
  66. package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  67. package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
  68. package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  69. package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
  70. package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  71. package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  72. package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
  73. package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  74. package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  75. package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  76. package/config/skills/self-improvement/SKILL.md +64 -0
  77. package/package.json +47 -0
  78. package/templates/plan/htmx.min.js +1 -0
  79. package/templates/plan/library/bug-investigation.mdx +79 -0
  80. package/templates/plan/library/decision-record.mdx +71 -0
  81. package/templates/plan/library/feature-design.mdx +92 -0
  82. package/templates/plan/meta.json.template +8 -0
  83. package/templates/plan/plan.canvas.template +1711 -0
  84. package/templates/plan/plan.html.template +937 -0
  85. package/templates/plan/plan.mdx.template +46 -0
@@ -0,0 +1,364 @@
1
+ # Mocking Strategies in C++
2
+
3
+ ## Overview
4
+
5
+ Mocking lets you test a unit in isolation by replacing its dependencies with controlled test doubles. In C++, the three primary strategies are:
6
+
7
+ 1. **Abstract Interface** — dependency inversion with a pure virtual interface
8
+ 2. **Link-Time Seam** — replace a compiled object at link time
9
+ 3. **`std::function` Injection** — pass behavior as a callable at runtime
10
+
11
+ Each has a different trade-off between isolation, compile-time cost, and how much you need to modify existing code.
12
+
13
+ ---
14
+
15
+ ## 1. Abstract Interface (Dependency Inversion)
16
+
17
+ ### Concept
18
+
19
+ Define a pure virtual interface. The class under test accepts a pointer/reference to the interface. In tests, pass a fake implementation.
20
+
21
+ ### When to Use
22
+
23
+ - You control the design and can introduce interfaces
24
+ - You need fine-grained control over mock behavior (ON_CALL, EXPECT_CALL)
25
+ - You want to test multiple implementations of the same interface
26
+
27
+ ### Code Example
28
+
29
+ ```cpp
30
+ // === interfaces/sensor_reading.h ===
31
+ #pragma once
32
+ #include <cstdint>
33
+ #include <optional>
34
+
35
+ struct ISensor {
36
+ virtual ~ISensor() = default;
37
+ virtual std::optional<double> read() = 0;
38
+ virtual bool is_connected() = 0;
39
+ };
40
+
41
+ // === main/policy/temp_monitor.h ===
42
+ #pragma once
43
+ #include "sensor_reading.h"
44
+
45
+ class TempMonitor {
46
+ public:
47
+ explicit TempMonitor(ISensor* sensor) : sensor_(sensor) {}
48
+ bool has_valid_reading();
49
+ double last_reading();
50
+ private:
51
+ ISensor* sensor_;
52
+ };
53
+
54
+ // === main/policy/temp_monitor.cpp ===
55
+ #include "temp_monitor.h"
56
+ #include <algorithm>
57
+
58
+ bool TempMonitor::has_valid_reading() {
59
+ return sensor_->is_connected() && sensor_->read().has_value();
60
+ }
61
+
62
+ double TempMonitor::last_reading() {
63
+ auto v = sensor_->read();
64
+ return v.value_or(0.0);
65
+ }
66
+
67
+ // === test/host/test_temp_monitor.cpp ===
68
+ #include <gtest/gtest.h>
69
+ #include "temp_monitor.h"
70
+ #include "sensor_reading.h"
71
+ #include <optional>
72
+
73
+ struct FakeSensor : ISensor {
74
+ std::optional<double> fake_value = std::nullopt;
75
+ bool connected = false;
76
+
77
+ std::optional<double> read() override { return fake_value; }
78
+ bool is_connected() override { return connected; }
79
+ };
80
+
81
+ TEST(TempMonitor, has_valid_reading_true_when_connected_and_has_value) {
82
+ FakeSensor fake;
83
+ fake.connected = true;
84
+ fake.fake_value = 23.5;
85
+ TempMonitor monitor(&fake);
86
+ EXPECT_TRUE(monitor.has_valid_reading());
87
+ EXPECT_DOUBLE_EQ(monitor.last_reading(), 23.5);
88
+ }
89
+
90
+ TEST(TempMonitor, has_valid_reading_false_when_disconnected) {
91
+ FakeSensor fake;
92
+ fake.connected = false;
93
+ fake.fake_value = 23.5;
94
+ TempMonitor monitor(&fake);
95
+ EXPECT_FALSE(monitor.has_valid_reading());
96
+ }
97
+
98
+ TEST(TempMonitor, has_valid_reading_false_when_no_value) {
99
+ FakeSensor fake;
100
+ fake.connected = true;
101
+ fake.fake_value = std::nullopt;
102
+ TempMonitor monitor(&fake);
103
+ EXPECT_FALSE(monitor.has_valid_reading());
104
+ }
105
+ ```
106
+
107
+ ### Trade-offs
108
+
109
+ | Pros | Cons |
110
+ |------|------|
111
+ | Full control over mock behavior | Requires interface in production code |
112
+ | Works with `gmock` (`ON_CALL`, `EXPECT_CALL`) | Adds indirection to class design |
113
+ | Tests are fully isolated | Can be verbose for simple cases |
114
+
115
+ ---
116
+
117
+ ## 2. Link-Time Seam
118
+
119
+ ### Concept
120
+
121
+ Compile a different version of a file at link time to replace the production implementation. The test links its own `.cpp` that provides a minimal or fake implementation of the dependency.
122
+
123
+ ### When to Use
124
+
125
+ - You cannot modify the existing production code to accept interfaces
126
+ - The dependency is a single global function or static method
127
+ - You want to test a module without any runtime injection mechanism
128
+
129
+ ### Code Example
130
+
131
+ ```cpp
132
+ // === main/policy/heartbeat.h ===
133
+ #pragma once
134
+ bool heartbeat_send(uint32_t interval_ms);
135
+
136
+ // === main/policy/heartbeat.cpp ===
137
+ #include "heartbeat.h"
138
+ #include "esp_timer.h" // FreeRTOS/ESP-IDF — not available on host
139
+
140
+ bool heartbeat_send(uint32_t interval_ms) {
141
+ esp_timer_start_periodic(..., interval_ms * 1000);
142
+ return true;
143
+ }
144
+
145
+ // === test/host/CMakeLists.txt ===
146
+ # Replace real heartbeat.cpp with stub at link time
147
+ add_executable(test_heartbeat
148
+ test_heartbeat.cpp
149
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../main/policy/heartbeat.cpp # production
150
+ ${CMAKE_CURRENT_SOURCE_DIR}/stubs/heartbeat_stub.cpp # our fake
151
+ )
152
+ ```
153
+
154
+ ```cpp
155
+ // === test/host/stubs/heartbeat_stub.cpp ===
156
+ #include "heartbeat.h"
157
+
158
+ bool heartbeat_send(uint32_t interval_ms) {
159
+ // No-op on host — just return true
160
+ (void)interval_ms;
161
+ return true;
162
+ }
163
+ ```
164
+
165
+ ```cpp
166
+ // === test/host/test_heartbeat.cpp ===
167
+ #include <gtest/gtest.h>
168
+ #include "heartbeat.h"
169
+
170
+ TEST(Heartbeat, send_returns_true) {
171
+ EXPECT_TRUE(heartbeat_send(1000));
172
+ }
173
+
174
+ TEST(Heartbeat, send_accepts_zero_interval) {
175
+ EXPECT_TRUE(heartbeat_send(0));
176
+ }
177
+ ```
178
+
179
+ ### Trade-offs
180
+
181
+ | Pros | Cons |
182
+ |------|------|
183
+ | No production code changes needed | Only works when you control what's linked |
184
+ | Works with any C/C++ function | Can mask integration issues if stub is too fake |
185
+ | Simple to implement | No runtime control — compile-time replacement only |
186
+
187
+ ---
188
+
189
+ ## 3. `std::function` Injection
190
+
191
+ ### Concept
192
+
193
+ Make behavior injectable via `std::function` member variables set at construction or via setters. No interface hierarchy needed.
194
+
195
+ ### When to Use
196
+
197
+ - You want minimal boilerplate
198
+ - The dependency is a simple callable (function, functor, lambda)
199
+ - You don't need `gmock` expectations — simple behavior is enough
200
+
201
+ ### Code Example
202
+
203
+ ```cpp
204
+ // === main/policy/resp_fallback.h ===
205
+ #pragma once
206
+ #include <functional>
207
+ #include "resp_fallback_types.h" // Response, FallbackResult
208
+
209
+ class RespFallback {
210
+ public:
211
+ using response_check_t = std::function<bool(const Response&)>;
212
+ using fallback_fn_t = std::function<FallbackResult(const Response*, Response*)>;
213
+
214
+ explicit RespFallback(response_check_t checker = nullptr,
215
+ fallback_fn_t fallback_fn = nullptr)
216
+ : checker_(std::move(checker)), fallback_fn_(std::move(fallback_fn)) {}
217
+
218
+ FallbackResult process(const Response* req, Response* out);
219
+
220
+ private:
221
+ response_check_t checker_;
222
+ fallback_fn_t fallback_fn_;
223
+ };
224
+
225
+ // === test/host/test_resp_fallback.cpp ===
226
+ #include <gtest/gtest.h>
227
+ #include "resp_fallback.h"
228
+
229
+ TEST(RespFallback, uses_checker_when_provided) {
230
+ bool checker_called = false;
231
+ RespFallback rf(
232
+ [&checker_called](const Response&) {
233
+ checker_called = true;
234
+ return true;
235
+ },
236
+ nullptr
237
+ );
238
+ Response out;
239
+ Response in = { .payload = "test", .payload_len = 4, .status_code = 500 };
240
+ rf.process(&in, &out);
241
+ EXPECT_TRUE(checker_called);
242
+ }
243
+
244
+ TEST(RespFallback, uses_fallback_fn_when_checker_fails) {
245
+ bool fallback_called = false;
246
+ RespFallback rf(
247
+ [](const Response&) { return false; }, // checker returns false
248
+ [&fallback_called](const Response*, Response* out) {
249
+ fallback_called = true;
250
+ out->payload = "fallback";
251
+ out->status_code = 200;
252
+ return FallbackResult::FALLBACK_OK;
253
+ }
254
+ );
255
+ Response out;
256
+ Response in = { .payload = "error", .payload_len = 5, .status_code = 500 };
257
+ FallbackResult r = rf.process(&in, &out);
258
+ EXPECT_TRUE(fallback_called);
259
+ EXPECT_EQ(r, FallbackResult::FALLBACK_OK);
260
+ }
261
+ ```
262
+
263
+ ### Trade-offs
264
+
265
+ | Pros | Cons |
266
+ |------|------|
267
+ | Zero boilerplate — no interface needed | Cannot use `EXPECT_CALL` / `ON_CALL` with gmock |
268
+ | Lambda-friendly | Lambdas can't be stored in `std::function` if they capture |
269
+ | Works with existing code (add a setter) | Tests must provide real behavior if production doesn't |
270
+
271
+ ---
272
+
273
+ ## 4. gmock (GoogleMock) — Struct-Level MATCHER
274
+
275
+ When using GoogleTest, `gmock` provides `MATCHER` and `ON_CALL` for fine-grained expectations on structs:
276
+
277
+ ```cpp
278
+ #include <gmock/gmock.h>
279
+ #include <gtest/gtest.h>
280
+
281
+ struct Reading {
282
+ double temperature;
283
+ uint32_t timestamp;
284
+ };
285
+
286
+ MATCHER_P(TempNear, expected, "") {
287
+ return std::abs(arg.temperature - expected) < 0.5;
288
+ }
289
+
290
+ TEST(Sensor, reading_near_expected) {
291
+ ::testing::MockFunction<bool(const Reading&)> mock_check;
292
+ ON_CALL(mock_check, Call)
293
+ .WillByDefault(::testing::Return(true));
294
+
295
+ EXPECT_CALL(mock_check, Call(TempNear(25.0))).Times(1);
296
+
297
+ Reading r = { 25.1, 123456 };
298
+ mock_check.Call(r);
299
+ }
300
+ ```
301
+
302
+ For simple structs, `MATCHER` is cleaner than writing a full fake class.
303
+
304
+ ---
305
+
306
+ ## Choosing a Strategy
307
+
308
+ | Situation | Recommended Strategy |
309
+ |-----------|---------------------|
310
+ | New code, you control design | Abstract Interface + `gmock` |
311
+ | Testing existing code with no interface | Link-Time Seam |
312
+ | Simple callable dependency, no gmock needed | `std::function` Injection |
313
+ | Struct-level assertions | `MATCHER` / `ON_CALL` |
314
+ | Need `EXPECT_CALL` with complex behavior | Abstract Interface + `gmock` |
315
+ | FreeRTOS/ESP-IDF call in production code | Link-Time Seam (stub at link) |
316
+
317
+ ---
318
+
319
+ ## Anti-Patterns
320
+
321
+ ### Don't Over-Mock
322
+
323
+ ```cpp
324
+ // BAD — mocking everything means you're not testing real code
325
+ TEST(System, processes_data) {
326
+ auto mock_storage = std::make_unique<MockStorage>();
327
+ auto mock_network = std::make_unique<MockNetwork>();
328
+ ON_CALL(*mock_storage, read).WillByDefault(...);
329
+ ON_CALL(*mock_network, send).WillByDefault(...);
330
+ System sys(std::move(mock_storage), std::move(mock_network));
331
+ // This tests nothing real
332
+ }
333
+ ```
334
+
335
+ **Better:** Test `System` with real `Storage` and mocked `Network`, or vice versa. Only mock at boundaries.
336
+
337
+ ### Don't Mock Value Types
338
+
339
+ ```cpp
340
+ // BAD — no need to mock a simple struct
341
+ struct Config { int timeout_ms; bool enabled; };
342
+
343
+ // GOOD — construct directly, no mock needed
344
+ Config cfg{1000, true};
345
+ ```
346
+
347
+ ### Don't Mock Stateless Utilities
348
+
349
+ ```cpp
350
+ // BAD — mocking a pure function like strlen is wasteful
351
+ // GOOD — just call strlen in tests directly
352
+ EXPECT_EQ(strlen("hello"), 5);
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Combining Strategies
358
+
359
+ You can combine strategies in the same project. For example:
360
+ - Use **Abstract Interface** for `ISensor`, `IStorage` (core domain interfaces)
361
+ - Use **Link-Time Seam** for FreeRTOS / ESP-IDF API calls
362
+ - Use **`std::function`** for simple policy callbacks
363
+
364
+ The key principle: **mock at boundaries, not deep inside the system**.
@@ -0,0 +1,308 @@
1
+ # TDD Workflow for C++
2
+
3
+ ## Overview
4
+
5
+ Test-Driven Development (TDD) follows a short red-green-refactor cycle:
6
+
7
+ 1. **Red** — Write a failing test before touching production code.
8
+ 2. **Green** — Write the minimum production code to make the test pass.
9
+ 3. **Refactor** — Clean up both test and production code, keeping tests green.
10
+
11
+ The goal is **narrow, fast feedback**: every line of production code has a failing test that motivated it.
12
+
13
+ ---
14
+
15
+ ## The Cycle in Detail
16
+
17
+ ### Step 1: Red — Write a Failing Test
18
+
19
+ Write the smallest possible test that describes the behavior you want:
20
+
21
+ ```cpp
22
+ // test/host/test_rate_limiter.cpp
23
+ #include <gtest/gtest.h>
24
+ #include "rate_limiter.h"
25
+
26
+ TEST(RateLimiter, allows_request_under_limit) {
27
+ RateLimiter rl(10); // 10 requests per second
28
+ // First request should be allowed
29
+ EXPECT_TRUE(rl.allow());
30
+ }
31
+ ```
32
+
33
+ Compile and run. It **must fail** because `rate_limiter.h` / `rate_limiter.cpp` don't exist yet:
34
+
35
+ ```
36
+ error: 'RateLimiter' file not found
37
+ ```
38
+
39
+ This is the correct outcome — you've specified what you want before building it.
40
+
41
+ ### Step 2: Green — Write Minimum Production Code
42
+
43
+ Create the stub to make it compile, then the implementation to make it pass:
44
+
45
+ ```cpp
46
+ // main/policy/rate_limiter.h
47
+ #pragma once
48
+ class RateLimiter {
49
+ public:
50
+ explicit RateLimiter(int max_per_second);
51
+ bool allow();
52
+ };
53
+ ```
54
+
55
+ ```cpp
56
+ // main/policy/rate_limiter.cpp
57
+ #include "rate_limiter.h"
58
+
59
+ RateLimiter::RateLimiter(int max_per_second) {}
60
+ bool RateLimiter::allow() { return true; } // minimum: always allow
61
+ ```
62
+
63
+ Run tests — they should pass. You now have a compiling, passing test.
64
+
65
+ ### Step 3: Refactor
66
+
67
+ Now add the second test:
68
+
69
+ ```cpp
70
+ TEST(RateLimiter, blocks_request_over_limit) {
71
+ RateLimiter rl(2);
72
+ rl.allow(); // request 1
73
+ rl.allow(); // request 2
74
+ EXPECT_FALSE(rl.allow()); // request 3 — over limit
75
+ }
76
+ ```
77
+
78
+ Run tests — this new test **fails** (current impl always returns `true`). Go back to Step 2.
79
+
80
+ ```cpp
81
+ // main/policy/rate_limiter.cpp — minimum change
82
+ #include "rate_limiter.h"
83
+ #include <atomic>
84
+
85
+ RateLimiter::RateLimiter(int max_per_second) : max_per_second_(max_per_second), count_(0) {}
86
+
87
+ bool RateLimiter::allow() {
88
+ int current = count_.load();
89
+ if (current >= max_per_second_) return false;
90
+ count_.store(current + 1);
91
+ return true;
92
+ }
93
+ ```
94
+
95
+ Run tests — all pass. Repeat the cycle.
96
+
97
+ ---
98
+
99
+ ## Structuring Tests for TDD
100
+
101
+ ### Arrange-Act-Assert (AAA)
102
+
103
+ ```cpp
104
+ TEST(RateLimiter, blocks_over_limit) {
105
+ // Arrange
106
+ RateLimiter rl(1);
107
+ rl.allow(); // consume the one allowed request
108
+
109
+ // Act
110
+ bool allowed = rl.allow();
111
+
112
+ // Assert
113
+ EXPECT_FALSE(allowed);
114
+ }
115
+ ```
116
+
117
+ ### One Logical Assertion Per Test
118
+
119
+ Prefer multiple `EXPECT_*` calls that are all checking related aspects of one behavior over one test per `EXPECT_*`. Example:
120
+
121
+ ```cpp
122
+ // Good — one test, multiple related EXPECTs
123
+ TEST(RespFallback, error_returns_fallback_and_sets_status) {
124
+ Response in = { .payload = "error", .payload_len = 5, .status_code = 500 };
125
+ Response out;
126
+ FallbackResult r = resp_fallback_process(&in, &out);
127
+ EXPECT_EQ(r, FALLBACK_OK);
128
+ EXPECT_EQ(out.status_code, 200);
129
+ EXPECT_STREQ(out.payload, "default_response");
130
+ }
131
+ ```
132
+
133
+ ### Test Naming for TDD
134
+
135
+ Name tests to describe **behavior**, not implementation:
136
+
137
+ ```
138
+ GOOD: RespFallback_returns_fallback_when_upstream_fails
139
+ GOOD: Datacollector_append_returns_minus_one_when_full
140
+ BAD: TestRespFallback (no behavior described)
141
+ BAD: Test1 (meaningless)
142
+ ```
143
+
144
+ ---
145
+
146
+ ## TDD for Embedded Firmware Modules
147
+
148
+ ### Example: `feature_flags.cpp` from scratch
149
+
150
+ **Start:** You have an empty `feature_flags.cpp` with only a header stub.
151
+
152
+ **Test 1:** `is_active_returns_false_for_unknown_feature`
153
+
154
+ ```cpp
155
+ TEST(FeatureFlags, is_active_returns_false_for_unknown_feature) {
156
+ EXPECT_FALSE(feature_flags_is_active("nonexistent"));
157
+ }
158
+ ```
159
+
160
+ Compile → link error (no implementation). Add stub:
161
+
162
+ ```cpp
163
+ bool feature_flags_is_active(const char* name) { return false; }
164
+ ```
165
+
166
+ Test passes. (You chose the simplest implementation that makes the test pass.)
167
+
168
+ **Test 2:** `is_active_returns_true_for_known_feature`
169
+
170
+ ```cpp
171
+ TEST(FeatureFlags, is_active_returns_true_for_known_feature) {
172
+ EXPECT_TRUE(feature_flags_is_active("debug"));
173
+ }
174
+ ```
175
+
176
+ Test fails (still returns `false`). Implement:
177
+
178
+ ```cpp
179
+ bool feature_flags_is_active(const char* name) {
180
+ if (strcmp(name, "debug") == 0) return true;
181
+ return false;
182
+ }
183
+ ```
184
+
185
+ Test passes.
186
+
187
+ **Test 3:** `is_active_returns_false_for_nullptr`
188
+
189
+ ```cpp
190
+ TEST(FeatureFlags, is_active_returns_false_for_nullptr) {
191
+ EXPECT_FALSE(feature_flags_is_active(nullptr));
192
+ }
193
+ ```
194
+
195
+ Add the null check. Tests pass.
196
+
197
+ **Refactor:** Replace chain of `if/strcmp` with a lookup table:
198
+
199
+ ```cpp
200
+ static bool flags[] = { false, false, false }; // debug=0, trace=1, legacy=2
201
+ ```
202
+
203
+ The tests **still pass** because they assert on behavior, not representation.
204
+
205
+ ---
206
+
207
+ ## Red-Green-Refactor in Practice
208
+
209
+ ### Red Flags (stop and refactor if you see these)
210
+
211
+ - **Test takes too long to write** — break it into smaller pieces
212
+ - **Test requires many mocks** — the module under test may have too many dependencies (consider interface injection)
213
+ - **Production code can't be tested in isolation** — the module needs refactoring before tests can be written
214
+ - **Tests are brittle** — if renaming a private method breaks a test, you're testing the wrong thing
215
+
216
+ ### Refactoring Rules
217
+
218
+ 1. **Never change tests to make production code pass.** Change production code to make tests pass.
219
+ 2. **Keep tests deterministic.** No random values, no timing dependencies.
220
+ 3. **Test behavior, not implementation.** If you refactor internal representation and tests break, the tests were overspecified.
221
+ 4. **Run the full test suite after every refactor** — green before, green after.
222
+
223
+ ---
224
+
225
+ ## What to Test First
226
+
227
+ ### Priority 1: Happy Path (primary behavior)
228
+
229
+ ```cpp
230
+ TEST(RateLimiter, allows_requests_under_limit) { ... }
231
+ ```
232
+
233
+ ### Priority 2: Edge Cases (boundary values)
234
+
235
+ ```cpp
236
+ TEST(RateLimiter, allows_zero_limit_as_always_blocked) { ... }
237
+ TEST(RateLimiter, handles_negative_limit) { ... } // invalid input
238
+ ```
239
+
240
+ ### Priority 3: Error Paths
241
+
242
+ ```cpp
243
+ TEST(Datacollector, append_returns_error_when_full) { ... }
244
+ TEST(RespFallback, returns_invalid_when_nullptr) { ... }
245
+ ```
246
+
247
+ ### Priority 4: Negative Tests (the "what shouldn't happen")
248
+
249
+ ```cpp
250
+ TEST(FeatureFlags, is_active_does_not_crash_on_nullptr) { ... }
251
+ TEST(RateLimiter, does_not_allow_over_limit_regardless_of_speed) { ... }
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Test Coverage in TDD
257
+
258
+ TDD naturally drives coverage high because:
259
+ - Every line of production code was written to satisfy a test
260
+ - You can't add code without a failing test first
261
+
262
+ However, TDD alone doesn't guarantee 80% coverage. Run `lcov` after the full suite to identify untested branches:
263
+
264
+ ```bash
265
+ cmake --build build -j$(nproc)
266
+ ./test_policy
267
+ lcov --capture --directory build --output coverage.info \
268
+ --exclude '*/test_*' --exclude '*/stubs/*' --exclude '*/build/*'
269
+ lcov --list coverage.info # inspect
270
+ ```
271
+
272
+ Add missing tests for any uncovered lines before opening a PR.
273
+
274
+ ---
275
+
276
+ ## When TDD Is Not Worth It
277
+
278
+ - **Trivial accessors** — `getter()` / `setter()` with no logic
279
+ - **Generated code** — auto-generated `serde`, `protobuf` bindings
280
+ - **One-off scripts** — not part of the production codebase
281
+ - **Quick prototypes** — exploratory code that will be thrown away
282
+
283
+ For these, write tests **after** if the code becomes permanent.
284
+
285
+ ---
286
+
287
+ ## TDD and the 80% Coverage Gate
288
+
289
+ TDD gets you to ~60-70% coverage naturally. The last 10-20% requires disciplined addition of:
290
+
291
+ 1. **Branch coverage** — every `if/else` and `switch` branch
292
+ 2. **Error paths** — every function that can return an error code
293
+ 3. **Negative tests** — every `if (ptr == nullptr)` path
294
+
295
+ Use `lcov --list coverage.info | grep -E "BRAN|BR" ` to find uncovered branches.
296
+
297
+ ---
298
+
299
+ ## Summary: TDD Checklist
300
+
301
+ Before each commit:
302
+ - [ ] Every new public method has at least one test
303
+ - [ ] Tests follow AAA structure
304
+ - [ ] Test names describe behavior (Subject_Behavior_Expected)
305
+ - [ ] Tests pass on first run (green)
306
+ - [ ] No test is skipped (`DISABLED_`) unless there's a tracked issue
307
+ - [ ] 80% line coverage gate passes (`lcov`)
308
+ - [ ] No new `// TODO` or `// FIXME` in test code
@@ -0,0 +1,41 @@
1
+ # Embedded ESP-IDF Skill
2
+
3
+ ESP-IDF v5.x C++ firmware patterns for opencode. Covers the `idf.py` workflow, FreeRTOS, IRAM/DRAM/PSRAM memory model, packed binary protocols with `static_assert`, Kconfig, drivers (I2C/SPI/GPIO/ADC/NVS/BLE/ESP-NOW), power management, and host-side tests that compile firmware headers without `idf.py`.
4
+
5
+ ## What it provides
6
+
7
+ - **SKILL.md** — quick start + 7 deep-dive references + 2 helper scripts
8
+ - **references/idf-py-commands.md** — every `idf.py` subcommand + exit codes
9
+ - **references/freertos-patterns.md** — task lifecycle, ISR-to-task handoff, ringbuffer streams
10
+ - **references/memory-and-iram.md** — IRAM/DRAM/PSRAM model, `IRAM_ATTR`, `MALLOC_CAP_*`
11
+ - **references/kconfig.md** — `depends on` / `select` / `imply` / `range` / `choice`
12
+ - **references/packed-structs.md** — `__attribute__((packed))` + `static_assert(sizeof == N)`
13
+ - **references/logging-discipline.md** — `ESP_LOG*` + low-rate aggregate lines
14
+ - **references/host-tests.md** — host tests without `idf.py`
15
+ - **scripts/idf_env.sh** — sources the vendored ESP-IDF env, validates `idf.py` is reachable
16
+ - **scripts/size_check.sh** — runs the project size budget check (AMS7-aware) or falls back to `idf.py size`
17
+
18
+ ## When it triggers
19
+
20
+ - Writing, reviewing, or debugging ESP-IDF C++ firmware
21
+ - Working with `idf.py build`/`flash`/`monitor`/`menuconfig`/`size`
22
+ - FreeRTOS task/queue/semaphore/mutex/ISR work
23
+ - IRAM/DRAM/PSRAM budgeting
24
+ - Packed binary protocols with `static_assert` on `sizeof`
25
+ - NVS, BLE/NimBLE, ESP-NOW
26
+ - Deep-sleep / light-sleep
27
+ - Adding Kconfig options
28
+ - Host-side C++ unit tests that compile firmware headers without `idf.py`
29
+
30
+ ## AMS7-specific extensions
31
+
32
+ This skill was first authored for the AMS7 ambulatory-monitoring firmware at `/projects/ams7_esp32`. Rules tagged `(AMS7)` are project-specific — do not silently generalize them to other ESP-IDF projects. Universal ESP-IDF rules are the default.
33
+
34
+ ## Manual install
35
+
36
+ ```bash
37
+ cp -R SKILL.md references scripts ~/.opencode/skills/embedded-esp-idf/
38
+ chmod +x ~/.opencode/skills/embedded-esp-idf/scripts/*.sh
39
+ ```
40
+
41
+ The BizarHarness installer can also install this automatically — select the **Embedded ESP-IDF** component.