autonomous-coding-toolkit 1.0.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 (324) hide show
  1. package/.claude-plugin/marketplace.json +22 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/LICENSE +21 -0
  4. package/Makefile +21 -0
  5. package/README.md +140 -0
  6. package/SECURITY.md +28 -0
  7. package/agents/bash-expert.md +113 -0
  8. package/agents/dependency-auditor.md +138 -0
  9. package/agents/integration-tester.md +120 -0
  10. package/agents/lesson-scanner.md +149 -0
  11. package/agents/python-expert.md +179 -0
  12. package/agents/service-monitor.md +141 -0
  13. package/agents/shell-expert.md +147 -0
  14. package/benchmarks/runner.sh +147 -0
  15. package/benchmarks/tasks/01-rest-endpoint/rubric.sh +29 -0
  16. package/benchmarks/tasks/01-rest-endpoint/task.md +17 -0
  17. package/benchmarks/tasks/02-refactor-module/task.md +8 -0
  18. package/benchmarks/tasks/03-fix-integration-bug/task.md +8 -0
  19. package/benchmarks/tasks/04-add-test-coverage/task.md +8 -0
  20. package/benchmarks/tasks/05-multi-file-feature/task.md +8 -0
  21. package/bin/act.js +238 -0
  22. package/commands/autocode.md +6 -0
  23. package/commands/cancel-ralph.md +18 -0
  24. package/commands/code-factory.md +53 -0
  25. package/commands/create-prd.md +55 -0
  26. package/commands/ralph-loop.md +18 -0
  27. package/commands/run-plan.md +117 -0
  28. package/commands/submit-lesson.md +122 -0
  29. package/docs/ARCHITECTURE.md +630 -0
  30. package/docs/CONTRIBUTING.md +125 -0
  31. package/docs/lessons/0001-bare-exception-swallowing.md +34 -0
  32. package/docs/lessons/0002-async-def-without-await.md +28 -0
  33. package/docs/lessons/0003-create-task-without-callback.md +28 -0
  34. package/docs/lessons/0004-hardcoded-test-counts.md +28 -0
  35. package/docs/lessons/0005-sqlite-without-closing.md +33 -0
  36. package/docs/lessons/0006-venv-pip-path.md +27 -0
  37. package/docs/lessons/0007-runner-state-self-rejection.md +35 -0
  38. package/docs/lessons/0008-quality-gate-blind-spot.md +33 -0
  39. package/docs/lessons/0009-parser-overcount-empty-batches.md +36 -0
  40. package/docs/lessons/0010-local-outside-function-bash.md +33 -0
  41. package/docs/lessons/0011-batch-tests-for-unimplemented-code.md +36 -0
  42. package/docs/lessons/0012-api-markdown-unescaped-chars.md +33 -0
  43. package/docs/lessons/0013-export-prefix-env-parsing.md +33 -0
  44. package/docs/lessons/0014-decorator-registry-import-side-effect.md +43 -0
  45. package/docs/lessons/0015-frontend-backend-schema-drift.md +43 -0
  46. package/docs/lessons/0016-event-driven-cold-start-seeding.md +44 -0
  47. package/docs/lessons/0017-copy-paste-logic-diverges.md +43 -0
  48. package/docs/lessons/0018-layer-passes-pipeline-broken.md +45 -0
  49. package/docs/lessons/0019-systemd-envfile-ignores-export.md +41 -0
  50. package/docs/lessons/0020-persist-state-incrementally.md +44 -0
  51. package/docs/lessons/0021-dual-axis-testing.md +48 -0
  52. package/docs/lessons/0022-jsx-factory-shadowing.md +43 -0
  53. package/docs/lessons/0023-static-analysis-spiral.md +51 -0
  54. package/docs/lessons/0024-shared-pipeline-implementation.md +55 -0
  55. package/docs/lessons/0025-defense-in-depth-all-entry-points.md +65 -0
  56. package/docs/lessons/0026-linter-no-rules-false-enforcement.md +54 -0
  57. package/docs/lessons/0027-jsx-silent-prop-drop.md +64 -0
  58. package/docs/lessons/0028-no-infrastructure-in-client-code.md +49 -0
  59. package/docs/lessons/0029-never-write-secrets-to-files.md +61 -0
  60. package/docs/lessons/0030-cache-merge-not-replace.md +62 -0
  61. package/docs/lessons/0031-verify-units-at-boundaries.md +66 -0
  62. package/docs/lessons/0032-module-lifecycle-subscribe-unsubscribe.md +89 -0
  63. package/docs/lessons/0033-async-iteration-mutable-snapshot.md +72 -0
  64. package/docs/lessons/0034-caller-missing-await-silent-discard.md +65 -0
  65. package/docs/lessons/0035-duplicate-registration-silent-overwrite.md +85 -0
  66. package/docs/lessons/0036-websocket-dirty-disconnect.md +33 -0
  67. package/docs/lessons/0037-parallel-agents-worktree-corruption.md +31 -0
  68. package/docs/lessons/0038-subscribe-no-stored-ref.md +36 -0
  69. package/docs/lessons/0039-fallback-or-default-hides-bugs.md +34 -0
  70. package/docs/lessons/0040-event-firehose-filter-first.md +36 -0
  71. package/docs/lessons/0041-ambiguous-base-dir-path-nesting.md +32 -0
  72. package/docs/lessons/0042-spec-compliance-insufficient.md +36 -0
  73. package/docs/lessons/0043-exact-count-extensible-collections.md +32 -0
  74. package/docs/lessons/0044-relative-file-deps-worktree.md +39 -0
  75. package/docs/lessons/0045-iterative-design-improvement.md +33 -0
  76. package/docs/lessons/0046-plan-assertion-math-bugs.md +38 -0
  77. package/docs/lessons/0047-pytest-single-threaded-default.md +37 -0
  78. package/docs/lessons/0048-integration-wiring-batch.md +40 -0
  79. package/docs/lessons/0049-ab-verification.md +41 -0
  80. package/docs/lessons/0050-editing-sourced-files-during-execution.md +33 -0
  81. package/docs/lessons/0051-infrastructure-fixes-cant-self-heal.md +30 -0
  82. package/docs/lessons/0052-uncommitted-changes-poison-quality-gates.md +31 -0
  83. package/docs/lessons/0053-jq-compact-flag-inconsistency.md +31 -0
  84. package/docs/lessons/0054-parser-matches-inside-code-blocks.md +30 -0
  85. package/docs/lessons/0055-agents-compensate-for-garbled-prompts.md +31 -0
  86. package/docs/lessons/0056-grep-count-exit-code-on-zero.md +42 -0
  87. package/docs/lessons/0057-new-artifacts-break-git-clean-gates.md +42 -0
  88. package/docs/lessons/0058-dead-config-keys-never-consumed.md +49 -0
  89. package/docs/lessons/0059-contract-test-shared-structures.md +53 -0
  90. package/docs/lessons/0060-set-e-silent-death-in-runners.md +53 -0
  91. package/docs/lessons/0061-context-injection-dirty-state.md +50 -0
  92. package/docs/lessons/0062-sibling-bug-neighborhood-scan.md +29 -0
  93. package/docs/lessons/0063-one-flag-two-lifetimes.md +31 -0
  94. package/docs/lessons/0064-test-passes-wrong-reason.md +31 -0
  95. package/docs/lessons/0065-pipefail-grep-count-double-output.md +39 -0
  96. package/docs/lessons/0066-local-keyword-outside-function.md +37 -0
  97. package/docs/lessons/0067-stdin-hang-non-interactive-shell.md +36 -0
  98. package/docs/lessons/0068-agent-builds-wrong-thing-correctly.md +31 -0
  99. package/docs/lessons/0069-plan-quality-dominates-execution.md +30 -0
  100. package/docs/lessons/0070-spec-echo-back-prevents-drift.md +31 -0
  101. package/docs/lessons/0071-positive-instructions-outperform-negative.md +30 -0
  102. package/docs/lessons/0072-lost-in-the-middle-context-placement.md +30 -0
  103. package/docs/lessons/0073-unscoped-lessons-cause-false-positives.md +30 -0
  104. package/docs/lessons/0074-stale-context-injection-wrong-batch.md +32 -0
  105. package/docs/lessons/0075-research-artifacts-must-persist.md +32 -0
  106. package/docs/lessons/0076-wrong-decomposition-contaminates-downstream.md +30 -0
  107. package/docs/lessons/0077-cherry-pick-merges-need-manual-resolution.md +30 -0
  108. package/docs/lessons/0078-static-review-without-live-test.md +30 -0
  109. package/docs/lessons/0079-integration-wiring-batch-required.md +32 -0
  110. package/docs/lessons/FRAMEWORK.md +161 -0
  111. package/docs/lessons/SUMMARY.md +201 -0
  112. package/docs/lessons/TEMPLATE.md +85 -0
  113. package/docs/plans/2026-02-21-code-factory-v2-design.md +204 -0
  114. package/docs/plans/2026-02-21-code-factory-v2-implementation-plan.md +2189 -0
  115. package/docs/plans/2026-02-21-code-factory-v2-phase4-design.md +537 -0
  116. package/docs/plans/2026-02-21-code-factory-v2-phase4-implementation-plan.md +2012 -0
  117. package/docs/plans/2026-02-21-hardening-pass-design.md +108 -0
  118. package/docs/plans/2026-02-21-hardening-pass-plan.md +1378 -0
  119. package/docs/plans/2026-02-21-mab-research-report.md +406 -0
  120. package/docs/plans/2026-02-21-marketplace-restructure-design.md +240 -0
  121. package/docs/plans/2026-02-21-marketplace-restructure-plan.md +832 -0
  122. package/docs/plans/2026-02-21-phase4-completion-plan.md +697 -0
  123. package/docs/plans/2026-02-21-validator-suite-design.md +148 -0
  124. package/docs/plans/2026-02-21-validator-suite-plan.md +540 -0
  125. package/docs/plans/2026-02-22-mab-research-round2.md +556 -0
  126. package/docs/plans/2026-02-22-mab-run-design.md +462 -0
  127. package/docs/plans/2026-02-22-mab-run-plan.md +2046 -0
  128. package/docs/plans/2026-02-22-operations-design-methodology-research.md +681 -0
  129. package/docs/plans/2026-02-22-research-agent-failure-taxonomy.md +532 -0
  130. package/docs/plans/2026-02-22-research-code-guideline-policies.md +886 -0
  131. package/docs/plans/2026-02-22-research-codebase-audit-refactoring.md +908 -0
  132. package/docs/plans/2026-02-22-research-coding-standards-documentation.md +541 -0
  133. package/docs/plans/2026-02-22-research-competitive-landscape.md +687 -0
  134. package/docs/plans/2026-02-22-research-comprehensive-testing.md +1076 -0
  135. package/docs/plans/2026-02-22-research-context-utilization.md +459 -0
  136. package/docs/plans/2026-02-22-research-cost-quality-tradeoff.md +548 -0
  137. package/docs/plans/2026-02-22-research-lesson-transferability.md +508 -0
  138. package/docs/plans/2026-02-22-research-multi-agent-coordination.md +312 -0
  139. package/docs/plans/2026-02-22-research-phase-integration.md +602 -0
  140. package/docs/plans/2026-02-22-research-plan-quality.md +428 -0
  141. package/docs/plans/2026-02-22-research-prompt-engineering.md +558 -0
  142. package/docs/plans/2026-02-22-research-unconventional-perspectives.md +528 -0
  143. package/docs/plans/2026-02-22-research-user-adoption.md +638 -0
  144. package/docs/plans/2026-02-22-research-verification-effectiveness.md +433 -0
  145. package/docs/plans/2026-02-23-agent-suite-design.md +299 -0
  146. package/docs/plans/2026-02-23-agent-suite-plan.md +578 -0
  147. package/docs/plans/2026-02-23-phase3-cost-infrastructure-design.md +148 -0
  148. package/docs/plans/2026-02-23-phase3-cost-infrastructure-plan.md +1062 -0
  149. package/docs/plans/2026-02-23-research-bash-expert-agent.md +543 -0
  150. package/docs/plans/2026-02-23-research-dependency-auditor-agent.md +564 -0
  151. package/docs/plans/2026-02-23-research-improving-existing-agents.md +503 -0
  152. package/docs/plans/2026-02-23-research-integration-tester-agent.md +454 -0
  153. package/docs/plans/2026-02-23-research-python-expert-agent.md +429 -0
  154. package/docs/plans/2026-02-23-research-service-monitor-agent.md +425 -0
  155. package/docs/plans/2026-02-23-research-shell-expert-agent.md +533 -0
  156. package/docs/plans/2026-02-23-roadmap-to-completion.md +530 -0
  157. package/docs/plans/2026-02-24-headless-module-split-design.md +98 -0
  158. package/docs/plans/2026-02-24-headless-module-split.md +443 -0
  159. package/docs/plans/2026-02-24-lesson-scope-metadata-design.md +228 -0
  160. package/docs/plans/2026-02-24-lesson-scope-metadata-plan.md +968 -0
  161. package/docs/plans/2026-02-24-npm-packaging-design.md +841 -0
  162. package/docs/plans/2026-02-24-npm-packaging-plan.md +1965 -0
  163. package/docs/plans/audit-findings.md +186 -0
  164. package/docs/telegram-notification-format.md +98 -0
  165. package/examples/example-plan.md +51 -0
  166. package/examples/example-prd.json +72 -0
  167. package/examples/example-roadmap.md +33 -0
  168. package/examples/quickstart-plan.md +63 -0
  169. package/hooks/hooks.json +26 -0
  170. package/hooks/setup-symlinks.sh +48 -0
  171. package/hooks/stop-hook.sh +135 -0
  172. package/package.json +47 -0
  173. package/policies/bash.md +71 -0
  174. package/policies/python.md +71 -0
  175. package/policies/testing.md +61 -0
  176. package/policies/universal.md +60 -0
  177. package/scripts/analyze-report.sh +97 -0
  178. package/scripts/architecture-map.sh +145 -0
  179. package/scripts/auto-compound.sh +273 -0
  180. package/scripts/batch-audit.sh +42 -0
  181. package/scripts/batch-test.sh +101 -0
  182. package/scripts/entropy-audit.sh +221 -0
  183. package/scripts/failure-digest.sh +51 -0
  184. package/scripts/generate-ast-rules.sh +96 -0
  185. package/scripts/init.sh +112 -0
  186. package/scripts/lesson-check.sh +428 -0
  187. package/scripts/lib/common.sh +61 -0
  188. package/scripts/lib/cost-tracking.sh +153 -0
  189. package/scripts/lib/ollama.sh +60 -0
  190. package/scripts/lib/progress-writer.sh +128 -0
  191. package/scripts/lib/run-plan-context.sh +215 -0
  192. package/scripts/lib/run-plan-echo-back.sh +231 -0
  193. package/scripts/lib/run-plan-headless.sh +396 -0
  194. package/scripts/lib/run-plan-notify.sh +57 -0
  195. package/scripts/lib/run-plan-parser.sh +81 -0
  196. package/scripts/lib/run-plan-prompt.sh +215 -0
  197. package/scripts/lib/run-plan-quality-gate.sh +132 -0
  198. package/scripts/lib/run-plan-routing.sh +315 -0
  199. package/scripts/lib/run-plan-sampling.sh +170 -0
  200. package/scripts/lib/run-plan-scoring.sh +146 -0
  201. package/scripts/lib/run-plan-state.sh +142 -0
  202. package/scripts/lib/run-plan-team.sh +199 -0
  203. package/scripts/lib/telegram.sh +54 -0
  204. package/scripts/lib/thompson-sampling.sh +176 -0
  205. package/scripts/license-check.sh +74 -0
  206. package/scripts/mab-run.sh +575 -0
  207. package/scripts/module-size-check.sh +146 -0
  208. package/scripts/patterns/async-no-await.yml +5 -0
  209. package/scripts/patterns/bare-except.yml +6 -0
  210. package/scripts/patterns/empty-catch.yml +6 -0
  211. package/scripts/patterns/hardcoded-localhost.yml +9 -0
  212. package/scripts/patterns/retry-loop-no-backoff.yml +12 -0
  213. package/scripts/pipeline-status.sh +197 -0
  214. package/scripts/policy-check.sh +226 -0
  215. package/scripts/prior-art-search.sh +133 -0
  216. package/scripts/promote-mab-lessons.sh +126 -0
  217. package/scripts/prompts/agent-a-superpowers.md +29 -0
  218. package/scripts/prompts/agent-b-ralph.md +29 -0
  219. package/scripts/prompts/judge-agent.md +61 -0
  220. package/scripts/prompts/planner-agent.md +44 -0
  221. package/scripts/pull-community-lessons.sh +90 -0
  222. package/scripts/quality-gate.sh +266 -0
  223. package/scripts/research-gate.sh +90 -0
  224. package/scripts/run-plan.sh +329 -0
  225. package/scripts/scope-infer.sh +159 -0
  226. package/scripts/setup-ralph-loop.sh +155 -0
  227. package/scripts/telemetry.sh +230 -0
  228. package/scripts/tests/run-all-tests.sh +52 -0
  229. package/scripts/tests/test-act-cli.sh +46 -0
  230. package/scripts/tests/test-agents-md.sh +87 -0
  231. package/scripts/tests/test-analyze-report.sh +114 -0
  232. package/scripts/tests/test-architecture-map.sh +89 -0
  233. package/scripts/tests/test-auto-compound.sh +169 -0
  234. package/scripts/tests/test-batch-test.sh +65 -0
  235. package/scripts/tests/test-benchmark-runner.sh +25 -0
  236. package/scripts/tests/test-common.sh +168 -0
  237. package/scripts/tests/test-cost-tracking.sh +158 -0
  238. package/scripts/tests/test-echo-back.sh +180 -0
  239. package/scripts/tests/test-entropy-audit.sh +146 -0
  240. package/scripts/tests/test-failure-digest.sh +66 -0
  241. package/scripts/tests/test-generate-ast-rules.sh +145 -0
  242. package/scripts/tests/test-helpers.sh +82 -0
  243. package/scripts/tests/test-init.sh +47 -0
  244. package/scripts/tests/test-lesson-check.sh +278 -0
  245. package/scripts/tests/test-lesson-local.sh +55 -0
  246. package/scripts/tests/test-license-check.sh +109 -0
  247. package/scripts/tests/test-mab-run.sh +182 -0
  248. package/scripts/tests/test-ollama-lib.sh +49 -0
  249. package/scripts/tests/test-ollama.sh +60 -0
  250. package/scripts/tests/test-pipeline-status.sh +198 -0
  251. package/scripts/tests/test-policy-check.sh +124 -0
  252. package/scripts/tests/test-prior-art-search.sh +96 -0
  253. package/scripts/tests/test-progress-writer.sh +140 -0
  254. package/scripts/tests/test-promote-mab-lessons.sh +110 -0
  255. package/scripts/tests/test-pull-community-lessons.sh +149 -0
  256. package/scripts/tests/test-quality-gate.sh +241 -0
  257. package/scripts/tests/test-research-gate.sh +132 -0
  258. package/scripts/tests/test-run-plan-cli.sh +86 -0
  259. package/scripts/tests/test-run-plan-context.sh +305 -0
  260. package/scripts/tests/test-run-plan-e2e.sh +153 -0
  261. package/scripts/tests/test-run-plan-headless.sh +424 -0
  262. package/scripts/tests/test-run-plan-notify.sh +124 -0
  263. package/scripts/tests/test-run-plan-parser.sh +217 -0
  264. package/scripts/tests/test-run-plan-prompt.sh +254 -0
  265. package/scripts/tests/test-run-plan-quality-gate.sh +222 -0
  266. package/scripts/tests/test-run-plan-routing.sh +178 -0
  267. package/scripts/tests/test-run-plan-scoring.sh +148 -0
  268. package/scripts/tests/test-run-plan-state.sh +261 -0
  269. package/scripts/tests/test-run-plan-team.sh +157 -0
  270. package/scripts/tests/test-scope-infer.sh +150 -0
  271. package/scripts/tests/test-setup-ralph-loop.sh +63 -0
  272. package/scripts/tests/test-telegram-env.sh +38 -0
  273. package/scripts/tests/test-telegram.sh +121 -0
  274. package/scripts/tests/test-telemetry.sh +46 -0
  275. package/scripts/tests/test-thompson-sampling.sh +139 -0
  276. package/scripts/tests/test-validate-all.sh +60 -0
  277. package/scripts/tests/test-validate-commands.sh +89 -0
  278. package/scripts/tests/test-validate-hooks.sh +98 -0
  279. package/scripts/tests/test-validate-lessons.sh +150 -0
  280. package/scripts/tests/test-validate-plan-quality.sh +235 -0
  281. package/scripts/tests/test-validate-plans.sh +187 -0
  282. package/scripts/tests/test-validate-plugin.sh +106 -0
  283. package/scripts/tests/test-validate-prd.sh +184 -0
  284. package/scripts/tests/test-validate-skills.sh +134 -0
  285. package/scripts/validate-all.sh +57 -0
  286. package/scripts/validate-commands.sh +67 -0
  287. package/scripts/validate-hooks.sh +89 -0
  288. package/scripts/validate-lessons.sh +98 -0
  289. package/scripts/validate-plan-quality.sh +369 -0
  290. package/scripts/validate-plans.sh +120 -0
  291. package/scripts/validate-plugin.sh +86 -0
  292. package/scripts/validate-policies.sh +42 -0
  293. package/scripts/validate-prd.sh +118 -0
  294. package/scripts/validate-skills.sh +96 -0
  295. package/skills/autocode/SKILL.md +285 -0
  296. package/skills/autocode/ab-verification.md +51 -0
  297. package/skills/autocode/code-quality-standards.md +37 -0
  298. package/skills/autocode/competitive-mode.md +364 -0
  299. package/skills/brainstorming/SKILL.md +97 -0
  300. package/skills/capture-lesson/SKILL.md +187 -0
  301. package/skills/check-lessons/SKILL.md +116 -0
  302. package/skills/dispatching-parallel-agents/SKILL.md +110 -0
  303. package/skills/executing-plans/SKILL.md +85 -0
  304. package/skills/finishing-a-development-branch/SKILL.md +201 -0
  305. package/skills/receiving-code-review/SKILL.md +72 -0
  306. package/skills/requesting-code-review/SKILL.md +59 -0
  307. package/skills/requesting-code-review/code-reviewer.md +82 -0
  308. package/skills/research/SKILL.md +145 -0
  309. package/skills/roadmap/SKILL.md +115 -0
  310. package/skills/subagent-driven-development/SKILL.md +98 -0
  311. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +18 -0
  312. package/skills/subagent-driven-development/implementer-prompt.md +73 -0
  313. package/skills/subagent-driven-development/spec-reviewer-prompt.md +57 -0
  314. package/skills/systematic-debugging/SKILL.md +134 -0
  315. package/skills/systematic-debugging/condition-based-waiting.md +64 -0
  316. package/skills/systematic-debugging/defense-in-depth.md +32 -0
  317. package/skills/systematic-debugging/root-cause-tracing.md +55 -0
  318. package/skills/test-driven-development/SKILL.md +167 -0
  319. package/skills/using-git-worktrees/SKILL.md +219 -0
  320. package/skills/using-superpowers/SKILL.md +54 -0
  321. package/skills/verification-before-completion/SKILL.md +140 -0
  322. package/skills/verify/SKILL.md +82 -0
  323. package/skills/writing-plans/SKILL.md +128 -0
  324. package/skills/writing-skills/SKILL.md +93 -0
