@polderlabs/bizar 6.2.4 → 6.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 (332) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +31 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/sandbox.mjs +220 -0
  128. package/cli/commands/validate.mjs +252 -288
  129. package/cli/dev-link.test.mjs +11 -11
  130. package/cli/doctor.mjs +131 -213
  131. package/cli/install.mjs +33 -37
  132. package/cli/install.test.mjs +9 -9
  133. package/cli/provision-claude.mjs +893 -0
  134. package/cli/provision.mjs +330 -226
  135. package/cli/provision.test.mjs +102 -0
  136. package/cli/utils.mjs +72 -58
  137. package/config/AGENTS.md +28 -28
  138. package/config/agents/_shared/AGENT_BASELINE.md +104 -684
  139. package/config/skills/bizar/SKILL.md +197 -0
  140. package/config/skills/cubesandbox/SKILL.md +148 -0
  141. package/config/skills/harness-engineering/SKILL.md +142 -0
  142. package/install.sh +66 -44
  143. package/package.json +19 -11
  144. package/packages/sdk/ARCHITECTURE.md +40 -36
  145. package/packages/sdk/package-lock.json +32 -0
  146. package/packages/sdk/package.json +15 -5
  147. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  148. package/packages/sdk/src/fingerprint.ts +0 -0
  149. package/packages/sdk/src/index.ts +38 -73
  150. package/packages/sdk/src/mcp/bin.ts +51 -0
  151. package/packages/sdk/src/mcp/server.ts +498 -0
  152. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  153. package/packages/sdk/tests/sdk.test.mjs +148 -0
  154. package/packages/sdk/vitest.config.ts +1 -1
  155. package/scripts/bh-full-e2e.mjs +166 -363
  156. package/scripts/check-deps.mjs +28 -46
  157. package/scripts/mirror-claude-md.sh +78 -0
  158. package/scripts/test-in-container.sh +157 -0
  159. package/templates/clean-state-checklist.md +3 -3
  160. package/templates/sprint-contract.md +2 -2
  161. package/bizar-dash/.bizar/activity.log +0 -3
  162. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  163. package/bizar-dash/.omx/state/session.json +0 -10
  164. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  165. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  166. package/bizar-dash/bizar-design/canvas.html +0 -281
  167. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  168. package/bizar-dash/bizar-design/components.css +0 -1665
  169. package/bizar-dash/bizar-design/components.html +0 -557
  170. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  172. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  174. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  176. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  177. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  178. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  179. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  180. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  181. package/bizar-dash/bizar-design/icons.svg +0 -44
  182. package/bizar-dash/bizar-design/index.html +0 -192
  183. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  185. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  189. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  190. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  191. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  192. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  193. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  194. package/bizar-dash/bizar-design/prototype.js +0 -114
  195. package/bizar-dash/bizar-design/tokens.css +0 -106
  196. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  197. package/bizar-dash/node_modules/.package-lock.json +0 -6
  198. package/bizar-dash/package-lock.json +0 -6
  199. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  200. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  201. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  202. package/cli/commands/cline-cmd.mjs +0 -289
  203. package/cli/commands/validate.test.mjs +0 -344
  204. package/cli/doctor.test.mjs +0 -350
  205. package/config/cline.json.template +0 -342
  206. package/packages/sdk/src/client.ts +0 -188
  207. package/packages/sdk/src/cline-events.ts +0 -134
  208. package/packages/sdk/src/cline-types.ts +0 -66
  209. package/packages/sdk/src/cline.ts +0 -339
  210. package/packages/sdk/src/errors.ts +0 -129
  211. package/packages/sdk/src/events.ts +0 -153
  212. package/packages/sdk/src/types.ts +0 -176
  213. package/packages/sdk/tests/client.test.ts +0 -217
  214. package/packages/sdk/tests/errors.test.ts +0 -108
  215. package/packages/sdk/tests/events.test.ts +0 -139
  216. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  217. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  218. package/plugins/bizar/ARCHITECTURE.md +0 -142
  219. package/plugins/bizar/CONSTRAINTS.md +0 -67
  220. package/plugins/bizar/LICENSE +0 -21
  221. package/plugins/bizar/README.md +0 -448
  222. package/plugins/bizar/index.ts +0 -872
  223. package/plugins/bizar/package.json +0 -39
  224. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  225. package/plugins/bizar/src/background-state.ts +0 -641
  226. package/plugins/bizar/src/background.ts +0 -1806
  227. package/plugins/bizar/src/cline-runner.ts +0 -203
  228. package/plugins/bizar/src/clineruntime.ts +0 -227
  229. package/plugins/bizar/src/commands-impl.ts +0 -151
  230. package/plugins/bizar/src/commands.ts +0 -1799
  231. package/plugins/bizar/src/compaction.d.mts +0 -48
  232. package/plugins/bizar/src/compaction.mjs +0 -192
  233. package/plugins/bizar/src/dashboard-client.ts +0 -233
  234. package/plugins/bizar/src/event-stream.ts +0 -606
  235. package/plugins/bizar/src/fingerprint.ts +0 -120
  236. package/plugins/bizar/src/handoff.ts +0 -79
  237. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  238. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  239. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  240. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  241. package/plugins/bizar/src/http-client.ts +0 -467
  242. package/plugins/bizar/src/key-rotation.ts +0 -218
  243. package/plugins/bizar/src/logger.ts +0 -144
  244. package/plugins/bizar/src/loop-engineering.ts +0 -241
  245. package/plugins/bizar/src/loop.ts +0 -176
  246. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  247. package/plugins/bizar/src/odin.ts +0 -227
  248. package/plugins/bizar/src/options.ts +0 -470
  249. package/plugins/bizar/src/plan-fs.ts +0 -323
  250. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  251. package/plugins/bizar/src/report.ts +0 -178
  252. package/plugins/bizar/src/research-prompt.ts +0 -35
  253. package/plugins/bizar/src/serve-info.ts +0 -228
  254. package/plugins/bizar/src/serve.ts +0 -496
  255. package/plugins/bizar/src/settings.ts +0 -349
  256. package/plugins/bizar/src/state.ts +0 -298
  257. package/plugins/bizar/src/tool-discipline.ts +0 -105
  258. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  259. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  260. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  261. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  262. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  263. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  264. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  265. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  266. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  267. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  268. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  269. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  270. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  271. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  272. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  273. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  274. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  275. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  276. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  277. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  278. package/plugins/bizar/src/tools/team-status.ts +0 -76
  279. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  280. package/plugins/bizar/src/trajectory.ts +0 -104
  281. package/plugins/bizar/tests/README.md +0 -99
  282. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  283. package/plugins/bizar/tests/background-state.test.ts +0 -277
  284. package/plugins/bizar/tests/background.test.ts +0 -402
  285. package/plugins/bizar/tests/block.test.ts +0 -195
  286. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  287. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  288. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  289. package/plugins/bizar/tests/commands.test.ts +0 -584
  290. package/plugins/bizar/tests/compaction.test.ts +0 -264
  291. package/plugins/bizar/tests/config.test.ts +0 -128
  292. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  293. package/plugins/bizar/tests/dispose.test.ts +0 -336
  294. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  295. package/plugins/bizar/tests/event.test.ts +0 -262
  296. package/plugins/bizar/tests/fingerprint.test.ts +0 -162
  297. package/plugins/bizar/tests/http-client.test.ts +0 -404
  298. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  299. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  300. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  301. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  302. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  303. package/plugins/bizar/tests/loop.test.ts +0 -397
  304. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  305. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  306. package/plugins/bizar/tests/odin.test.ts +0 -125
  307. package/plugins/bizar/tests/options.test.ts +0 -329
  308. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  309. package/plugins/bizar/tests/safety.test.ts +0 -256
  310. package/plugins/bizar/tests/serve.test.ts +0 -339
  311. package/plugins/bizar/tests/settings.test.ts +0 -351
  312. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  313. package/plugins/bizar/tests/state.test.ts +0 -276
  314. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  315. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  316. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  317. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  318. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  319. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  320. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  321. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  322. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  323. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  324. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  325. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  326. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  327. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  328. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  329. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  330. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  331. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  332. package/plugins/bizar/tsconfig.json +0 -29
