@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,164 @@
1
+ # Host-side tests
2
+
3
+ ESP-IDF firmware that depends on FreeRTOS, drivers, and NVS cannot run on the host. But policy modules, payload encoders/decoders, pure-C++ algorithms, and protocol parsers can — and should — be unit-tested on the host with a normal C++ toolchain.
4
+
5
+ `(AMS7)` AMS7's host tests are deliberately lightweight: one `.cpp` test file + one `run_*_test.sh` shell wrapper. No CMake host build, no fakes library, no GoogleTest. The whole test is `g++ -std=c++17 ... && ./a.out`.
6
+
7
+ For larger projects with GoogleTest / Catch2 / fakes, see the **`cpp-testing`** skill.
8
+
9
+ ## AMS7 pattern (test + run script)
10
+
11
+ A typical test file in `tests/connectivity/`:
12
+
13
+ ```cpp
14
+ // tests/connectivity/espnow_imu_frame_test.cpp
15
+ #include <cassert>
16
+ #include <cstdio>
17
+
18
+ #include "espnow_imu_frame.hpp"
19
+
20
+ int main() {
21
+ core_espnow_imu_frame_v1_t frame = {};
22
+ espnow_imu_frame_init(&frame, 0x1234U, 9U, 55U,
23
+ 1, -2, 3, -4, 5, -6);
24
+
25
+ assert(frame.magic == CORE_ESPNOW_IMU_MAGIC);
26
+ assert(frame.version == CORE_ESPNOW_IMU_VERSION);
27
+ assert(frame.frame_type == CORE_ESPNOW_FRAME_IMU_RAW);
28
+ assert(frame.id == 0x1234U);
29
+ assert(frame.seq == 9U);
30
+ assert(frame.age_ms == 55U);
31
+ assert(frame.accel_x == 1);
32
+ assert(frame.accel_y == -2);
33
+ assert(frame.accel_z == 3);
34
+ assert(frame.gyro_x == -4);
35
+ assert(frame.gyro_y == 5);
36
+ assert(frame.gyro_z == -6);
37
+
38
+ std::puts("espnow_imu_frame_test: ok");
39
+ return 0;
40
+ }
41
+ ```
42
+
43
+ And the wrapper `tests/connectivity/run_espnow_imu_frame_test.sh`:
44
+
45
+ ```bash
46
+ #!/usr/bin/env bash
47
+ set -euo pipefail
48
+
49
+ g++ -std=c++17 \
50
+ tests/connectivity/espnow_imu_frame_test.cpp \
51
+ main/connectivity/espnow_imu_frame.cpp \
52
+ -I main/connectivity \
53
+ -o /tmp/espnow_imu_frame_test
54
+
55
+ /tmp/espnow_imu_frame_test
56
+ ```
57
+
58
+ Run it:
59
+
60
+ ```bash
61
+ bash tests/connectivity/run_espnow_imu_frame_test.sh
62
+ ```
63
+
64
+ The wrapper does three things: compile, link, run. Add `g++` flags (`-Wall -Wextra -Werror`) for stricter checking.
65
+
66
+ ## What can be host-tested
67
+
68
+ - **Packed structs and frame encoders** — `espnow_*_frame_*`, `gateway_command_*`, `marker_*`. Pure data marshalling.
69
+ - **Policy modules** — `connectivity_mode_policy`, `electrode_detach_gate`, `acquisition_command_policy`. Pure decision logic.
70
+ - **Pure algorithms** — `hr_fused`, `resp_fallback`, `summary_metrics`. Make sure they don't `malloc` or call FreeRTOS and they're trivially host-testable.
71
+ - **State machines** — anything that's a transition table plus a few event handlers.
72
+
73
+ ## What must NOT be host-tested
74
+
75
+ - Anything that includes `<freertos/FreeRTOS.h>`.
76
+ - Anything that includes `esp_log.h` (unless you fake it).
77
+ - Anything that includes driver headers (`driver/spi_master.h`, `driver/i2c.h`).
78
+ - Anything that pulls in NVS, BLE, Wi-Fi, ESP-NOW stacks.
79
+
80
+ If a header you want to test transitively includes one of these, factor the pure logic out into a separate translation unit. The split is usually: `policy.hpp/cpp` for pure logic, `policy_runtime.cpp` for the FreeRTOS-aware glue.
81
+
82
+ ## Fakes for FreeRTOS (when you need them)
83
+
84
+ If you have a real CMake host project (not the AMS7 minimal pattern) and want to test code that touches FreeRTOS, use the **`cpp-testing`** skill's fakes recipe. The minimum useful fakes:
85
+
86
+ - `fake_freertos.h` — declares the FreeRTOS API surface used by the code under test, with hooks (`xQueueCreate` returns a recorded handle, etc.).
87
+ - `fake_log.h` — empty `ESP_LOG*` macros or a recorded message list.
88
+
89
+ Keep fakes local to the test, not in `main/`. Use `BUILD_ONLY_FAKES` or a separate `tests/host/` CMake target.
90
+
91
+ ## CMake host project (alternative to per-test shell scripts)
92
+
93
+ For larger suites:
94
+
95
+ ```cmake
96
+ # tests/host/CMakeLists.txt
97
+ cmake_minimum_required(VERSION 3.16)
98
+ project(ams7_host_tests CXX)
99
+
100
+ set(CMAKE_CXX_STANDARD 17)
101
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
102
+
103
+ add_executable(test_espnow_imu
104
+ ../../main/connectivity/espnow_imu_frame.cpp
105
+ espnow_imu_frame_test.cpp
106
+ )
107
+ target_include_directories(test_espnow_imu PRIVATE
108
+ ${CMAKE_SOURCE_DIR}/../../main/connectivity
109
+ )
110
+ ```
111
+
112
+ ```bash
113
+ cmake -S tests/host -B tests/host/build
114
+ cmake --build tests/host/build
115
+ ./tests/host/build/test_espnow_imu
116
+ ```
117
+
118
+ Tradeoffs vs the AMS7 minimal pattern:
119
+
120
+ | Aspect | AMS7 shell wrapper | CMake host project |
121
+ |------------------|---------------------------------|---------------------------|
122
+ | Setup cost | Zero — one shell script per test| One CMakeLists to maintain |
123
+ | Linking firmware | Direct `g++ ... file.cpp` | Same, via CMake rules |
124
+ | Adding fakes | None | Add a `fake/` directory |
125
+ | Test discovery | Manual | `ctest` |
126
+ | CI integration | `find tests -name run_*.sh` | `ctest --output-on-failure` |
127
+ | Build parallelism| Sequential | Parallel via `cmake --build` |
128
+
129
+ Both are valid. AMS7 picks the shell wrapper because every test is one file and one assertion set — `ctest` overhead is not worth it.
130
+
131
+ ## Tests vs fixtures
132
+
133
+ AMS7 has `tests/fixtures/` for replay inputs (recorded sensor traces used to drive host tests):
134
+
135
+ ```cpp
136
+ // tests/fixtures/replay/ecg_2024-09-12.bin — raw bytes recorded from a real run
137
+ ```
138
+
139
+ Load fixtures in tests via `std::ifstream` rather than copy-pasting bytes into the test source.
140
+
141
+ ## Common pitfalls
142
+
143
+ - **Including `<esp_log.h>` from a header that's host-tested.** Fix: forward-declare the log macros in the header, include the real one only in `.cpp`. Or include the AMS7 fake `tests/support/host_include/esp_log.h` if one exists.
144
+ - **`malloc` in pure logic.** Symptom: host test runs fine but firmware crashes from fragmentation. Fix: allocate in the wrapper, pass a buffer in.
145
+ - **Globals with static initialization.** Symptom: works on host, fails on target because init order is different. Fix: prefer explicit `init()` calls.
146
+ - **Asserting on floating-point equality.** Use a tolerance: `assert(std::abs(a - b) < 1e-3)`.
147
+ - **Forgetting `-Werror` in CI.** Compile-only warnings become ship-time bugs. The AMS7 host tests are silent on warnings by default; consider adding `-Wall -Wextra` to your shell wrappers.
148
+ - **Test passes on host, fails on firmware.** The two are usually equivalent for pure logic, but always cross-check with a firmware-side integration test (a debug print, a recorded trace, or an ESP-NOW roundtrip).
149
+
150
+ ## When to grow beyond the minimal pattern
151
+
152
+ Stay with the shell wrapper while:
153
+
154
+ - Test count is < ~30.
155
+ - Each test is one `.cpp` plus one shell script.
156
+ - No fakes needed.
157
+ - CI just iterates `tests/ -name run_*.sh`.
158
+
159
+ Migrate to a CMake host project when:
160
+
161
+ - You need `ctest` for CI parallelism and reporting.
162
+ - You need fakes (FreeRTOS, log, drivers).
163
+ - A test needs to link multiple firmware modules together.
164
+ - You want fixtures automatically discovered.
@@ -0,0 +1,157 @@
1
+ # idf.py commands
2
+
3
+ `idf.py` is the single entry point to ESP-IDF v5. All commands assume the IDF environment is sourced:
4
+
5
+ ```bash
6
+ source esp/esp-idf/export.sh
7
+ ```
8
+
9
+ If `idf.py` is not on PATH, `scripts/idf_env.sh` will print the source line and tell you whether the env is healthy.
10
+
11
+ ## Core workflow
12
+
13
+ ```bash
14
+ idf.py build # compile (incremental)
15
+ idf.py -p /dev/ttyUSB0 flash # write image to board
16
+ idf.py -p /dev/ttyUSB0 monitor # serial console (Ctrl-] to exit)
17
+ idf.py -p /dev/ttyUSB0 flash monitor # flash then monitor in one shot
18
+ ```
19
+
20
+ `-p` is the serial port. Linux: `/dev/ttyUSB0` or `/dev/ttyACM0`. macOS: `/dev/cu.usbserial-*`. Windows: `COM3`.
21
+
22
+ ## Configuration
23
+
24
+ ```bash
25
+ idf.py menuconfig # TUI Kconfig editor
26
+ idf.py menuconfig --no-target # don't ask to set target
27
+ idf.py save-defconfig # write minimal defconfig from current settings
28
+ ```
29
+
30
+ `menuconfig` writes the result to `sdkconfig`. To apply a layered profile:
31
+
32
+ ```bash
33
+ idf.py -B build_espnow_flow -C . \
34
+ --sdkconfig sdkconfig.espnow_flow \
35
+ build flash
36
+ ```
37
+
38
+ The `-B build_<name>` puts the build output in `build_<name>/`, leaving the default `build/` clean.
39
+
40
+ ## Build inspection
41
+
42
+ ```bash
43
+ idf.py size # text: app partition + IRAM + DRAM
44
+ idf.py size --format json # machine-readable for tooling
45
+ idf.py size --format csv # spreadsheet-friendly
46
+ idf.py size --output-file size.json
47
+ idf.py size-components # per-component breakdown
48
+ ```
49
+
50
+ `idf.py size` exit code is 0 on success regardless of size; size policy lives in project-specific tools.
51
+
52
+ ## Cleaning
53
+
54
+ ```bash
55
+ idf.py clean # remove build artifacts but keep config
56
+ idf.py fullclean # remove build/ AND sdkconfig (then menuconfig again)
57
+ idf.py -B build_espnow_flow clean # clean a specific build dir
58
+ ```
59
+
60
+ `fullclean` is destructive — it deletes your local `sdkconfig` overrides. Commit your `sdkconfig` and any profile files first.
61
+
62
+ ## Target and project
63
+
64
+ ```bash
65
+ idf.py set-target esp32 # esp32 / esp32s2 / esp32s3 / esp32c3 / esp32h2
66
+ idf.py create-project foo # scaffold a new project from a template
67
+ idf.py add-dependency "owner/repo^1.2.3" # pull into managed_components/
68
+ ```
69
+
70
+ After `set-target`, ESP-IDF regenerates component configs — clean build artifacts:
71
+
72
+ ```bash
73
+ idf.py set-target esp32s3 && idf.py build
74
+ ```
75
+
76
+ ## Partition table
77
+
78
+ ```bash
79
+ idf.py partition-table # regenerate partition_table/*.bin from partitions.csv
80
+ idf.py erase-flash # erase entire flash (use with care)
81
+ ```
82
+
83
+ `partitions.csv` is at the project root; `idf.py menuconfig → Partition Table` selects which CSV to use. Common layouts:
84
+
85
+ - `partitions_singleapp.csv` — single OTA-less app partition
86
+ - `partitions_two_ota.csv` — A/B OTA slots (default for OTA-enabled projects)
87
+ - A custom CSV — declare `app`, `data`, `nvs`, `phy_init`, `storage` partitions with explicit sizes
88
+
89
+ `(AMS7)` The AMS7 partition table reserves a large `storage` partition for SD-aware OTA staging.
90
+
91
+ ## Monitor filters
92
+
93
+ ```bash
94
+ idf.py -p /dev/ttyUSB0 monitor --print-filter="*:I" # only INFO and above
95
+ idf.py monitor --print-filter="ams7driver:I espnow_summary:W" # per-tag levels
96
+ idf.py monitor --elf <elf_path> # decode panic backtraces
97
+ ```
98
+
99
+ `-d` disables line-ending conversion (binary output). `-t` adds a timestamp prefix.
100
+
101
+ Inside the monitor, useful key bindings:
102
+
103
+ - `Ctrl-]` — exit
104
+ - `Ctrl-T` — toggle menu (send break, change filter, etc.)
105
+
106
+ ## Build flags
107
+
108
+ ```bash
109
+ idf.py -DCMAKE_BUILD_TYPE=Debug build # Debug / Release / RelWithDebInfo / MinSizeRel
110
+ idf.py -DCONFIG_COMPILER_OPTIMIZATION_PERF=y build # enable IDF performance optimizations
111
+ idf.py -DSDKCONFIG_DEFAULTS=sdkconfig.espnow_flow build
112
+ ```
113
+
114
+ `-DSDKCONFIG_DEFAULTS` is equivalent to the `--sdkconfig` CLI flag and is useful for CI.
115
+
116
+ ## OpenOCD / debugging
117
+
118
+ ```bash
119
+ idf.py openocd # start OpenOCD for JTAG debugging
120
+ idf.py gdb # start xtensa-esp32-elf-gdb
121
+ idf.py gdbgui # GDB with a frontend
122
+ idf.py app-flash # flash only the app partition
123
+ idf.py bootloader-flash # flash only the bootloader
124
+ ```
125
+
126
+ GDB requires an `openocd` server on the default port (3333).
127
+
128
+ ## OTA
129
+
130
+ ```bash
131
+ idf.py app # build the app, ready for OTA push
132
+ idf.py ota # ... or `idf.py app` then push via HTTP
133
+ ```
134
+
135
+ For SD-card OTA staging: copy `build/<project>.bin` to the SD card under the OTA path your bootloader expects, then reboot.
136
+
137
+ ## Useful exit-code patterns
138
+
139
+ ```bash
140
+ # CI: build, then size check
141
+ idf.py build && idf.py size
142
+
143
+ # AMS7: full CI gate
144
+ source esp/esp-idf/export.sh
145
+ idf.py build
146
+ tools/check_idf_size_budget.py
147
+ ```
148
+
149
+ `scripts/size_check.sh` wraps the AMS7-specific size policy and falls back to `idf.py size` when run in a non-AMS7 project.
150
+
151
+ ## Common pitfalls
152
+
153
+ - **Forgot to source the env.** Symptom: `idf.py: command not found`. Fix: `source esp/esp-idf/export.sh` per shell session.
154
+ - **Wrong target.** Symptom: linker errors about `esp_image_header_t` size mismatches or missing `esp_hw_support`. Fix: `idf.py set-target esp32` then rebuild.
155
+ - **`sdkconfig` drift.** Symptom: subtle behavior changes after a `git pull`. Fix: `idf.py menuconfig` to re-resolve, or `git checkout sdkconfig` and re-apply.
156
+ - **`-B build_<flow>` left stale.** Symptom: build picks up old sources. Fix: `idf.py -B build_<flow> fullclean`.
157
+ - **Monitor hangs on macOS.** Use `/dev/cu.usbserial-*` not `/dev/tty.usbserial-*` (the `tty.` node blocks open while the driver holds it).
@@ -0,0 +1,159 @@
1
+ # Kconfig
2
+
3
+ Kconfig is the build-time configuration system ESP-IDF inherits from the Linux kernel. Options live in `Kconfig` files, are presented through `idf.py menuconfig`, and resolve to `CONFIG_*` macros visible in C/C++.
4
+
5
+ ## Where Kconfig files live
6
+
7
+ ESP-IDF automatically sources a `Kconfig` file from every registered component. The top-level `Kconfig.projbuild` is required and seeds the project menu. Conventions:
8
+
9
+ - `main/Kconfig` — app-level options. Always include `main "AMS7 Project Options"` style menus.
10
+ - `components/<name>/Kconfig` — component-private options; only visible if the component is included in the build.
11
+ - `Kconfig.projbuild` — root-level options that gate top-of-tree decisions.
12
+
13
+ `(AMS7)` AMS7 puts its entire menu under `main/Kconfig` (single file, ~430 lines), with the menu title `"AMS7 Project Options"`. Every project-defined option uses the `AMS7_*` prefix.
14
+
15
+ ## Adding a simple bool option
16
+
17
+ ```kconfig
18
+ menu "Connectivity Workflow"
19
+
20
+ config AMS7_SUMMARY_ENABLE
21
+ bool "Enable ESP-NOW summary workflow"
22
+ default n
23
+ help
24
+ Master switch for the recording workflow that starts with BLE
25
+ control and then switches to ESP-NOW summary mode after
26
+ acquisition begins. Keep disabled to preserve the BLE-only
27
+ workflow.
28
+
29
+ endmenu
30
+ ```
31
+
32
+ The `CONFIG_AMS7_SUMMARY_ENABLE` macro is then visible everywhere in C/C++:
33
+
34
+ ```cpp
35
+ #if CONFIG_AMS7_SUMMARY_ENABLE
36
+ init_espnow_summary();
37
+ #endif
38
+ ```
39
+
40
+ ## Types
41
+
42
+ | Type | C macro | Notes |
43
+ |-------------|--------------------------------------|----------------------------------------|
44
+ | `bool` | `CONFIG_FOO` (1 or undefined) | `default y` or `default n` |
45
+ | `int` | `CONFIG_FOO` (int literal) | `range MIN MAX` is recommended |
46
+ | `hex` | `CONFIG_FOO` (hex literal) | Useful for bitmasks and addresses |
47
+ | `string` | `CONFIG_FOO` (string literal) | `default "literal"` |
48
+ | `choice` | selects one of several values | Use for mutually-exclusive options |
49
+
50
+ ```kconfig
51
+ config AMS7_HR_HOLD_MS
52
+ int "Hold time for fused HR before fallback (ms)"
53
+ depends on AMS7_VITALS_ENABLE
54
+ range 0 60000
55
+ default 10000
56
+ help
57
+ How long to keep reporting fused HR after the fused pipeline
58
+ reports a confident beat, before falling back to the simple
59
+ backend. 0 disables the hold.
60
+ ```
61
+
62
+ In code:
63
+
64
+ ```cpp
65
+ #if CONFIG_AMS7_HR_HOLD_MS > 0
66
+ const TickType_t kHoldTicks = pdMS_TO_TICKS(CONFIG_AMS7_HR_HOLD_MS);
67
+ #endif
68
+ ```
69
+
70
+ `menuconfig` values are stored in `sdkconfig` as `CONFIG_<NAME>=<value>` lines. `idf.py menuconfig` is the editor.
71
+
72
+ ## Dependencies and implications
73
+
74
+ ```kconfig
75
+ config AMS7_BLE_VITALS_ENABLE
76
+ bool "Enable custom BLE vitals characteristic"
77
+ depends on AMS7_VITALS_ENABLE
78
+ default y
79
+ ```
80
+
81
+ - `depends on AMS7_VITALS_ENABLE` — option is **invisible** unless `AMS7_VITALS_ENABLE=y`. Selecting it auto-selects the dep.
82
+ - `select AMS7_ESPNOW_ENABLE` — when **this** option is enabled, force `AMS7_ESPNOW_ENABLE=y`.
83
+ - `imply AMS7_ESPNOW_LIVE_ECG_ENABLE` — when **this** option is enabled, set `AMS7_ESPNOW_LIVE_ECG_ENABLE=y` unless the user explicitly disabled it (softer than `select`).
84
+
85
+ `select` chains can cause cycles if used carelessly — prefer `depends on` for prerequisites.
86
+
87
+ ## Ranges
88
+
89
+ ```kconfig
90
+ config AMS7_ESPNOW_WIFI_CHANNEL
91
+ int "ESP-NOW Wi-Fi channel"
92
+ range 1 13
93
+ default 6
94
+ ```
95
+
96
+ The value is validated by menuconfig; `range` rejects out-of-range entries.
97
+
98
+ ## Choices
99
+
100
+ ```kconfig
101
+ choice AMS7_HR_BACKEND
102
+ prompt "Heart-rate backend"
103
+ default AMS7_HR_BACKEND_FUSED
104
+ depends on AMS7_VITALS_ENABLE
105
+
106
+ config AMS7_HR_BACKEND_SIMPLE
107
+ bool "Simple R-peak detector"
108
+
109
+ config AMS7_HR_BACKEND_FUSED
110
+ bool "Fused multi-source pipeline"
111
+
112
+ endchoice
113
+ ```
114
+
115
+ Only one of the listed `config`s can be `y` at a time. Use `# CONFIG_AMS7_HR_BACKEND_FUSED is not set` to switch.
116
+
117
+ ## Sourcing custom Kconfig files
118
+
119
+ In a component's top-level `Kconfig` (sourced automatically), include sub-files:
120
+
121
+ ```kconfig
122
+ mainmenu "My Component"
123
+
124
+ menu "Sensor Options"
125
+ source "Kconfig.sensor"
126
+ endmenu
127
+ ```
128
+
129
+ Or in `CMakeLists.txt`, register the file as part of the component:
130
+
131
+ ```cmake
132
+ idf_component_register(
133
+ ...
134
+ KCONFIG
135
+ "Kconfig"
136
+ )
137
+ ```
138
+
139
+ For project-wide overlays, create `sdkconfig.espnow_flow` (or similar) and pass `--sdkconfig` to `idf.py`. Layers are applied in order: defaults, then `sdkconfig`, then any `--sdkconfig` files.
140
+
141
+ ## Help text
142
+
143
+ Always include a `help` block. menuconfig shows it on the option's help line; the project documentation pulls it from `Kconfig` when generating reference. Keep it under ~10 lines.
144
+
145
+ ## AMS7 conventions (project-specific)
146
+
147
+ - Prefix every project-defined option with `AMS7_`. Never use `CONFIG_FOO` for an AMS7-introduced symbol.
148
+ - Group options by feature in `menu "..."` blocks. AMS7 groups: Connectivity Workflow, Vitals, ESP-NOW, Logging, etc.
149
+ - Default to `n` for any feature that increases IRAM/flash — acquisition builds are size-sensitive.
150
+ - For "trial debug" toggles, name them `*_TRIAL_DEBUG_LOG_ENABLE` and gate `ESP_LOG*` calls behind them — never compile trial-debug logs into release by default.
151
+ - Add a `help` block on every option. AMS7 reviewers check for missing help text.
152
+
153
+ ## Common pitfalls
154
+
155
+ - **Forgetting `default`.** menuconfig prompts for a value on first use; this is annoying. Always set a sensible default.
156
+ - **`select` cycle.** `A select B; B select A` causes menuconfig to error. Use `depends on` instead.
157
+ - **Component Kconfig included in disabled component.** If a component's Kconfig has options, that component must be discoverable. Check `EXTRA_COMPONENT_DIRS` in `CMakeLists.txt`.
158
+ - **Renaming an option without a migration.** Renaming `AMS7_X` to `AMS7_Y` leaves `sdkconfig` referring to the old name. `menuconfig` will silently drop the new option. Fix: add a `config AMS7_Y` with `default AMS7_X` then remove `AMS7_X`.
159
+ - **`range` on a `bool`.** Doesn't make sense — Kconfig will refuse. Use a `choice` instead.
@@ -0,0 +1,118 @@
1
+ # Logging discipline
2
+
3
+ ESP-IDF logging uses `ESP_LOG*` macros. They are cheap when disabled (compile out), acceptable at moderate rates, and dangerous at acquisition-frame rates.
4
+
5
+ ## The macros
6
+
7
+ ```cpp
8
+ ESP_LOGE(TAG, "fmt", ...); // error — always shown at default verbosity
9
+ ESP_LOGW(TAG, "fmt", ...); // warning — recoverable issue
10
+ ESP_LOGI(TAG, "fmt", ...); // info — state transitions
11
+ ESP_LOGD(TAG, "fmt", ...); // debug — verbose, off by default
12
+ ESP_LOGV(TAG, "fmt", ...); // verbose — trace, off by default
13
+ ```
14
+
15
+ Each is gated by a per-level compile-time switch (`CONFIG_LOG_DEFAULT_LEVEL`) and a per-tag runtime level (`esp_log_level_set("tag", ESP_LOG_WARN)`).
16
+
17
+ ## Tags
18
+
19
+ Use a stable per-module tag — a file-scope `static const char *TAG`:
20
+
21
+ ```cpp
22
+ static const char *TAG = "ams7driver";
23
+ ESP_LOGI(TAG, "Start acquisition stack_hw=%lu", (unsigned long)stack_hw);
24
+ ```
25
+
26
+ Tags show up as `[tag]` prefixes in the serial monitor and let you filter:
27
+
28
+ ```bash
29
+ idf.py monitor --print-filter="ams7driver:I espnow_summary:W"
30
+ ```
31
+
32
+ Pick short, stable tags. AMS7 module tags: `ams7driver`, `espnow_summary`, `ble_prph`, `acqsystem`, `dps310`, `ads129x`, `icm42688p`, `sdcard`.
33
+
34
+ ## Format strings
35
+
36
+ Format strings are validated against arguments at compile time when GCC sees the format attribute:
37
+
38
+ ```cpp
39
+ ESP_LOGI(TAG, "Hold=%lu ms", (unsigned long)ms); // %lu for unsigned long
40
+ ESP_LOGI(TAG, "Cap=%" PRIu32, cap); // PRIu32 macro for uint32_t
41
+ ESP_LOGI(TAG, "Ratio=%f", ratio); // %f for double, %f for float (promoted)
42
+ ```
43
+
44
+ Common gotchas:
45
+
46
+ - `%lu` for `uint32_t` is **wrong on platforms where `long` is 64-bit**. Use `%" PRIu32 "` instead.
47
+ - `%d` for `size_t` is wrong on 64-bit hosts; use `%zu`.
48
+ - `%p` for arbitrary pointer; cast to `void *` first.
49
+
50
+ ## Gating with Kconfig
51
+
52
+ Wrap debug logs in a `CONFIG_*` guard so a release build can drop them entirely:
53
+
54
+ ```cpp
55
+ #if CONFIG_AMS7_RESP_TRIAL_DEBUG_LOG_ENABLE
56
+ ESP_LOGI(TAG, "resp sample processed src=%d q=%u", src, q);
57
+ #endif
58
+ ```
59
+
60
+ `(AMS7)` AMS7 names these options `*_TRIAL_DEBUG_LOG_ENABLE` and defaults them to `n` (or `y` only during early integration). They are the right toggle for "I want to see this while tuning the algorithm but not in production."
61
+
62
+ ## Universal anti-patterns
63
+
64
+ - **Per-frame logs in acquisition.** At 125–500 Hz this is unprintable and floods the UART. The host monitor loses the frames you actually need.
65
+ - **Logs in ISRs.** `ESP_LOG*` is not `IRAM_ATTR`; calling it from an ISR pulls the whole logging chain into IRAM. Use `xQueueSendFromISR` to a logging task instead.
66
+ - **Logging from `app_main`'s setup before serial is up.** First few lines can be lost. Wait for `ESP_LOGI(TAG, "boot")` to appear before assuming the console is alive.
67
+ - **Sensitive data in logs.** Avoid logging peer MACs, subject IDs, or anything that crosses a wire to a host that doesn't need it. Add a `LOG_REDACT` flag if the project warrants it.
68
+
69
+ ## AMS7 acquisition-logging rules
70
+
71
+ `(AMS7)` Acquisition builds must not log per-sample or per-frame. Acceptable log rates:
72
+
73
+ - **Per event**: BLE connect/disconnect, ESP-NOW peer add/remove, acquisition start/stop, file open/close, error conditions. These are inherently low-rate.
74
+ - **Periodic aggregate**: low-rate counters via a `pl:` (payload) console line every N ms. Example:
75
+
76
+ ```cpp
77
+ // Throttled to 1 Hz regardless of underlying sample rate
78
+ if ((now_ms - last_pl_ms) >= 1000) {
79
+ last_pl_ms = now_ms;
80
+ ESP_LOGI(TAG, "pl: beats=%lu rr_ms=%lu q=%u en=%u src=%d",
81
+ beats, rr_ms, quality, envelope, source);
82
+ }
83
+ ```
84
+
85
+ The `pl:` prefix is grep-friendly — host-side tooling can pluck aggregate lines from the serial stream without parsing every byte.
86
+
87
+ - **Trial-debug toggles**: `CONFIG_AMS7_RESP_TRIAL_DEBUG_LOG_ENABLE`, `CONFIG_AMS7_HR_FUSED_DEBUG_LOG_ENABLE`, etc. Default `n` for release builds. When enabled, log per-decision but still throttled.
88
+
89
+ ## Tagging aggregate lines
90
+
91
+ Use a tag prefix that distinguishes aggregate from per-event so the host can filter:
92
+
93
+ ```cpp
94
+ // Aggregate (high-volume, low-rate)
95
+ ESP_LOGI("ams7_pl", "pl: hr=%lu rr_ms=%lu q=%u", hr_bpm, rr_ms, q);
96
+
97
+ // Per-event (low-volume, descriptive)
98
+ ESP_LOGI(TAG, "Acquisition start mode=%d stack_hw=%lu", mode, stack_hw);
99
+ ```
100
+
101
+ ## Runtime level control
102
+
103
+ `esp_log_level_set` lets you bump a tag's verbosity at runtime:
104
+
105
+ ```cpp
106
+ esp_log_level_set("espnow_summary", ESP_LOG_DEBUG); // noisy on demand
107
+ esp_log_level_set("ams7driver", ESP_LOG_WARN); // quiet a noisy module
108
+ ```
109
+
110
+ This is the right tool for "I want to debug one module without rebuilding."
111
+
112
+ ## Common pitfalls
113
+
114
+ - **Logs in a tight loop.** Symptom: dropouts in other tasks, overflowing the UART ringbuffer. Fix: throttle or remove.
115
+ - **Float in `ESP_LOG*`.** ESP-IDF supports `%f` but pulling in `<stdio.h>` float formatting adds code; consider integer milliunits instead.
116
+ - **Missing format attribute.** ESP-IDF declares `__attribute__((format(printf, ...)))` on `ESP_LOG*` so format mismatches are warnings. Don't bypass with a `static_cast`.
117
+ - **Stale `TAG`.** Renaming a file but leaving the old `TAG`. Tag should match the module name in `idf.py monitor` filters.
118
+ - **Compile-time vs runtime gate confusion.** `CONFIG_LOG_DEFAULT_LEVEL` is compile-time; `esp_log_level_set` is runtime. A tag's level is `min(compile_max, runtime_set, default)`.