@@ -0,0 +1,61 @@
1
+ ---
2
+ id: 29
3
+ title: "Never write secret values into committed files"
4
+ severity: blocker
5
+ languages: [all]
6
+ scope: [universal]
7
+ category: silent-failures
8
+ pattern:
9
+ type: syntactic
10
+ regex: "(api_key|token|password|secret)\\s*=\\s*['\"][^'\"]{8,}"
11
+ description: "Actual secret values hardcoded in source files"
12
+ fix: "Reference secrets by env var name only; in tests use mock values; enforce with pre-commit hooks"
13
+ example:
14
+ bad: |
15
+ # config.py
16
+ API_KEY = 'sk-1234567890abcdef'
17
+ DATABASE_PASSWORD = 'prodPassword123'
18
+
19
+ # Committed to repo, exposed to anyone with access
20
+ good: |
21
+ # config.py
22
+ import os
23
+ API_KEY = os.environ.get('API_KEY', '')
24
+ DATABASE_PASSWORD = os.environ.get('DATABASE_PASSWORD', '')
25
+
26
+ # .env (never committed, sourced by deployment)
27
+ API_KEY=sk-1234567890abcdef
28
+ DATABASE_PASSWORD=prodPassword123
29
+ ---
30
+
31
+ ## Observation
32
+
33
+ Secrets hardcoded in source files are committed to version control, exposing them to anyone with repo access. Even after deletion, secrets remain in git history forever.
34
+
35
+ ## Insight
36
+
37
+ Source code is assumed to be shareable (it's version-controlled, reviewed, archived). Secrets are the opposite (must be kept private, rotated, compartmentalized). Mixing them violates the principle of least privilege and creates an irreversible exposure risk.
38
+
39
+ ## Lesson
40
+
41
+ **Never write secret values (passwords, API keys, tokens) into any file that gets committed:**
42
+
43
+ 1. **Configuration files**: Use environment variables with `os.environ.get()` (Python) or `process.env` (Node.js)
44
+ 2. **Tests**: Use mocks, fixtures, or test fixtures; never real credentials
45
+ 3. **.env files**: Create locally, gitignore them, source them at runtime
46
+ 4. **Pre-commit hooks**: Add linters (gitleaks, detect-secrets) to reject commits containing secrets
47
+
48
+ If a secret is committed:
49
+
50
+ 1. Rotate it immediately (invalidate the exposed credential)
51
+ 2. Scrub it from history (git filter-branch, BFG repo cleaner)
52
+ 3. Document the incident
53
+
54
+ Recommended workflow:
55
+
56
+ - `config.py` reads from `os.environ`
57
+ - `.env` (gitignored) contains local values
58
+ - CI/deployment sets env vars via secrets manager (Vault, AWS Secrets, etc.)
59
+ - Tests use fixtures/mocks, no real credentials
60
+
61
+ Verify by inspecting the last 50 commits: `git log --all -S 'sk-' | head -50` should find zero matches.
@@ -0,0 +1,62 @@
1
+ ---
2
+ id: 30
3
+ title: "Cache/registry updates must merge, never replace"
4
+ severity: should-fix
5
+ languages: [python, javascript, all]
6
+ scope: [universal]
7
+ category: integration-boundaries
8
+ pattern:
9
+ type: semantic
10
+ description: "Cache update replaces entire cache instead of merging, losing entries from other modules"
11
+ fix: "Use cache.update(new_entries) not cache = new_entries"
12
+ example:
13
+ bad: |
14
+ # Module A
15
+ cache = {'user:1': 'Alice'}
16
+
17
+ # Module B updates cache
18
+ def refresh_posts():
19
+ posts = fetch_posts()
20
+ cache = {'post:1': 'Hello'} # Replaces entire cache!
21
+ # user:1 lost
22
+
23
+ # Now cache only has posts, users are gone
24
+ good: |
25
+ # Shared cache object
26
+ cache = {}
27
+
28
+ # Module B updates cache (merge, don't replace)
29
+ def refresh_posts():
30
+ posts = fetch_posts()
31
+ cache.update({f'post:{p.id}': p for p in posts})
32
+ # All previous entries preserved
33
+
34
+ # Cache has both users and posts
35
+ ---
36
+
37
+ ## Observation
38
+
39
+ When multiple modules access a shared cache, replacing it from one module loses entries written by others. A refresh operation in Module B that replaces the cache erases data from Module A that's still valid.
40
+
41
+ ## Insight
42
+
43
+ Cache semantics depend on ownership. If the cache is owned by one module, that module can replace it. If it's shared, updates must be additive or selective. Replacement (assignment) assumes sole ownership; without it, you lose data from other modules.
44
+
45
+ ## Lesson
46
+
47
+ When updating a shared cache or registry:
48
+
49
+ 1. **Merge, don't replace**: Use `cache.update()` (Python dict), `Object.assign()` (JavaScript), or similar. Never reassign the cache variable.
50
+ 2. **Selective update**: If you need to replace specific keys, use `cache.pop(key)` then `cache[key] = value`, or `cache.update({key: value})`.
51
+ 3. **TTL/expiry**: For caches with stale data, use timestamps or TTLs instead of wholesale replacement. Stale entries expire; fresh entries remain.
52
+ 4. **Ownership**: Document which module owns the cache. If multiple modules write to it, document the contract: "All posts keys start with `post:`, all user keys start with `user:`. Modules only touch their namespace."
53
+
54
+ Example of selective update:
55
+
56
+ ```python
57
+ # Only replace posts, keep other entries
58
+ posts_dict = {f'post:{p.id}': p for p in new_posts}
59
+ cache.update(posts_dict) # user:1 and user:2 still there
60
+ ```
61
+
62
+ Test this by simulating concurrent updates and verifying data from both modules persists.
@@ -0,0 +1,66 @@
1
+ ---
2
+ id: 31
3
+ title: "Verify units at every boundary (0-1 vs 0-100)"
4
+ severity: should-fix
5
+ languages: [all]
6
+ scope: [universal]
7
+ category: integration-boundaries
8
+ pattern:
9
+ type: semantic
10
+ description: "Data crosses boundary with implicit unit change (proportion vs percentage)"
11
+ fix: "Verify units at every boundary; add unit to variable names (accuracy_pct, ratio_0_1)"
12
+ example:
13
+ bad: |
14
+ # Model outputs probability (0-1)
15
+ def predict(input):
16
+ return model.predict(input) # Returns 0.85 (85% confidence)
17
+
18
+ # UI assumes percentage (0-100)
19
+ confidence = predict(data)
20
+ ui.show_progress_bar(confidence) # Shows 0.85% instead of 85%!
21
+ good: |
22
+ # Clear units in names and documentation
23
+ def predict(input):
24
+ return model.predict(input) # Returns probability_ratio_0_1
25
+
26
+ # UI explicitly converts
27
+ probability_ratio_0_1 = predict(data)
28
+ confidence_pct = probability_ratio_0_1 * 100
29
+ ui.show_progress_bar(confidence_pct) # Shows 85%
30
+ ---
31
+
32
+ ## Observation
33
+
34
+ Data flows between systems with different unit conventions: probabilities (0-1), percentages (0-100), milliseconds vs seconds, ppm vs ppb. A boundary crossing without explicit conversion silently produces wrong results with no error.
35
+
36
+ ## Insight
37
+
38
+ Unit mismatches are silent failures because both sides are syntactically valid — a number is a number. The bug isn't a crash, it's a wrong result. A 0.85 probability rendered as 0.85% is off by two orders of magnitude but the code runs without error.
39
+
40
+ ## Lesson
41
+
42
+ At every data boundary (API, database, service-to-service), document and verify units:
43
+
44
+ 1. **Variable names include units**: `accuracy_pct`, `ratio_0_1`, `duration_ms`, `temp_celsius`
45
+ 2. **API contracts specify units**: "response returns confidence as float 0-1, not percentage"
46
+ 3. **Conversion explicit**: `pct = ratio_0_1 * 100` is clear; `pct = ratio_0_1` is not
47
+ 4. **Tests verify conversion**: Test that a 0.5 probability produces a 50% display value
48
+
49
+ Example contract in docs or code:
50
+
51
+ ```
52
+ GET /model/predict
53
+ Response: { "probability_ratio_0_1": 0.85 }
54
+ The probability is returned as a ratio (0-1), NOT a percentage.
55
+ ```
56
+
57
+ Add unit verification tests:
58
+
59
+ ```python
60
+ result = predict(data)
61
+ assert 0 <= result <= 1, f"Expected probability 0-1, got {result}"
62
+ ```
63
+
64
+ For databases, use migration notes: "analytics.confidence column changed from integer (0-100) to float (0-1) in v2.1."
65
+
66
+ Verify by running data through all boundaries and spot-checking units at each step.
@@ -0,0 +1,89 @@
1
+ ---
2
+ id: 32
3
+ title: "Module lifecycle: subscribe after init gate, unsubscribe on shutdown"
4
+ severity: should-fix
5
+ languages: [python, javascript]
6
+ scope: [universal]
7
+ category: resource-lifecycle
8
+ pattern:
9
+ type: semantic
10
+ description: "Component subscribes to events in constructor but never unsubscribes on shutdown"
11
+ fix: "Subscribe in initialize() after startup gate, store callback ref on self, unsubscribe in shutdown()"
12
+ example:
13
+ bad: |
14
+ class EventListener:
15
+ def __init__(self):
16
+ self.event_bus = get_event_bus()
17
+ self.event_bus.subscribe('user_login', self.on_login)
18
+ # subscribe in __init__, no unsubscribe
19
+ self.callback_ref = None # Lost reference
20
+
21
+ def on_login(self, event):
22
+ print(f"User {event.user} logged in")
23
+
24
+ # No shutdown method, so callback never unsubscribed
25
+ good: |
26
+ class EventListener:
27
+ def __init__(self):
28
+ self.event_bus = None
29
+ self.callback_ref = None
30
+
31
+ async def initialize(self):
32
+ self.event_bus = await get_event_bus()
33
+ self.callback_ref = self.on_login
34
+ self.event_bus.subscribe('user_login', self.callback_ref)
35
+
36
+ def on_login(self, event):
37
+ print(f"User {event.user} logged in")
38
+
39
+ async def shutdown(self):
40
+ if self.callback_ref:
41
+ self.event_bus.unsubscribe('user_login', self.callback_ref)
42
+ self.callback_ref = None
43
+ ---
44
+
45
+ ## Observation
46
+
47
+ Components subscribe to events in constructors but rarely unsubscribe. On shutdown, stale callbacks remain registered and continue firing, creating memory leaks and ghost events.
48
+
49
+ ## Insight
50
+
51
+ Constructors are for initialization; cleanup is for shutdown. Mixing them violates the resource lifecycle principle. A callback registered in `__init__` may outlive the component because nothing explicitly removes it.
52
+
53
+ ## Lesson
54
+
55
+ Follow this subscription lifecycle:
56
+
57
+ 1. **Separate init/shutdown**: Never subscribe in `__init__`. Use an explicit `initialize()` method.
58
+ 2. **Store callback reference**: Keep a reference to the callback on `self` so you can unsubscribe later.
59
+ 3. **Unsubscribe on shutdown**: In `shutdown()`, unsubscribe and set callback to None.
60
+
61
+ Pattern:
62
+
63
+ ```python
64
+ class Component:
65
+ def __init__(self):
66
+ self.event_bus = None
67
+ self.on_event_callback = None
68
+
69
+ async def initialize(self):
70
+ self.event_bus = await get_event_bus()
71
+ self.on_event_callback = self.on_event # Store ref
72
+ self.event_bus.subscribe('event_type', self.on_event_callback)
73
+
74
+ def on_event(self, event):
75
+ # Handle event
76
+
77
+ async def shutdown(self):
78
+ if self.on_event_callback:
79
+ self.event_bus.unsubscribe('event_type', self.on_event_callback)
80
+ self.on_event_callback = None
81
+ ```
82
+
83
+ Test this by:
84
+ 1. Create component
85
+ 2. Verify callback is registered (count subscribers)
86
+ 3. Shutdown component
87
+ 4. Fire event, verify callback doesn't fire (or count unchanged)
88
+
89
+ This pattern is critical for long-running services where components are created and destroyed repeatedly.
@@ -0,0 +1,72 @@
1
+ ---
2
+ id: 33
3
+ title: "Async iteration over mutable collections needs snapshot"
4
+ severity: blocker
5
+ languages: [python]
6
+ scope: [language:python]
7
+ category: async-traps
8
+ pattern:
9
+ type: syntactic
10
+ regex: "for .+ in self\\..+:"
11
+ description: "Iterating over instance attribute in async function without snapshot"
12
+ fix: "Snapshot before iterating: for item in list(my_set):"
13
+ example:
14
+ bad: |
15
+ class EventDispatcher:
16
+ def __init__(self):
17
+ self.subscribers = set()
18
+
19
+ async def dispatch(self, event):
20
+ # Iterating over mutable set in async context
21
+ for subscriber in self.subscribers: # Can raise "Set changed during iteration"
22
+ await subscriber.handle(event)
23
+
24
+ async def unsubscribe(self, subscriber):
25
+ self.subscribers.discard(subscriber)
26
+ good: |
27
+ class EventDispatcher:
28
+ def __init__(self):
29
+ self.subscribers = set()
30
+
31
+ async def dispatch(self, event):
32
+ # Snapshot before iterating
33
+ subscribers_copy = list(self.subscribers)
34
+ for subscriber in subscribers_copy:
35
+ await subscriber.handle(event)
36
+
37
+ async def unsubscribe(self, subscriber):
38
+ self.subscribers.discard(subscriber)
39
+ ---
40
+
41
+ ## Observation
42
+
43
+ In async contexts, iterating over a mutable collection (set, dict) that can be modified by concurrent code raises `RuntimeError: Set changed during iteration`. Synchronous iteration is safe because the event loop is blocked; async iteration is not.
44
+
45
+ ## Insight
46
+
47
+ Async/await allows other tasks to run between iterations. If another task modifies the collection you're iterating over, Python raises an error. The instinct is to ignore this risk in single-threaded async code, but multiple tasks can run in the same thread.
48
+
49
+ ## Lesson
50
+
51
+ When iterating over a collection in an async function:
52
+
53
+ 1. **Always snapshot first**: `for item in list(collection)` creates a snapshot immune to concurrent modification
54
+ 2. **Copy the right way**:
55
+ - Sets: `list(my_set)`
56
+ - Dicts: `dict(my_dict)` or `list(my_dict.items())`
57
+ - Lists: `my_list.copy()` or `list(my_list)`
58
+ 3. **Verify the pattern**: Grep for `for .+ in self\\.` in async functions and check for snapshots
59
+
60
+ Pattern:
61
+
62
+ ```python
63
+ async def broadcast(self):
64
+ # Snapshot before any await
65
+ handlers_copy = list(self.handlers)
66
+ for handler in handlers_copy:
67
+ await handler.process()
68
+ ```
69
+
70
+ Test by subscribing/unsubscribing in a concurrent task while dispatching, and verify no RuntimeError is raised.
71
+
72
+ This is Python-specific. JavaScript's for-of and async iteration have different semantics, but the same principle applies: if concurrent code modifies the collection, snapshot first.
@@ -0,0 +1,65 @@
1
+ ---
2
+ id: 34
3
+ title: "Caller-side missing await silently discards work"
4
+ severity: blocker
5
+ languages: [python, javascript]
6
+ scope: [universal]
7
+ category: async-traps
8
+ pattern:
9
+ type: semantic
10
+ description: "Async function called without await, coroutine created but never executed"
11
+ fix: "Always await async calls; use create_task() with done_callback for fire-and-forget"
12
+ example:
13
+ bad: |
14
+ async def save_to_database(data):
15
+ await db.save(data)
16
+ print("Saved!")
17
+
18
+ async def main():
19
+ save_to_database(data) # Missing await!
20
+ # Function never executed, "Saved!" never prints
21
+ print("Done") # Prints immediately, before save completes
22
+
23
+ # Result: data may never be saved
24
+ good: |
25
+ async def main():
26
+ # Option 1: await (blocking)
27
+ await save_to_database(data)
28
+
29
+ # Option 2: fire-and-forget with task
30
+ task = asyncio.create_task(save_to_database(data))
31
+ task.add_done_callback(handle_save_error)
32
+
33
+ print("Done")
34
+ ---
35
+
36
+ ## Observation
37
+
38
+ Calling an async function without `await` creates a coroutine object but doesn't execute it. The work is discarded, often silently. In Python, the event loop may warn "coroutine was never awaited"; in JavaScript, it's silent.
39
+
40
+ ## Insight
41
+
42
+ Async functions are lazy — they return a coroutine/promise that must be awaited to execute. Missing `await` is a type error (object of wrong type is created), but Python's runtime allows it. This is a language design quirk: async functions look like regular functions but require explicit awaiting.
43
+
44
+ ## Lesson
45
+
46
+ **Always await async calls:**
47
+
48
+ 1. **Default: await**: `await save_to_database(data)`
49
+ 2. **Fire-and-forget**: If you don't want to block, use `asyncio.create_task()` with error handling:
50
+
51
+ ```python
52
+ task = asyncio.create_task(save_to_database(data))
53
+ task.add_done_callback(lambda t: t.result() if t.exception() is None else None)
54
+ ```
55
+
56
+ 3. **Never just call**: `save_to_database(data)` is a bug
57
+
58
+ Linting:
59
+
60
+ - Python: Use `pylint` with `no-unused-variable` or linters that detect unawaited coroutines
61
+ - JavaScript: Use TypeScript or ESLint with `no-floating-promises` rule
62
+
63
+ Test: Verify that a fire-and-forget task completes before the program exits. Use a counter or log to verify the callback was called.
64
+
65
+ This is critical in production: losing async work silently is a data loss bug.
@@ -0,0 +1,85 @@
1
+ ---
2
+ id: 35
3
+ title: "Duplicate registration IDs cause silent overwrite"
4
+ severity: should-fix
5
+ languages: [python, javascript, all]
6
+ scope: [universal]
7
+ category: silent-failures
8
+ pattern:
9
+ type: semantic
10
+ description: "Multiple components register with the same ID, last one silently overwrites earlier ones"
11
+ fix: "Check for existing registration before inserting; log warning or raise on duplicate"
12
+ example:
13
+ bad: |
14
+ class HandlerRegistry:
15
+ def __init__(self):
16
+ self.handlers = {}
17
+
18
+ def register(self, handler_id, handler):
19
+ self.handlers[handler_id] = handler # Silently overwrites
20
+
21
+ registry = HandlerRegistry()
22
+ registry.register('payment', PaymentHandler())
23
+ registry.register('payment', StripeHandler()) # Oops, overwrites first one
24
+ # Now only Stripe handler is registered, PaymentHandler is lost
25
+
26
+ registry.handle('payment', data) # Only StripeHandler runs
27
+ good: |
28
+ class HandlerRegistry:
29
+ def __init__(self):
30
+ self.handlers = {}
31
+
32
+ def register(self, handler_id, handler):
33
+ if handler_id in self.handlers:
34
+ raise ValueError(f"Handler '{handler_id}' already registered")
35
+ self.handlers[handler_id] = handler
36
+
37
+ registry = HandlerRegistry()
38
+ registry.register('payment', PaymentHandler())
39
+ registry.register('payment', StripeHandler()) # Raises ValueError
40
+ # Bug caught immediately
41
+ ---
42
+
43
+ ## Observation
44
+
45
+ Registries that accept duplicate IDs silently overwrite earlier registrations. A second module registering with the same ID erases the first module's handler, and there's no error.
46
+
47
+ ## Insight
48
+
49
+ Registries are designed to prevent collisions: each ID maps to one handler. Without collision detection, the register operation becomes a silent update, and duplicate IDs are indistinguishable from intentional overwrites.
50
+
51
+ ## Lesson
52
+
53
+ When building a registration system (event handlers, plugins, middleware):
54
+
55
+ 1. **Check for duplicates**: Before inserting, verify the ID isn't already registered.
56
+ 2. **Three options on duplicate**:
57
+ - **Reject** (strict): Raise an exception. Fails fast, prevents bugs.
58
+ - **Warn** (permissive): Log a warning, then overwrite. Allows dynamic reconfiguration but can mask bugs.
59
+ - **Append** (list-based): If multiple handlers per ID are valid, use a list instead of a dict.
60
+
61
+ Pattern:
62
+
63
+ ```python
64
+ def register(self, handler_id, handler):
65
+ if handler_id in self.handlers:
66
+ raise ValueError(f"Duplicate registration: '{handler_id}'")
67
+ self.handlers[handler_id] = handler
68
+ ```
69
+
70
+ Or with warning:
71
+
72
+ ```python
73
+ def register(self, handler_id, handler):
74
+ if handler_id in self.handlers:
75
+ logger.warning(f"Overwriting handler '{handler_id}'")
76
+ self.handlers[handler_id] = handler
77
+ ```
78
+
79
+ Test by:
80
+ 1. Register handler A with ID 'foo'
81
+ 2. Register handler B with ID 'foo'
82
+ 3. Verify exception is raised OR warning is logged
83
+ 4. Verify the correct handler is in the registry afterward
84
+
85
+ Choose **reject** (strict) by default unless dynamic reconfiguration is explicitly required.
@@ -0,0 +1,33 @@
1
+ ---
2
+ id: 0036
3
+ title: "WebSocket dirty disconnects raise RuntimeError, not close"
4
+ severity: should-fix
5
+ languages: [python]
6
+ scope: [language:python]
7
+ category: resource-lifecycle
8
+ pattern:
9
+ type: semantic
10
+ description: "WebSocket send after client disconnects without close frame raises RuntimeError instead of WebSocketDisconnect"
11
+ fix: "Wrap all WebSocket sends in try/except RuntimeError and clean up the connection"
12
+ example:
13
+ bad: |
14
+ async def broadcast(self, message):
15
+ for ws in self.connections:
16
+ await ws.send_json({"msg": message})
17
+ good: |
18
+ async def broadcast(self, message):
19
+ for ws in self.connections:
20
+ try:
21
+ await ws.send_json({"msg": message})
22
+ except RuntimeError:
23
+ self.connections.remove(ws)
24
+ ---
25
+
26
+ ## Observation
27
+ WebSocket connections that are terminated by the client without a proper close frame (e.g., mobile network loss, browser tab close) raise `RuntimeError` on the next `send()` call, not `WebSocketDisconnect`. This exception type varies by websocket library implementation and client disconnection method.
28
+
29
+ ## Insight
30
+ Developers expect WebSocket sends to raise `WebSocketDisconnect` on all disconnection types, so they only catch that exception. Dirty disconnects bypass the close handshake, triggering RuntimeError instead. This causes unhandled exceptions in broadcast loops.
31
+
32
+ ## Lesson
33
+ Always wrap WebSocket sends in `try/except RuntimeError` in addition to connection-close handlers. Store connection state on `self`, remove failed connections immediately, and log the disconnection for visibility. Test with mobile network loss simulation, not just clean closes.
@@ -0,0 +1,31 @@
1
+ ---
2
+ id: 0037
3
+ title: "Parallel agents sharing worktree corrupt staging area"
4
+ severity: blocker
5
+ languages: [all]
6
+ scope: [universal]
7
+ category: integration-boundaries
8
+ pattern:
9
+ type: semantic
10
+ description: "Multiple agents or CI jobs commit to the same git worktree, corrupting the staging area"
11
+ fix: "Each parallel agent gets its own git worktree; never share a worktree between concurrent processes"
12
+ example:
13
+ bad: |
14
+ # Both agents write to same repo
15
+ Agent A: git add file1.py && git commit -m "feat: A"
16
+ Agent B: git add file2.py && git commit -m "feat: B" # corrupts A's staging
17
+ good: |
18
+ # Each agent has isolated worktree
19
+ git worktree add agent-a-branch
20
+ git worktree add agent-b-branch
21
+ # Agents work in separate directories
22
+ ---
23
+
24
+ ## Observation
25
+ When multiple agents or CI processes write to the same git repository directory concurrently, they interfere with each other's staging area, index locks, and commit state. This results in "fatal: cannot lock ref" errors, lost commits, or commits with wrong file combinations.
26
+
27
+ ## Insight
28
+ Git's index is a single file (`.git/index`) shared across all operations in a repository. The staging area is not thread-safe by design. Concurrent writes to this file corrupt it.
29
+
30
+ ## Lesson
31
+ Never share a git worktree between concurrent processes. Use `git worktree add` to create isolated working directories for each parallel agent. Each worktree has its own index and staging area. Verify worktrees are cleaned up and removed after use.
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: 0038
3
+ title: "Subscribe without stored ref = cannot unsubscribe"
4
+ severity: should-fix
5
+ languages: [python, javascript]
6
+ scope: [universal]
7
+ category: resource-lifecycle
8
+ pattern:
9
+ type: syntactic
10
+ regex: "\.subscribe\(lambda|\.subscribe\(\s*\("
11
+ description: "Event subscription with anonymous lambda cannot be unsubscribed later"
12
+ fix: "Store callback on self before subscribing; unsubscribe with stored ref in shutdown"
13
+ example:
14
+ bad: |
15
+ def __init__(self):
16
+ self.emitter.subscribe(lambda event: self.on_event(event))
17
+
18
+ def shutdown(self):
19
+ # No way to unsubscribe -- callback ref lost
20
+ good: |
21
+ def __init__(self):
22
+ self._callback = lambda event: self.on_event(event)
23
+ self.emitter.subscribe(self._callback)
24
+
25
+ def shutdown(self):
26
+ self.emitter.unsubscribe(self._callback)
27
+ ---
28
+
29
+ ## Observation
30
+ Event subscriptions created with inline lambdas or anonymous functions cannot be unsubscribed later because the callback reference is not stored. In shutdown or cleanup code, there's no way to reference the callback to remove it.
31
+
32
+ ## Insight
33
+ The subscriber pattern returns a reference to the callback if you need to unsubscribe later. When the callback is created inline and not stored, that reference is lost immediately after subscription.
34
+
35
+ ## Lesson
36
+ Always store event callbacks on `self` before subscribing. Unsubscribe using the stored reference in `shutdown()` or cleanup methods. Test that subscriptions are properly cleaned up and no callbacks fire after shutdown. This prevents memory leaks and stale event handlers.
@@ -0,0 +1,34 @@
1
+ ---
2
+ id: 0039
3
+ title: "Fallback `or default()` hides initialization bugs"
4
+ severity: should-fix
5
+ languages: [python]
6
+ scope: [language:python]
7
+ category: silent-failures
8
+ pattern:
9
+ type: semantic
10
+ description: "Expression like `self._resource or Resource()` creates new resource every access when _resource was never initialized"
11
+ fix: "Replace with guard return + warning: if not self._resource: logger.warning('not initialized'); return"
12
+ example:
13
+ bad: |
14
+ def get_value(self):
15
+ # If _resource never initialized, creates new one silently
16
+ return (self._resource or Resource()).value
17
+
18
+ # Bug: each call creates a new Resource if never initialized
19
+ good: |
20
+ def get_value(self):
21
+ if not self._resource:
22
+ logger.warning("Resource not initialized")
23
+ return None
24
+ return self._resource.value
25
+ ---
26
+
27
+ ## Observation
28
+ Using `or` as a fallback to create a default object (`self._resource or Resource()`) masks initialization bugs. The code never fails; it silently creates a new object on every access, leading to duplicate work, lost state, and difficult-to-trace behavior.
29
+
30
+ ## Insight
31
+ Fallback patterns hide the bug rather than fail fast. The developer doesn't know initialization was skipped because the code "works." State stored in the first Resource is lost on the next access, causing subtle state inconsistencies.
32
+
33
+ ## Lesson
34
+ Replace fallback patterns with explicit guard checks. Log a warning if the resource is not initialized, then return early or raise an exception. This makes initialization bugs fail fast and visible. Test initialization paths explicitly to ensure resources are initialized before first use.
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: 0040
3
+ title: "Process all events when 5% are relevant -- filter first"
4
+ severity: should-fix
5
+ languages: [all]
6
+ scope: [domain:ha-aria]
7
+ category: performance
8
+ pattern:
9
+ type: semantic
10
+ description: "Event handler processes every event when only a small fraction are relevant"
11
+ fix: "Filter by domain/type/source at the top of the handler before any expensive operations"
12
+ example:
13
+ bad: |
14
+ def on_event(self, event):
15
+ # Processes every event, even irrelevant ones
16
+ parsed = expensive_parse(event)
17
+ if parsed.domain != "target_domain":
18
+ return
19
+ self.handle_target(parsed)
20
+ good: |
21
+ def on_event(self, event):
22
+ if event.domain != "target_domain":
23
+ return
24
+ # Only expensive parse for relevant events
25
+ parsed = expensive_parse(event)
26
+ self.handle_target(parsed)
27
+ ---
28
+
29
+ ## Observation
30
+ Event handlers receive high-volume event streams (e.g., Home Assistant state_changed, MQTT topic subscriptions). Filtering happens after expensive operations (parsing, decoding, database lookups) instead of before, wasting CPU on irrelevant events.
31
+
32
+ ## Insight
33
+ Early filtering is a free optimization. Checking a simple field (`event.type`, `event.domain`) takes nanoseconds. Do this first, return immediately for irrelevant events, then proceed with expensive operations only for matching events.
34
+
35
+ ## Lesson
36
+ Filter events at the very top of the handler using simple field checks before any expensive operations. Structure the filter to reject irrelevant events as quickly as possible. If filtering becomes complex, move it to a decorator or middleware layer. Test with high event volume (1000s/sec) to verify performance doesn't degrade.