@@ -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)`.
@@ -0,0 +1,137 @@
1
+ # Memory model and IRAM
2
+
3
+ ESP32 (Xtensa LX6/LX7, or RISC-V on S3/C3) has distinct address spaces. Code and data are placed into them by the linker based on attributes; getting this wrong is the difference between a working firmware and one that crashes on the first SPI-DMA transfer.
4
+
5
+ ## Address spaces at a glance
6
+
7
+ | Space | Size (classic ESP32) | What lives here |
8
+ |-----------|-----------------------------|--------------------------------------------------------------|
9
+ | IRAM | ~128 KB (some chips 256 KB) | ISR handlers, `IRAM_ATTR` functions, flash-cache-disabled |
10
+ | DRAM | ~120 KB | `.data`, `.bss`, heap, stacks, default-code-section RAM |
11
+ | Flash | 4–16 MB | Program text (XIP), read-only data, filesystems |
12
+ | PSRAM | 0–8 MB optional | Large buffers via `MALLOC_CAP_SPIRAM` |
13
+
14
+ ## IRAM (instruction RAM)
15
+
16
+ IRAM is the tightest budget on most ESP32 firmware because it has to be physically present on-chip and is not expandable. Code in IRAM can run without flash cache, which is required during:
17
+
18
+ - **ISR handlers** — the cache may be disabled.
19
+ - **SPI / DMA completion paths** — flash-cache-disabled periods when the SPI peripheral is busy.
20
+ - **Time-critical inner loops** — a few hot loops that would be slowed by XIP cache misses.
21
+
22
+ Mark a function IRAM-resident with the attribute:
23
+
24
+ ```cpp
25
+ #include "esp_attr.h"
26
+
27
+ void IRAM_ATTR spi_complete_isr(spi_transaction_t *trans) {
28
+ // runs from IRAM, must be small and avoid non-IRAM APIs
29
+ }
30
+ ```
31
+
32
+ `(AMS7)` Keep IRAM remaining ≥ 20 KiB on AMS7. Below 24 KiB is a warning. Do not add `IRAM_ATTR` unless the function is genuinely on a cache-disabled path. See `docs/firmware/memory_budget.md`.
33
+
34
+ ### What blows the IRAM budget
35
+
36
+ - **Every `IRAM_ATTR` function** — even a 200-byte routine pulls in 200 bytes of IRAM that flash would otherwise have held.
37
+ - **Logging inside ISRs** — `ESP_LOG*` is not `IRAM_ATTR` by default; if you call it from an ISR, the linker pulls the entire logging chain into IRAM. Use direct `uart_tx` or a deferred task instead.
38
+ - **Format strings in IRAM** — `printf`-style code in IRAM is expensive. Move formatted output to a task.
39
+ - **Per-frame work in IRAM** — even non-IRAM helpers reached only from IRAM functions can end up in IRAM if the linker decides so (`-ffunction-sections` + linker `--gc-sections` reduces but does not eliminate this).
40
+ - **Inlining** — `static inline` in a header used by an IRAM file ends up in IRAM for every consumer.
41
+
42
+ ### Diagnosing IRAM growth
43
+
44
+ ```bash
45
+ idf.py size --format json --output-file size.json
46
+ python3 -c "import json; s=json.load(open('size.json')); print(s['iram_size'], s['dram_size'])"
47
+ ```
48
+
49
+ For per-symbol breakdown:
50
+
51
+ ```bash
52
+ $IDF_PATH/tools/uf2/elf2image.py build/<project>.elf # if needed
53
+ xtensa-esp32-elf-objdump -d build/<project>.elf | grep '<.*>:' | head
54
+ ```
55
+
56
+ The AMS7-specific gate `tools/check_idf_size_budget.py` (wrapped by `scripts/size_check.sh`) reads the JSON and fails on hard thresholds.
57
+
58
+ ## DRAM (data RAM)
59
+
60
+ DRAM holds:
61
+
62
+ - `.data` — initialized globals (`int x = 5;`).
63
+ - `.bss` — zero-initialized globals (`int x;`).
64
+ - **Heap** — `malloc` / `new` returns here by default.
65
+ - **Stacks** — one stack per FreeRTOS task; `app_main`'s stack is fixed.
66
+
67
+ ### Prefer static
68
+
69
+ Heap fragmentation is severe on ESP32 — there is no `mmap` or `sbrk`, just a fixed free list. Long-running firmware that `malloc`s in a loop will eventually fail with `ESP_ERR_NO_MEM`.
70
+
71
+ Rules:
72
+
73
+ - `static const` arrays for lookup tables.
74
+ - `static` (file-scope) for buffers reused across calls.
75
+ - `std::array<T, N>` or `std::span` over `std::vector`.
76
+ - Pool allocators if you really need dynamic.
77
+
78
+ When you must malloc, prefer `heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL)` and free in matching scope. Always check the returned pointer.
79
+
80
+ ### Stacks
81
+
82
+ FreeRTOS stacks grow downward; overflow corrupts whatever is below them. Tuning:
83
+
84
+ - Stack depth is in **words** (4 bytes on Xtensa, 4 bytes on RISC-V too). `4096` = 16 KiB.
85
+ - Over-budget: enable `CONFIG_COMPILER_STACK_CHECK_MODE_NORM` (compiler-instrumented) or `CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` (hardware watchpoint, only one task at a time).
86
+ - Use `uxTaskGetStackHighWaterMark(handle)` to measure headroom at runtime.
87
+
88
+ `(AMS7)` AMS7 acquisition stacks are 8–16 KB; command-handler stack on the PRO CPU is 16 KB (`CONFIG_MAIN_TASK_STACK_SIZE=16384`).
89
+
90
+ ### Internal vs PSRAM (DMA-capable)
91
+
92
+ `MALLOC_CAP_INTERNAL` requests memory in DRAM. `MALLOC_CAP_SPIRAM` requests external PSRAM (slow, no DMA). Combine as needed:
93
+
94
+ ```cpp
95
+ // 8-bit accessible, internal (DMA-capable) — for SPI/I2S buffers
96
+ uint8_t *dma_buf = (uint8_t *)heap_caps_malloc(4096, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
97
+
98
+ // large, no DMA requirement — JSON caches, log buffers
99
+ char *big_buf = (char *)heap_caps_malloc(64 * 1024, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
100
+ ```
101
+
102
+ Always check the return value — `MALLOC_CAP_SPIRAM` may fail if PSRAM is not configured, or return NULL if the chip has none.
103
+
104
+ ## Flash
105
+
106
+ Most code lives in flash and is executed in place (XIP). The flash cache is on-chip and small (~32 KB on classic ESP32). Functions that are not in cache take a flash-read penalty.
107
+
108
+ To check cache hit rate at runtime:
109
+
110
+ ```cpp
111
+ #include "esp_heap_caps.h"
112
+ ESP_LOGI(TAG, "free 8-bit internal: %u", heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL));
113
+ ```
114
+
115
+ For static asserts on flash size, see `idf.py size` output. The relevant numbers are:
116
+
117
+ - **Flash app binary** — total image size.
118
+ - **`bytes free in the smallest app partition`** — partition headroom; matters for OTA.
119
+
120
+ ## `.iram0.lit`, `.iram0.text`, and `.dram0.*`
121
+
122
+ The default linker script places:
123
+
124
+ - `.iram0.lit` — literal pools (constants referenced from IRAM).
125
+ - `.iram0.text` — `IRAM_ATTR` functions.
126
+ - `.dram0.data` / `.dram0.bss` — initialized / zero-initialized data.
127
+ - `.flash.text` — all other code.
128
+
129
+ `idf.py size` aggregates these. Per-section breakdown requires `xtensa-esp32-elf-size -A build/<project>.elf`.
130
+
131
+ ## Common pitfalls
132
+
133
+ - **Calling a non-IRAM function from an ISR.** Symptom: intermittent crash in the ISR. Fix: mark the called function `IRAM_ATTR` or move the work to a task.
134
+ - **Using `printf` / `ESP_LOG*` from an ISR.** Symptom: large IRAM growth. Fix: use `xQueueSendFromISR` to defer the log to a task.
135
+ - **Malloc with `MALLOC_CAP_SPIRAM` on a chip without PSRAM.** Symptom: NULL return, hard fault on deref. Fix: fall back to internal with a smaller size, or fail loudly.
136
+ - **Stack overflow from deep recursion.** Symptom: hard fault or corruption deep in the call stack. Fix: convert to iteration, or bump the task's stack.
137
+ - **Static buffers in headers.** A `static constexpr` buffer in a header included by many `.cpp` files duplicates per translation unit, then collides at link. Use `inline constexpr` (C++17) or move to a single `.cpp`.
@@ -0,0 +1,121 @@
1
+ # NVS (Non-Volatile Storage) on ESP-IDF
2
+
3
+ NVS stores small key-value pairs in flash and survives reboot. ESP-IDF provides `nvs_flash.h` (low-level) and `nvs.h` (typed handle API). Most firmware code uses the typed handle API.
4
+
5
+ ## Initialization — once at boot
6
+
7
+ `nvs_flash_init()` must be called **exactly once at application startup**, not on every operation. Calling it in a getter/setter is an anti-pattern.
8
+
9
+ ```cpp
10
+ // GOOD — in app_main()
11
+ extern "C" void app_main() {
12
+ esp_err_t ret = nvs_flash_init();
13
+ if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
14
+ ESP_ERROR_CHECK(nvs_flash_erase());
15
+ ret = nvs_flash_init();
16
+ }
17
+ ESP_ERROR_CHECK(ret);
18
+ // ... start tasks, services, etc.
19
+ }
20
+
21
+ // BAD — calling nvs_flash_init() in every Get/Set is wasteful
22
+ esp_err_t Configuration::GetInt(const char* key, int32_t* out) {
23
+ nvs_flash_init(); // <-- wrong
24
+ nvs_handle_t h;
25
+ nvs_open("ns", NVS_READONLY, &h);
26
+ // ...
27
+ }
28
+ ```
29
+
30
+ For partition-encrypted or large NVS partitions, also handle `ESP_ERR_NVS_NO_FREE_PAGES` and `ESP_ERR_NVS_NEW_VERSION_FOUND` by erase + retry. See `examples/storage/nvs_value_iterator` in ESP-IDF.
31
+
32
+ ## Namespaces and handles
33
+
34
+ Open a handle per operation or per long-lived component:
35
+
36
+ ```cpp
37
+ // Per-operation (simple, slightly more overhead)
38
+ nvs_handle_t h;
39
+ ESP_ERROR_CHECK(nvs_open("ams7cfg", NVS_READWRITE, &h));
40
+ int32_t val = 42;
41
+ ESP_ERROR_CHECK(nvs_set_i32(h, "ble_adv", val));
42
+ ESP_ERROR_CHECK(nvs_commit(h));
43
+ nvs_close(h);
44
+
45
+ // Per-component (preferred for hot paths)
46
+ class ConfigStore {
47
+ public:
48
+ esp_err_t Init() {
49
+ esp_err_t err = nvs_open("ams7cfg", NVS_READWRITE, &handle_);
50
+ if (err != ESP_OK) return err;
51
+ // optional: pre-load known keys
52
+ return ESP_OK;
53
+ }
54
+ ~ConfigStore() { nvs_close(handle_); }
55
+ private:
56
+ nvs_handle_t handle_{};
57
+ };
58
+ ```
59
+
60
+ `nvs_commit()` is **required** for write durability — `nvs_set_*` only updates the in-memory cache.
61
+
62
+ ## Typed vs blob storage
63
+
64
+ | Type | Use for |
65
+ |---|---|
66
+ | `nvs_set_i8/u8/i16/u16/i32/u64` | Counters, flags, IDs, durations |
67
+ | `nvs_set_str` | Variable-length strings (max 4000 bytes per key, ~1984 bytes safe) |
68
+ | `nvs_set_blob` | Packed structs, calibration data, JSON payloads |
69
+ | `nvs_set_str` with fixed keys | Persistent default values (e.g., `feature_flag_default` = "on") |
70
+
71
+ Use **typed keys** for atomic per-field updates; use **blob** only when the data is always written and read as a unit (otherwise partial writes corrupt the format).
72
+
73
+ ## Error handling
74
+
75
+ `ESP_ERROR_CHECK_WITHOUT_ABORT(nvs_set_*(...))` silently swallows NVS failures. The caller has no way to know the persist operation succeeded. Two patterns:
76
+
77
+ ```cpp
78
+ // 1. Propagate the error to the caller
79
+ esp_err_t ConfigStore::SetInt(const char* key, int32_t val) {
80
+ esp_err_t err = nvs_set_i32(handle_, key, val);
81
+ if (err != ESP_OK) return err;
82
+ return nvs_commit(handle_);
83
+ }
84
+
85
+ // 2. Log and degrade gracefully
86
+ esp_err_t err = nvs_set_i32(handle_, key, val);
87
+ if (err != ESP_OK) {
88
+ ESP_LOGE(TAG, "nvs_set_i32(%s) failed: %s", key, esp_err_to_name(err));
89
+ return; // caller decides what to do
90
+ }
91
+ ```
92
+
93
+ Avoid bare `ESP_ERROR_CHECK_WITHOUT_ABORT` followed by an assumed-success return value.
94
+
95
+ ## Common pitfalls
96
+
97
+ - **Forgetting `nvs_commit()`** — `nvs_set_*` is buffered; without commit, values are lost on power cycle
98
+ - **Using the same handle from two tasks** — `nvs_handle_t` is not thread-safe; serialize access or use one handle per task
99
+ - **Hitting the 4-6 KB per-namespace limit** — large blobs need a dedicated namespace; check `nvs_get_stats()` first
100
+ - **Storing `std::string` directly** — NVS stores C strings, convert with `.c_str()` for write and `nvs_get_str` + sized buffer for read
101
+ - **Initializing on every getter** — see "Initialization" above
102
+ - **Hardcoded partition not present** — confirm `partitions.csv` includes an `nvs` entry; default is usually fine but custom layouts can break
103
+
104
+ ## Kconfig gating for NVS debug
105
+
106
+ Expose NVS internals under a debug option so production builds stay quiet:
107
+
108
+ ```kconfig
109
+ config AMS7_NVS_DEBUG_ENABLE
110
+ bool "Enable verbose NVS logging"
111
+ default n
112
+ help
113
+ When enabled, every NVS get/set logs a line. Disable in production
114
+ to avoid per-write console output.
115
+ ```
116
+
117
+ ## AMS7-specific patterns
118
+
119
+ `(AMS7)` The AMS7 firmware uses the namespace `"ams7cfg"` for runtime feature flags and study metadata. The NVS handle is owned by `Configuration` in `main/ams7conf.cpp`. The boot path in `main/ams7_esp32.cpp` calls `nvs_flash_init()` once before any `Configuration` access.
120
+
121
+ `(AMS7)` Persisted feature-flag defaults are stored as NVS strings (e.g., `ble_adv_snapshot_default = "on"`) — the runtime override is in RAM only. This lets `_SAVE` commands persist while plain `!FEATURE=true` commands stay ephemeral.
@@ -0,0 +1,192 @@
1
+ # Packed binary protocols
2
+
3
+ Wire-format frames over ESP-NOW, BLE characteristic writes, or any byte-oriented transport must have deterministic layout. ESP32 is little-endian natively, but `__attribute__((packed))` structs are still required to lock field offsets across compilers and configurations.
4
+
5
+ ## The canonical AMS7 frame shape
6
+
7
+ Every AMS7 wire frame lives in `main/connectivity/espnow_*_frame.hpp` and follows the same pattern. The IMU frame is the cleanest example:
8
+
9
+ ```cpp
10
+ // main/connectivity/espnow_imu_frame.hpp
11
+ #pragma once
12
+
13
+ #include <stdint.h>
14
+
15
+ #ifdef __cplusplus
16
+ extern "C" {
17
+ #endif
18
+
19
+ #define CORE_ESPNOW_IMU_MAGIC 0x494dU // ASCII 'I','M'
20
+ #define CORE_ESPNOW_IMU_VERSION 1U
21
+ #define CORE_ESPNOW_FRAME_IMU_RAW 1U
22
+
23
+ typedef struct __attribute__((packed)) {
24
+ uint16_t magic; // identifies the frame family on the wire
25
+ uint8_t version; // protocol version of this struct
26
+ uint8_t frame_type; // sub-type within the family
27
+ uint16_t id; // sender / subject id
28
+ uint16_t seq; // monotonic sequence
29
+ uint16_t age_ms; // capture age in ms (sender-stamped)
30
+ int16_t accel_x, accel_y, accel_z;
31
+ int16_t gyro_x, gyro_y, gyro_z;
32
+ } core_espnow_imu_frame_v1_t;
33
+
34
+ void espnow_imu_frame_init(core_espnow_imu_frame_v1_t *frame,
35
+ uint16_t id,
36
+ uint16_t seq,
37
+ uint16_t age_ms,
38
+ int16_t accel_x,
39
+ int16_t accel_y,
40
+ int16_t accel_z,
41
+ int16_t gyro_x,
42
+ int16_t gyro_y,
43
+ int16_t gyro_z);
44
+
45
+ #ifdef __cplusplus
46
+ }
47
+ static_assert(sizeof(core_espnow_imu_frame_v1_t) == 22,
48
+ "core_espnow_imu_frame_v1_t must be 22 bytes");
49
+ #else
50
+ _Static_assert(sizeof(core_espnow_imu_frame_v1_t) == 22,
51
+ "core_espnow_imu_frame_v1_t must be 22 bytes");
52
+ #endif
53
+ ```
54
+
55
+ This pattern is **load-bearing** for the project:
56
+
57
+ - `magic` is the first 16 bits so the receiver can reject foreign frames in O(1) before parsing.
58
+ - `version` distinguishes `_v1_t` from `_v2_t` so the receiver can dispatch to the right decoder.
59
+ - `frame_type` distinguishes sub-types within the same magic family (e.g., `IMU_RAW` vs a future `IMU_QUAT`).
60
+ - A trailing `static_assert` locks the on-wire size — adding a field is a build error, not a silent wire-format drift.
61
+
62
+ `(AMS7)` Naming convention is `core_<transport>_<family>_frame_v<N>_t`. Static asserts are mandatory on every wire struct.
63
+
64
+ ## The universal five rules
65
+
66
+ 1. **`__attribute__((packed))`** on every wire struct. Never rely on natural alignment.
67
+ 2. **`static_assert(sizeof(T) == N)`** for every wire struct. The assertion fires at compile time, not over the air.
68
+ 3. **Fixed-width types** only: `uint16_t`, `int16_t`, `uint32_t`, `int32_t`. Never `int`, `long`, `short`.
69
+ 4. **Magic byte first**, then version, then frame_type. Let the receiver reject foreign frames cheaply.
70
+ 5. **Length-prefixed framing** on the wire: 2 bytes little-endian length, then that many bytes of payload. No delimiter scanning, no escape bytes.
71
+
72
+ ## Byte order
73
+
74
+ ESP32 is little-endian natively; a `uint16_t` written via `memcpy` lands as low-byte-then-high-byte. When porting to a big-endian host (or a future big-endian SoC), use explicit byte-swap helpers instead of leaving `<<` chains:
75
+
76
+ ```cpp
77
+ // Bad: silent byte-order dependence
78
+ uint32_t be = (raw[0] << 24) | (raw[1] << 16) | (raw[2] << 8) | raw[3];
79
+
80
+ // Good: explicit and obvious
81
+ #include <endian.h>
82
+ uint32_t value = le32toh(*reinterpret_cast<const uint32_t *>(raw));
83
+ ```
84
+
85
+ Most ESP-IDF code uses the native LE order and stays put — the explicit byte swap is only needed when a host tool expects BE or when you genuinely don't know the SoC.
86
+
87
+ ## Versioning and capability bits
88
+
89
+ When you need to evolve a frame without breaking old receivers, **bump the version** and add a `_v2_t`. Old receivers see the new magic and ignore it; new receivers see the version byte and dispatch:
90
+
91
+ ```cpp
92
+ typedef struct __attribute__((packed)) {
93
+ uint16_t magic;
94
+ uint8_t version; // 2
95
+ uint8_t frame_type;
96
+ uint16_t id;
97
+ uint16_t seq;
98
+ uint32_t timestamp_us; // new in v2
99
+ /* ... existing fields ... */
100
+ } core_espnow_imu_frame_v2_t;
101
+
102
+ static_assert(sizeof(core_espnow_imu_frame_v2_t) == 30,
103
+ "core_espnow_imu_frame_v2_t must be 30 bytes");
104
+ ```
105
+
106
+ For optional fields, use a `capability_bits` bitmask after the header:
107
+
108
+ ```cpp
109
+ typedef struct __attribute__((packed)) {
110
+ uint16_t magic;
111
+ uint8_t version;
112
+ uint8_t frame_type;
113
+ uint16_t id;
114
+ uint16_t seq;
115
+ uint32_t cap_bits; // bit 0 = has_quaternion, bit 1 = has_temperature, ...
116
+ /* ... variable ... */
117
+ } core_espnow_capability_frame_v1_t;
118
+ ```
119
+
120
+ Receivers mask-and-test `cap_bits` to know which optional fields are present.
121
+
122
+ ## Wire framing (length-prefixed)
123
+
124
+ ESP-NOW itself has a 250-byte MTU per packet and 6-byte receiver MAC; AMS7 adds a 2-byte length prefix and a magic-byte envelope:
125
+
126
+ ```
127
+ +--------+--------+--------------+----------+
128
+ | length | magic | payload | (rounded |
129
+ | (u16) | (u16) | (frame_t) | to MTU) |
130
+ +--------+--------+--------------+----------+
131
+ ```
132
+
133
+ The receiver reads `length`, then reads exactly `length` bytes. If `length` exceeds the packet, the packet is dropped. If `magic` does not match a known family, drop.
134
+
135
+ For ESP-NOW specifically, AMS7 frames use a per-frame structure with sender-stamped `age_ms` so receivers can detect stale samples without keeping state.
136
+
137
+ ## Init functions
138
+
139
+ Always provide an `_init` function that fills the struct from named arguments. This is the only thing that touches the wire struct's bytes, so the layout can change without callers changing:
140
+
141
+ ```cpp
142
+ void espnow_imu_frame_init(core_espnow_imu_frame_v1_t *frame,
143
+ uint16_t id, uint16_t seq, uint16_t age_ms,
144
+ int16_t ax, int16_t ay, int16_t az,
145
+ int16_t gx, int16_t gy, int16_t gz) {
146
+ frame->magic = CORE_ESPNOW_IMU_MAGIC;
147
+ frame->version = CORE_ESPNOW_IMU_VERSION;
148
+ frame->frame_type = CORE_ESPNOW_FRAME_IMU_RAW;
149
+ frame->id = id;
150
+ frame->seq = seq;
151
+ frame->age_ms = age_ms;
152
+ frame->accel_x = ax; frame->accel_y = ay; frame->accel_z = az;
153
+ frame->gyro_x = gx; frame->gyro_y = gy; frame->gyro_z = gz;
154
+ }
155
+ ```
156
+
157
+ The init function is the seam — add a field, the init function gets a new argument, every caller updates. The wire size assertion catches a missed caller at compile time.
158
+
159
+ ## Sending and receiving
160
+
161
+ ```cpp
162
+ // Send: copy struct bytes into a flat buffer, prefix with length, hand to ESP-NOW.
163
+ core_espnow_imu_frame_v1_t f{};
164
+ espnow_imu_frame_init(&f, id, seq, age_ms, ax, ay, az, gx, gy, gz);
165
+ uint8_t wire[2 + sizeof(f)];
166
+ wire[0] = sizeof(f) & 0xff;
167
+ wire[1] = (sizeof(f) >> 8) & 0xff;
168
+ memcpy(wire + 2, &f, sizeof(f));
169
+ esp_now_send(peer, wire, sizeof(wire));
170
+ ```
171
+
172
+ ```cpp
173
+ // Receive: validate length, magic, version; then consume fields.
174
+ void on_recv(const uint8_t *data, size_t len) {
175
+ if (len < 2 + sizeof(core_espnow_imu_frame_v1_t)) return;
176
+ uint16_t want = (uint16_t)data[0] | ((uint16_t)data[1] << 8);
177
+ if (want != sizeof(core_espnow_imu_frame_v1_t)) return;
178
+ core_espnow_imu_frame_v1_t f;
179
+ memcpy(&f, data + 2, sizeof(f));
180
+ if (f.magic != CORE_ESPNOW_IMU_MAGIC || f.version != CORE_ESPNOW_IMU_VERSION) return;
181
+ handle_imu(&f);
182
+ }
183
+ ```
184
+
185
+ ## Common pitfalls
186
+
187
+ - **No `static_assert`.** A field added in one header silently breaks every receiver. Always lock `sizeof`.
188
+ - **`int` or `long` fields.** Their size varies across compilers and platforms; never use them on the wire.
189
+ - **Natural alignment assumed.** Without `__attribute__((packed))`, the compiler may insert padding that you cannot see at the source level.
190
+ - **Magic-byte collision.** Two frame families accidentally using the same 2-byte magic. Pick from a sparse range; document in `docs/transport_payload_reference.md`.
191
+ - **Sending the struct directly via ESP-NOW.** ESP-NOW needs a flat byte buffer and a length prefix; sending `&frame, sizeof(frame)` skips the length prefix and confuses the receiver's parser.
192
+ - **Endianness assumptions on the host.** If a host-side tool (Rust gateway, Python decoder) reads the bytes, it must apply the same byte order. Document in `docs/transport_payload_reference.md`.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # idf_env.sh — print the source line for the ESP-IDF environment and verify idf.py is reachable.
3
+ # Run as: bash scripts/idf_env.sh
4
+ # Exits 0 if `idf.py` is on PATH after a (dry-run) env probe; 1 otherwise.
5
+
6
+ set -euo pipefail
7
+
8
+ # Find the repo root from the skill directory. We resolve the script's real
9
+ # path so it works whether invoked from the skill dir or anywhere else.
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Heuristic: the project root is two levels up from scripts/ inside the skill.
13
+ # Skill layout: <repo>/scripts/<this>.sh → PROJECT_ROOT is SCRIPT_DIR's parent.
14
+ PROJECT_ROOT_DEFAULT="$(cd "${SCRIPT_DIR}/.." && pwd)"
15
+
16
+ PROJECT_ROOT="${PROJECT_ROOT:-${PROJECT_ROOT_DEFAULT}}"
17
+
18
+ # The vendored ESP-IDF is conventionally at <repo>/esp/esp-idf.
19
+ IDF_DIR_CANDIDATE="${PROJECT_ROOT}/esp/esp-idf"
20
+ EXPORT_LINE="source ${IDF_DIR_CANDIDATE}/export.sh"
21
+
22
+ if [[ ! -d "${IDF_DIR_CANDIDATE}" ]]; then
23
+ echo "Run: ${EXPORT_LINE} # (note: esp-idf not found at ${IDF_DIR_CANDIDATE})" >&2
24
+ echo "info: set PROJECT_ROOT to override the discovery path" >&2
25
+ echo "info: set IDF_PATH to a different ESP-IDF checkout" >&2
26
+ exit 1
27
+ fi
28
+
29
+ if [[ ! -x "${IDF_DIR_CANDIDATE}/tools/idf.py" ]] && [[ ! -f "${IDF_DIR_CANDIDATE}/tools/idf.py" ]]; then
30
+ echo "Run: ${EXPORT_LINE} # (note: idf.py not present at expected path)" >&2
31
+ exit 1
32
+ fi
33
+
34
+ # Probe: source the env in a subshell, then check whether idf.py is on PATH.
35
+ # We avoid printing the env's own banner by sending output to /dev/null.
36
+ if (
37
+ set +e
38
+ # shellcheck disable=SC1091
39
+ source "${IDF_DIR_CANDIDATE}/export.sh" >/dev/null 2>&1
40
+ command -v idf.py >/dev/null 2>&1
41
+ ) ; then
42
+ echo "Run: ${EXPORT_LINE}"
43
+ exit 0
44
+ else
45
+ echo "Run: ${EXPORT_LINE} # (env probe failed; check IDF_PATH and Python)" >&2
46
+ exit 1
47
+ fi