Copy disabled (too large)
Download .txt
Showing preview only (14,682K chars total). Download the full file to get everything.
Repository: rvdbreemen/OTGW-firmware
Branch: dev
Commit: f863aebea932
Files: 844
Total size: 13.8 MB
Directory structure:
gitextract_aoxwd8ux/
├── .claude/
│ ├── .vscode/
│ │ ├── arduino.json
│ │ ├── c_cpp_properties.json
│ │ ├── settings.json
│ │ └── tasks.json
│ ├── adr-kit-guide.md
│ ├── backlog-cli-reference.md
│ ├── commands/
│ │ ├── backlog_discord.md
│ │ └── check_otgw_issues.md
│ ├── docs/
│ │ └── discord-backlog-bridge.md
│ ├── settings.20260421_085354.bak
│ ├── settings.json
│ └── skills/
│ ├── adr/
│ │ └── SKILL.md
│ ├── flash/
│ │ └── SKILL.md
│ ├── release/
│ │ └── SKILL.md
│ └── update-docs/
│ └── SKILL.md
├── .codex
├── .copilot-tracking/
│ └── research/
│ ├── 20260306-mqtt-json-refactor-research.md
│ └── 20260306-ui-fixes-otmonitor-panel-spacing-research.md
├── .external-reviews/
│ ├── HANDOFF-claude-review-c-codebase-303Qj.md
│ └── README.md
├── .full-review/
│ ├── 00-scope.md
│ ├── 01-quality-architecture.md
│ ├── 02-security-performance.md
│ ├── 03-testing-documentation.md
│ ├── 05-final-report.md
│ ├── phase1a-code-quality.md
│ ├── phase1b-architecture.md
│ ├── phase2a-security.md
│ ├── phase2b-performance.md
│ ├── phase3a-testing.md
│ ├── phase3b-documentation.md
│ └── state.json
├── .full-review-archive-20260421-085044/
│ ├── 00-scope.md
│ ├── 01-quality-architecture.md
│ ├── 02-security-performance.md
│ └── state.json
├── .gitattributes
├── .githooks/
│ └── pre-commit
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md.example
│ ├── actions/
│ │ ├── build/
│ │ │ └── action.yml
│ │ └── setup/
│ │ └── action.yml
│ ├── agents/
│ │ ├── adr-generator.agent.md
│ │ ├── api-architect.agent.md
│ │ ├── context7.agent.md
│ │ ├── critical-thinking.agent.md
│ │ ├── debug.agent.md
│ │ ├── devils-advocate.agent.md
│ │ ├── expert-cpp-software-engineer.agent.md
│ │ ├── expert-react-frontend-engineer.agent.md
│ │ ├── gpt-5-beast-mode.agent.md
│ │ ├── implementation-plan.agent.md
│ │ ├── specification.agent.md
│ │ ├── task-planner.agent.md
│ │ └── task-researcher.agent.md
│ ├── copilot-instructions.md
│ ├── instructions/
│ │ ├── adr.code-review.instructions.md
│ │ └── adr.coding-agent.instructions.md
│ ├── prompts/
│ │ └── check-discord-issues.prompt.md
│ ├── skills/
│ │ ├── adr/
│ │ │ ├── ALWAYS_USE_SKILL.md
│ │ │ ├── IMPLEMENTATION_SUMMARY.md
│ │ │ ├── QUICK_START.md
│ │ │ ├── README.md
│ │ │ ├── SKILL.md
│ │ │ └── USAGE_GUIDE.md
│ │ ├── algorithmic-art/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── templates/
│ │ │ ├── generator_template.js
│ │ │ └── viewer.html
│ │ ├── brand-guidelines/
│ │ │ ├── LICENSE.txt
│ │ │ └── SKILL.md
│ │ ├── canvas-design/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── canvas-fonts/
│ │ │ ├── ArsenalSC-OFL.txt
│ │ │ ├── BigShoulders-OFL.txt
│ │ │ ├── Boldonse-OFL.txt
│ │ │ ├── BricolageGrotesque-OFL.txt
│ │ │ ├── CrimsonPro-OFL.txt
│ │ │ ├── DMMono-OFL.txt
│ │ │ ├── EricaOne-OFL.txt
│ │ │ ├── GeistMono-OFL.txt
│ │ │ ├── Gloock-OFL.txt
│ │ │ ├── IBMPlexMono-OFL.txt
│ │ │ ├── InstrumentSans-OFL.txt
│ │ │ ├── Italiana-OFL.txt
│ │ │ ├── JetBrainsMono-OFL.txt
│ │ │ ├── Jura-OFL.txt
│ │ │ ├── LibreBaskerville-OFL.txt
│ │ │ ├── Lora-OFL.txt
│ │ │ ├── NationalPark-OFL.txt
│ │ │ ├── NothingYouCouldDo-OFL.txt
│ │ │ ├── Outfit-OFL.txt
│ │ │ ├── PixelifySans-OFL.txt
│ │ │ ├── PoiretOne-OFL.txt
│ │ │ ├── RedHatMono-OFL.txt
│ │ │ ├── Silkscreen-OFL.txt
│ │ │ ├── SmoochSans-OFL.txt
│ │ │ ├── Tektur-OFL.txt
│ │ │ ├── WorkSans-OFL.txt
│ │ │ └── YoungSerif-OFL.txt
│ │ ├── doc-coauthoring/
│ │ │ └── SKILL.md
│ │ ├── docx/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ ├── __init__.py
│ │ │ ├── accept_changes.py
│ │ │ ├── comment.py
│ │ │ ├── office/
│ │ │ │ ├── helpers/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── merge_runs.py
│ │ │ │ │ └── simplify_redlines.py
│ │ │ │ ├── pack.py
│ │ │ │ ├── schemas/
│ │ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── pml.xsd
│ │ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ │ ├── sml.xsd
│ │ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── wml.xsd
│ │ │ │ │ │ └── xml.xsd
│ │ │ │ │ ├── ecma/
│ │ │ │ │ │ └── fouth-edition/
│ │ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ │ ├── mce/
│ │ │ │ │ │ └── mc.xsd
│ │ │ │ │ └── microsoft/
│ │ │ │ │ ├── wml-2010.xsd
│ │ │ │ │ ├── wml-2012.xsd
│ │ │ │ │ ├── wml-2018.xsd
│ │ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ │ └── wml-symex-2015.xsd
│ │ │ │ ├── soffice.py
│ │ │ │ ├── unpack.py
│ │ │ │ ├── validate.py
│ │ │ │ └── validators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── docx.py
│ │ │ │ ├── pptx.py
│ │ │ │ └── redlining.py
│ │ │ └── templates/
│ │ │ ├── comments.xml
│ │ │ ├── commentsExtended.xml
│ │ │ ├── commentsExtensible.xml
│ │ │ ├── commentsIds.xml
│ │ │ └── people.xml
│ │ ├── frontend-design/
│ │ │ ├── LICENSE.txt
│ │ │ └── SKILL.md
│ │ ├── internal-comms/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── examples/
│ │ │ ├── 3p-updates.md
│ │ │ ├── company-newsletter.md
│ │ │ ├── faq-answers.md
│ │ │ └── general-comms.md
│ │ ├── mcp-builder/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── reference/
│ │ │ │ ├── evaluation.md
│ │ │ │ ├── mcp_best_practices.md
│ │ │ │ ├── node_mcp_server.md
│ │ │ │ └── python_mcp_server.md
│ │ │ └── scripts/
│ │ │ ├── connections.py
│ │ │ ├── evaluation.py
│ │ │ ├── example_evaluation.xml
│ │ │ └── requirements.txt
│ │ ├── pdf/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── forms.md
│ │ │ ├── reference.md
│ │ │ └── scripts/
│ │ │ ├── check_bounding_boxes.py
│ │ │ ├── check_fillable_fields.py
│ │ │ ├── convert_pdf_to_images.py
│ │ │ ├── create_validation_image.py
│ │ │ ├── extract_form_field_info.py
│ │ │ ├── extract_form_structure.py
│ │ │ ├── fill_fillable_fields.py
│ │ │ └── fill_pdf_form_with_annotations.py
│ │ ├── pptx/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── editing.md
│ │ │ ├── pptxgenjs.md
│ │ │ └── scripts/
│ │ │ ├── __init__.py
│ │ │ ├── add_slide.py
│ │ │ ├── clean.py
│ │ │ ├── office/
│ │ │ │ ├── helpers/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── merge_runs.py
│ │ │ │ │ └── simplify_redlines.py
│ │ │ │ ├── pack.py
│ │ │ │ ├── schemas/
│ │ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── pml.xsd
│ │ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ │ ├── sml.xsd
│ │ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── wml.xsd
│ │ │ │ │ │ └── xml.xsd
│ │ │ │ │ ├── ecma/
│ │ │ │ │ │ └── fouth-edition/
│ │ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ │ ├── mce/
│ │ │ │ │ │ └── mc.xsd
│ │ │ │ │ └── microsoft/
│ │ │ │ │ ├── wml-2010.xsd
│ │ │ │ │ ├── wml-2012.xsd
│ │ │ │ │ ├── wml-2018.xsd
│ │ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ │ └── wml-symex-2015.xsd
│ │ │ │ ├── soffice.py
│ │ │ │ ├── unpack.py
│ │ │ │ ├── validate.py
│ │ │ │ └── validators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── docx.py
│ │ │ │ ├── pptx.py
│ │ │ │ └── redlining.py
│ │ │ └── thumbnail.py
│ │ ├── refactor/
│ │ │ └── SKILL.md
│ │ ├── skill-creator/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── references/
│ │ │ │ ├── output-patterns.md
│ │ │ │ └── workflows.md
│ │ │ └── scripts/
│ │ │ ├── init_skill.py
│ │ │ ├── package_skill.py
│ │ │ └── quick_validate.py
│ │ ├── template-skill/
│ │ │ └── SKILL.md
│ │ ├── theme-factory/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── themes/
│ │ │ ├── arctic-frost.md
│ │ │ ├── botanical-garden.md
│ │ │ ├── desert-rose.md
│ │ │ ├── forest-canopy.md
│ │ │ ├── golden-hour.md
│ │ │ ├── midnight-galaxy.md
│ │ │ ├── modern-minimalist.md
│ │ │ ├── ocean-depths.md
│ │ │ ├── sunset-boulevard.md
│ │ │ └── tech-innovation.md
│ │ ├── web-artifacts-builder/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ ├── bundle-artifact.sh
│ │ │ └── init-artifact.sh
│ │ ├── webapp-testing/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── examples/
│ │ │ │ ├── console_logging.py
│ │ │ │ ├── element_discovery.py
│ │ │ │ └── static_html_automation.py
│ │ │ └── scripts/
│ │ │ └── with_server.py
│ │ └── xlsx/
│ │ ├── LICENSE.txt
│ │ ├── SKILL.md
│ │ └── scripts/
│ │ ├── office/
│ │ │ ├── helpers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── merge_runs.py
│ │ │ │ └── simplify_redlines.py
│ │ │ ├── pack.py
│ │ │ ├── schemas/
│ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ ├── pml.xsd
│ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ ├── sml.xsd
│ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ ├── wml.xsd
│ │ │ │ │ └── xml.xsd
│ │ │ │ ├── ecma/
│ │ │ │ │ └── fouth-edition/
│ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ ├── mce/
│ │ │ │ │ └── mc.xsd
│ │ │ │ └── microsoft/
│ │ │ │ ├── wml-2010.xsd
│ │ │ │ ├── wml-2012.xsd
│ │ │ │ ├── wml-2018.xsd
│ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ └── wml-symex-2015.xsd
│ │ │ ├── soffice.py
│ │ │ ├── unpack.py
│ │ │ ├── validate.py
│ │ │ └── validators/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── docx.py
│ │ │ ├── pptx.py
│ │ │ └── redlining.py
│ │ └── recalc.py
│ └── workflows/
│ ├── claude-code-review.yml
│ ├── claude.yml
│ ├── evaluate.yml
│ ├── opentherm-v42-spec-audit.yml
│ ├── release-assets.yml
│ └── trigger-copilot-agent.yml
├── .gitignore
├── .gitmodules
├── AGENTS.md
├── AUTHORS
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── backlog/
│ ├── archive/
│ │ └── tasks/
│ │ ├── task-1 - Audit-and-fix-cMsg-shared-global-buffer-reentrancy-hazard.md
│ │ ├── task-10 - Harden-REST-API-input-validation-postSettings-field-whitelist-Dallas-labels.md
│ │ ├── task-11 - Add-WebSocket-idle-timeout-and-Content-Length-headers-for-file-serving.md
│ │ ├── task-12 - Fix-typos-and-minor-code-quality-issues-across-codebase.md
│ │ ├── task-13 - Upgrade-WiFiManager-from-RC-to-stable-release.md
│ │ ├── task-14 - Check-and-upgrade-WebSockets-DallasTemperature-and-OneWire-libraries.md
│ │ ├── task-15 - Refactor-OTA-updater-flow.md
│ │ ├── task-15.1 - Refactor-OTA-page-JavaScript.md
│ │ ├── task-15.2 - Refactor-OTA-backend-handler.md
│ │ ├── task-15.3 - Clarify-flash-mode-runtime-handling.md
│ │ ├── task-16 - Fix-MQTT-status-first-publish-and-reconnect-republish.md
│ │ ├── task-17 - Align-MQTT-publish-implementation-to-ADR-052.md
│ │ ├── task-18 - Reclaim-FSexplorer-static-HTML-streaming-buffers.md
│ │ ├── task-19 - Replace-global-sMessage-scratch-buffer-with-smaller-status-representation.md
│ │ ├── task-2 - Move-networkStuff.h-function-bodies-to-networkStuff.ino.md
│ │ ├── task-20 - Reduce-persistent-RAM-used-by-webhook-payload-expansion.md
│ │ ├── task-21 - Compact-OT-MQTT-publish-tracking-tables.md
│ │ ├── task-22 - Release-MQTT-autodiscovery-workspace-after-publish-sessions.md
│ │ ├── task-22.1 - Implement-in-place-MQTT-autodiscovery-line-parsing.md
│ │ ├── task-22.2 - Implement-streaming-MQTT-autodiscovery-template-rendering.md
│ │ ├── task-22.3 - Integrate-and-validate-reduced-workspace-MQTT-autodiscovery.md
│ │ ├── task-23 - Stream-PROGMEM-MQTT-payloads-and-remove-1200-byte-payload-scratch-buffer.md
│ │ ├── task-24 - Convert-all-MQTT-publishes-to-chunked-beginPublish-flow-and-shrink-client-buffer.md
│ │ ├── task-241 - Investigate-Tado-X-DHW-control-not-reflecting-correctly-via-OTGW.md
│ │ ├── task-242 - Fix-upgrade-to-v6.6-fails-reported-by-Tomba-on-Tweakers.md
│ │ ├── task-243 - Fix-NTP-time-sync-still-stuck-at-2106-02-07-after-v1.3.7-beta-fix.md
│ │ ├── task-243 - Investigate-Tado-X-DHW-control-not-reflecting-correctly-via-OTGW.md
│ │ ├── task-25 - Stream-mqttha.cfg-parsing-and-remove-persistent-MQTT-autodiscovery-workspace.md
│ │ ├── task-254 - Upgrade-ESP8266-Arduino-core-from-2.7.4-to-3.1.2.md
│ │ ├── task-255 - Update-all-pinned-Arduino-libraries-to-latest-compatible-versions.md
│ │ ├── task-256 - Fix-ESP8266-3.x-breaking-changes-in-firmware-code.md
│ │ ├── task-257 - SimpleTelnet-library-scaffold-and-build-integration.md
│ │ ├── task-258 - SimpleTelnet-core-connection-management-engine.md
│ │ ├── task-259 - SimpleTelnet-Stream-interface-—-broadcast-write-and-polling-read.md
│ │ ├── task-26 - Replace-dense-MQTT-publish-tracking-table-with-bounded-sparse-tracking.md
│ │ ├── task-260 - SimpleTelnet-CLI-input-mode-with-per-client-line-buffering.md
│ │ ├── task-261 - SimpleTelnet-printf-en-printf_P-PROGMEM-helpers.md
│ │ ├── task-262 - SimpleTelnet-firmware-migratie-—-OTGWstream-en-debugTelnet.md
│ │ ├── task-263 - SimpleTelnet-integratie-validatie-en-heap-meting.md
│ │ ├── task-264 - SimpleTelnet-worked-examples-—-StreamingMode-CLIMode-DualInstance.md
│ │ ├── task-265 - SimpleTelnet-API-documentatie-en-doxygen-header-comments.md
│ │ ├── task-266 - SimpleTelnet-publicatiebestanden-Arduino-Library-Manager-en-PlatformIO.md
│ │ ├── task-267 - SimpleTelnet-README.md-—-menselijk-Engelstalig-met-shoutouts-en-onderbouwing.md
│ │ ├── task-268 - Fix-ESP8266-v1.4.0-beta-reboot-loop-after-flash.md
│ │ ├── task-269 - Fix-PIC-firmware-v6.6-upgrade-via-web-UI-fails.md
│ │ ├── task-270 - Fix-MQTT-discovery-burst-on-every-reconnect.md
│ │ ├── task-271 - Build-generate-mqttha_progmem.h-from-mqttha.cfg.md
│ │ ├── task-272 - Refactor-MQTT-discovery-to-use-PROGMEM-index-instead-of-LittleFS.md
│ │ ├── task-273 - Config-switch-to-lwIP2-Low-Memory-variant-MSS536.md
│ │ ├── task-274 - Feature-scheduled-nightly-restart-for-heap-recovery.md
│ │ ├── task-276 - Optimize-eliminate-sLine1200-global-buffer-—-pass-PROGMEM-msg-pointers-directly.md
│ │ ├── task-277 - Optimize-eliminate-topicBuf200-stack-buffer-—-pass-PROGMEM-topic-pointers-directly.md
│ │ ├── task-278 - Fix-v1.4.0-beta-ESP8266-crash-reboot-loop-Exception-2-3.md
│ │ ├── task-279 - Fix-autodiscovery-PROGMEM-as-RAM-crashes-Exception-3.md
│ │ ├── task-280 - Fix-NULL-pointer-crash-in-getOTGWValue-updateSetting-at-boot-Exception-28.md
│ │ ├── task-281 - Refactor-mqttha_progmem-naar-leesbare-OTlookup_t-stijl.md
│ │ ├── task-282 - Refactor-MQTT-HA-discovery-compact-array-streaming-constructors.md
│ │ ├── task-3 - const-correctness-pass-on-MQTT-and-helper-functions.md
│ │ ├── task-338 - Slow-MQTT-discovery-drip-interval-from-1s-to-2s.md
│ │ ├── task-339 - Widen-MQTT-discovery-heap-pressure-backoff-trigger-to-HEAP_LOW.md
│ │ ├── task-340 - Use-getMaxFreeBlockSize-in-MQTT-WebSocket-publish-gates-for-fragmentation-awareness.md
│ │ ├── task-341 - JSON-ify-Status-frame-MQTT-fanout-single-publish-instead-of-9-sub-topics.md
│ │ ├── task-342 - Quiesce-MQTT-discovery-drip-during-Status-frame-burst.md
│ │ ├── task-343 - Delta-publishing-for-MQTT-Status-sub-topics-parked.md
│ │ ├── task-344 - Lower-heap-guard-thresholds-tuned-on-Crashevans-log-data.md
│ │ ├── task-345 - Refactor-nightly-restart-to-use-hourChanged-hook.md
│ │ ├── task-346 - Cumulative-heap-health-drop-statistics-with-hourly-MQTT-publish.md
│ │ ├── task-347 - Post-Status-burst-cooldown-window-for-MQTT-discovery-drip.md
│ │ ├── task-348 - Fix-discovery-drip-limbo-on-publish-failure.md
│ │ ├── task-349 - On-demand-MQTT-discovery-verification-and-republish.md
│ │ ├── task-350 - Unify-time-boundary-dispatcher-single-caller-contract.md
│ │ ├── task-351 - Daily-automatic-discovery-verification.md
│ │ ├── task-355 - choreadr-revert-ADR-062-064-to-Proposed-and-resolve-ghost-ADR-citations.md
│ │ ├── task-356 - fixmqtt-add-cooldown-precondition-on-api-v2-discovery-republish-to-prevent-verify-lockout.md
│ │ ├── task-357 - fixmqtt-guard-verify-window-callback-fall-through-to-command-dispatcher.md
│ │ ├── task-358 - fixmqtt-dedupe-heap-threshold-6000-between-REST-verify-and-startDiscoveryVerification.md
│ │ ├── task-359 - fixmqtt-enforce-NTP-and-uptime-preconditions-in-startDiscoveryVerification.md
│ │ ├── task-360 - docsmqtt-fix-stale-comments-on-heapdiag-call-site-drip-interval-and-ADR-077-reference.md
│ │ ├── task-361 - featmqtt-distinguish-verify-heap-abort-from-clean-pass-via-outcome-enum.md
│ │ ├── task-362 - chorecleanup-remove-dead-code-paths-and-write-only-state-fields-from-1.4.1-refactor.md
│ │ ├── task-363 - refactormqtt-extract-discovery-verification-state-machine-into-separate-TU.md
│ │ ├── task-364 - choreadr-implement-CI-gates-promised-by-ADR-062-for-discovery-counter-instrumentation.md
│ │ ├── task-365 - docsrelease-create-RELEASE_NOTES_1.4.1.md-update-BREAKING_CHANGES.md-README-What-is-new-section.md
│ │ ├── task-366 - docsapi-update-openapi.yaml-and-MQTT.md-for-new-discovery-verify-endpoints-and-heap-diag-topic.md
│ │ ├── task-367 - chorebacklog-append-erratum-on-TASK-342-346-349-351-Final-Summaries-and-remove-plan-file-references.md
│ │ ├── task-368 - choreci-wire-evaluate.py-into-GitHub-Actions-and-add-4-regex-gates-buffer-cooldown-ADR-VH-wrap.md
│ │ ├── task-369 - choretests-rewrite-tests-test_dallas_address.cpp-as-host-compilable-or-delete.md
│ │ ├── task-370 - fixheap-add-hysteresis-to-drip-interval-mode-transitions-to-stop-oscillation.md
│ │ ├── task-371 - fixotgw-quiesce-PIC-PR-readout-during-Status-burst-and-active-drip-tick.md
│ │ ├── task-372 - Fix-WiFi-does-not-reconnect-after-access-point-reboot.md
│ │ ├── task-386 - Fix-settings-page-double-tap-blanks-all-fields-1.4.2-beta.md
│ │ ├── task-4 - Break-OTGW-Core.ino-into-named-logical-regions-with-section-headers.md
│ │ ├── task-432 - Fix-1.5.0-beta-first-reboot-WiFi-association-without-DHCP-IP-andrebrait-reproducible.md
│ │ ├── task-5 - Add-bounds-validation-to-all-numeric-settings-in-updateSetting.md
│ │ ├── task-6 - Fix-MQTT-subscription-topic-truncation-and-byte-by-byte-streaming-write.md
│ │ ├── task-7 - Eliminate-String-class-from-FSexplorer-HTTP-handlers.md
│ │ ├── task-8 - Fix-undersized-buffers-overflowCountBuf-MQTT-payload-webhook-expansion.md
│ │ └── task-9 - Reduce-MQTT-callback-stack-pressure-and-protect-publishToSourceTopic-from-re-entrancy.md
│ ├── config.yml
│ └── tasks/
│ ├── task-240 - Fix-upgrade-to-v6.6-fails-reported-by-Tomba-on-Tweakers.md
│ ├── task-242 - Fix-OTGW-flapping-offline-online-with-serial-overrun-and-MQTT-throttle-drops.md
│ ├── task-275 - Validate-heap-stability-after-stap-1-fixes-—-decide-on-core-downgrade.md
│ ├── task-283 - Fix-v1.4.0-beta-boot-loop-triggered-by-MQTT-broker-connection.md
│ ├── task-352 - fixheapdiag-expand-sendMQTTheapdiag-JSON-buffer-to-prevent-truncation-at-max-counters.md
│ ├── task-353 - fixmqtt-lower-STATUS_BURST_COOLDOWN_MS-to-2000ms-to-stop-discovery-drip-stall.md
│ ├── task-354 - fixotgw-wrap-VH-status-publishers-in-beginStatusBurst-endStatusBurst-quiesce.md
│ ├── task-382 - Fix-MQTT-HA-discovery-drip-never-sends-device-name-or-sw_version-isFirstEntity-always-false.md
│ ├── task-383 - Docs-add-Arduino-Core-3.1.2-upgrade-warning-LittleFS-partition-change-causes-~10-min-boot-settings-loss.md
│ ├── task-384 - Fix-v1.3.5-bootloop-on-fresh-flash-to-Wemos-D1.md
│ ├── task-385 - Fix-text-fields-render-dark-in-light-mode-1.4.2-beta.md
│ ├── task-387 - Fix-theme-toggle-icon-overlaps-hostnameIP-text-in-mobile-header.md
│ ├── task-388 - Fix-MQTT-binary_sensor-discovery-via-flag-driven-otgw-pic-prefix.md
│ ├── task-389 - Create-ADR-065-otgw-pic-MQTT-subtree-is-stable-public-topic-API.md
│ ├── task-390 - Add-sendMQTTDataPic-helper-and-migrate-direct-publish-call-sites-to-use-it.md
│ ├── task-391 - Fix-1.4.2-webui-boot-lag-render-hotpath-lower-restore-cap-to-10k.md
│ ├── task-392 - Fix-findings-from-v1.4.1..dev-handoff-review.md
│ ├── task-395 - Port-TASK-394-Phase-12-reboot-diagnostics-fixes-from-2.0.0-to-dev.md
│ ├── task-396 - TASK-394-Phase-34-port-dev-hardening-deferred-reboot-OTA-heap-probes-watermark-flash-sanity-exccause.md
│ ├── task-397 - Diagnose-random-doBackgroundTasks-loop-stalls-—-BGTRACE-always-on-instrumentation.md
│ ├── task-398 - Create-LTS-1.4.x-on-2.7.4-branch-fork-dev-pin-to-Arduino-Core-2.7.4.md
│ ├── task-399 - Bump-SimpleTelnet-printf-stack-buffer-from-64-to-256-bytes-tunable-SIMPLETELNET_PRINTF_STACK_LEN.md
│ ├── task-400 - Per-bit-change-detection-for-OT-msgId-0-Status-MQTT-fan-out-60s-heartbeat.md
│ ├── task-401 - Per-bit-change-detection-60s-heartbeat-for-MQTT-fan-out-on-OT-msgId-5-ASF-6-RBP-and-100-Remote-Override.md
│ ├── task-402 - Rate-gate-MQTT-gated-fanout-publishes-at-1s-spacing-with-per-slot-pending-flags.md
│ ├── task-403 - Tune-MQTT-gated-fanout-spacing-from-1000ms-to-250ms-disable-BGTRACE-OTTRACE-instrumentation.md
│ ├── task-431 - Investigate-rapid-WebUI-page-refresh-freezes-the-OTGW-1.4.2-beta-requires-network-drop-to-recover.md
│ ├── task-478 - fixmqtt-stop-master-topic-flapping-for-non-echoed-OT-values-B-hybrid.md
│ ├── task-483 - fixwebui-apply-ADR-066-master-topic-filter-to-log-decode-and-REST-state.md
│ ├── task-484 - Fix-WiFi-setup-AP-mode-webUI-unreachable-after-Reset-Wifi-andrebrait-1.5.0-beta.md
│ ├── task-485 - Fix-AP-not-found-on-Netgear-Orbi-after-upgrading-to-1.4.1-aagorine.md
│ ├── task-486 - Fix-PIC-not-detected-on-Wemos-D1-Mini-Pro-GitHub-557-dwd1.md
│ ├── task-522 - HA-discovery-suppress-base-entity-when-bSeparateSources-is-enabled-no-overlap-design.md
│ ├── task-526 - Make-legacy-port-25238-otmonitor-TCP-opt-in-via-UI-toggle.md
│ ├── task-527 - feat-2.0.0-port-legacy-port-25238-opt-in-toggle-from-TASK-526-with-ESP32-OTDirect-considerations.md
│ ├── task-531 - Restore-backward-compatible-bare-topic-for-gateway-source-HA-entities-dev.md
│ ├── task-534 - Fix-DHW-setpoint-shows-HA-initial-value-43°C-and-DHW-temperature-unknown-in-HA-via-MQTT.md
│ ├── task-535 - Docs-fix-duplicate-HA-entities-after-firmware-upgrade-—-stale-retained-MQTT-discovery-topics.md
│ ├── task-536 - Add-dump-debug-info-command-to-debug-menu.md
│ ├── task-537 - Port-TASK-536-debug-dump-to-ESP32-2.0.0-branch.md
│ ├── task-538 - Drop-gateway-MQTT-sub-topic-canonical-entity-replaces-_gateway-HA-discovery.md
│ ├── task-538 - Fix-GWR-stuck-in-command-queue-causes-infinite-PIC-reset-loop.md
│ ├── task-539 - feat-2.0.0-port-TASK-538-—-drop-gateway-MQTT-sub-topic-canonical-entity-replaces-_gateway-HA-discovery.md
│ ├── task-540 - Add-HA-discovery-for-diagnostic-MQTT-topics-otgw-firmware-stats-reboot_count-etc..md
│ ├── task-541 - feat-2.0.0-port-TASK-540-—-add-HA-discovery-for-diagnostic-MQTT-topics.md
│ ├── task-542 - Fix-SimpleTelnet-OpenTherm-OTGWSerial-submodules-unregistered-in-.gitmodules-feature-dev-2.0.0.md
│ ├── task-543 - feat-2.0.0-HA-discovery-for-SAT-OTDirect-user-facing-topics.md
│ ├── task-545 - Compact-telnet-welcome-banner-with-diagnostic-snapshot-all-toggles-1.5.x.md
│ ├── task-546 - feat-2.0.0-port-TASK-534-DHW-climate-discovery-initial-fallback-removal.md
│ ├── task-547 - Fix-Services-unreachable-after-WiFi-reconnect.md
│ ├── task-548 - Feature-Static-IP-address-settings.md
│ ├── task-549 - Override-side-TSet-TrSet-routing-split-thermostat-vs-boiler-MQTT-publication-during-gateway-override.md
│ ├── task-551 - ADR-070-MQTT-source-topic-sibling-suffix-shape-supersedes-ADR-068-refines-ADR-069.md
│ ├── task-552 - Implement-ADR-070-switch-to-sibling-suffix-MQTT-source-topics-drop-base-suppression.md
│ ├── task-553 - fixmqtt-add-threshold-hysteresis-deadband-K-ticks-to-drip-mode-transitions-to-stop-~60-90s-thrash.md
│ ├── task-556 - featmqtt-flip-discovery-topic-shape-to-sibling-suffix-implements-ADR-071-supersedes-ADR-070-carve-out.md
│ ├── task-558 - Route-force-discovery-through-drip-publisher-add-maxBlock-to-throttle-warnings.md
│ ├── task-559 - Enforce-prerelease-bump-on-firmware-touching-commits-hook-helper-CLAUDE.md.md
│ ├── task-560 - Enforce-prerelease-bump-on-firmware-touching-commits-hook-helper-CLAUDE.md.md
│ ├── task-561 - fix-ADR-066-source-topic-gate-uses-wrong-enum-family-—-Write-Ack-flapping.md
│ ├── task-571 - fixmqtt-flip-MsgID-1-TSet-bSlaveEchoesValuefalse-—-heat-pump-non-echo-flap.md
│ ├── task-572 - fixmqtt-HA-discovery-friendly-name-uses-spaces-not-underscores.md
│ ├── task-573 - fixmqtt-normalise-HA-discovery-friendly-name-strings-—-split-camelCase-uppercase-acronyms-drop-typos.md
│ ├── task-575 - docs-update-documentation-for-changes-since-v1.4.1.md
│ ├── task-576 - feat-add-CHANGELOG.md-Keep-a-Changelog-and-integrate-into-release-workflow.md
│ ├── task-577 - Pure-JIT-MQTT-discovery-—-publish-OT-configs-only-when-MsgID-received.md
│ ├── task-578 - feat-2.0.0-port-TASK-577-—-Pure-JIT-MQTT-discovery-for-feature-branch.md
│ ├── task-588 - fixsat-wire-sat-curve_recommendation_attributes-to-HA-discovery-json_attributes_topic-or-remove-orphaned-publish.md
│ ├── task-589 - fixsat-remove-or-wire-orphaned-sat-climate_attributes-JSON-publish-512-byte-static-buffer.md
│ ├── task-590 - fixsat-remove-or-wire-orphaned-sat-pressure_health_attr-JSON-publish.md
│ ├── task-596 - docs-update-documentation-for-changes-since-v1.5.0-fix.md
│ └── task-86 - Fix-Max-CH-setpoint-shows-0°C-in-HA-Boiler-entity.md
├── bin/
│ └── bump-prerelease.sh
├── build.bat
├── build.py
├── build.sh
├── commits.txt
├── config.py
├── deep-research-report_arduino_core_3.1.2_reboot_issue_after_OTA.md
├── docs/
│ ├── BREAKING_CHANGES.md
│ ├── adr/
│ │ ├── ADR-001-esp8266-platform-selection.md
│ │ ├── ADR-002-modular-ino-architecture.md
│ │ ├── ADR-003-http-only-no-https.md
│ │ ├── ADR-004-static-buffer-allocation.md
│ │ ├── ADR-005-websocket-real-time-streaming.md
│ │ ├── ADR-006-mqtt-integration-pattern.md
│ │ ├── ADR-007-timer-based-task-scheduling.md
│ │ ├── ADR-008-littlefs-configuration-persistence.md
│ │ ├── ADR-009-progmem-string-literals.md
│ │ ├── ADR-010-multiple-concurrent-network-services.md
│ │ ├── ADR-011-external-hardware-watchdog.md
│ │ ├── ADR-012-pic-firmware-upgrade-via-web.md
│ │ ├── ADR-013-arduino-framework-over-esp-idf.md
│ │ ├── ADR-014-dual-build-system.md
│ │ ├── ADR-015-ntp-acetime-time-management.md
│ │ ├── ADR-016-opentherm-command-queue.md
│ │ ├── ADR-017-wifimanager-initial-configuration.md
│ │ ├── ADR-018-arduinojson-data-interchange.md
│ │ ├── ADR-019-rest-api-versioning-strategy.md
│ │ ├── ADR-020-dallas-ds18b20-sensor-integration.md
│ │ ├── ADR-021-s0-pulse-counter-interrupt-architecture.md
│ │ ├── ADR-022-gpio-output-bit-flag-control.md
│ │ ├── ADR-023-filesystem-explorer-http-api.md
│ │ ├── ADR-024-debug-telnet-command-console.md
│ │ ├── ADR-025-safari-websocket-connection-management.md
│ │ ├── ADR-026-conditional-javascript-cache-busting.md
│ │ ├── ADR-027-version-mismatch-warning-system.md
│ │ ├── ADR-028-file-streaming-over-loading.md
│ │ ├── ADR-029-simple-xhr-ota-flash.md
│ │ ├── ADR-030-heap-memory-monitoring-emergency-recovery.md
│ │ ├── ADR-031-two-microcontroller-coordination-architecture.md
│ │ ├── ADR-032-no-authentication-local-network-security.md
│ │ ├── ADR-033-dallas-sensor-custom-labels-graph-visualization.md
│ │ ├── ADR-034-non-blocking-modal-dialogs.md
│ │ ├── ADR-035-restful-api-compliance-strategy.md
│ │ ├── ADR-036-boot-sequence-ordering.md
│ │ ├── ADR-037-gateway-mode-detection.md
│ │ ├── ADR-038-opentherm-data-flow-pipeline.md
│ │ ├── ADR-039-otgraph-real-time-charting.md
│ │ ├── ADR-040-mqtt-source-specific-topics.md
│ │ ├── ADR-041-jit-ha-discovery.md
│ │ ├── ADR-042-streaming-json-no-arduinojson.md
│ │ ├── ADR-043-reset-pattern-wifi-recovery.md
│ │ ├── ADR-044-global-state-header-definition-pattern.md
│ │ ├── ADR-045-ps1-print-summary-parsing.md
│ │ ├── ADR-046-ps1-summary-translation-shared-publish-helpers.md
│ │ ├── ADR-047-nonblocking-wifi-reconnect.md
│ │ ├── ADR-048-nonblocking-webhook-state-machine.md
│ │ ├── ADR-049-string-prohibition-protocol-paths.md
│ │ ├── ADR-050-centralized-api-route-dispatch.md
│ │ ├── ADR-051-dual-encapsulating-structs.md
│ │ ├── ADR-052-mqtt-publish-eligibility-contract.md
│ │ ├── ADR-053-large-feature-buffer-static-allocation.md
│ │ ├── ADR-054-optional-http-basic-auth.md
│ │ ├── ADR-055-webhook-outbound-http-integration.md
│ │ ├── ADR-056-protected-admin-endpoint-security-and-secret-handling-contract.md
│ │ ├── ADR-057-webhook-delivery-retry-and-protected-test-endpoint-policy.md
│ │ ├── ADR-058-nonblocking-pic-command-response.md
│ │ ├── ADR-059-ser2net-queue-awareness.md
│ │ ├── ADR-060-pic-availability-guard-pattern.md
│ │ ├── ADR-061-wifi-reconnect-timeout-tuning.md
│ │ ├── ADR-062-retained-discovery-verification.md
│ │ ├── ADR-064-time-boundary-single-caller-contract.md
│ │ ├── ADR-065-otgw-pic-mqtt-subtree.md
│ │ ├── ADR-066-mqtt-publish-gating-by-source-and-slave-echo.md
│ │ ├── ADR-067-ha-discovery-state-reconciliation-on-ota-upgrade.md
│ │ ├── ADR-068-bseparatesources-mutually-exclusive-base-and-source-variants.md
│ │ ├── ADR-069-mqtt-source-topic-worldview-semantics.md
│ │ ├── ADR-070-mqtt-source-topic-sibling-suffix-shape.md
│ │ ├── ADR-071-mqtt-discovery-topic-sibling-suffix-shape.md
│ │ ├── ADR-072-ha-discovery-friendly-name-format.md
│ │ ├── ADR-073-jit-ha-discovery-smart-reconnect.md
│ │ ├── ADR_DATE_EVIDENCE_EXAMPLES.md
│ │ ├── ADR_DATE_VERIFICATION.md
│ │ ├── ADR_VERIFICATION_REPORT.md
│ │ ├── README.md
│ │ └── VERIFICATION_SUMMARY.md
│ ├── api/
│ │ ├── DALLAS_SENSOR_LABELS_API.md
│ │ ├── MQTT-message-id-echo-audit.md
│ │ ├── MQTT.md
│ │ ├── README.md
│ │ ├── WEBSOCKET_FLOW.md
│ │ ├── WEBSOCKET_QUICK_REFERENCE.md
│ │ ├── openapi-dallas-sensors.yaml
│ │ └── openapi.yaml
│ ├── archive/
│ │ ├── MQTT_old.md
│ │ ├── RELEASE_GENERATION_GUIDE.md
│ │ ├── daily-issue-report.md
│ │ ├── mqttha-generator/
│ │ │ ├── README.md
│ │ │ ├── generate_mqttha_data.py
│ │ │ ├── generate_mqttha_progmem.py
│ │ │ ├── generate_mqttha_readable.py
│ │ │ └── mqttha.cfg
│ │ ├── rc3-rc4-transition/
│ │ │ └── README.md
│ │ └── upgrade-from-0.x.md
│ ├── daily-issue-report.md
│ ├── features/
│ │ ├── TEMPERATURE_SENSOR_DIAGRAM.md
│ │ ├── TEMPERATURE_SENSOR_FINAL_SUMMARY.md
│ │ ├── TEMPERATURE_SENSOR_GRAPH_IMPLEMENTATION.md
│ │ ├── dallas-temperature-sensors.md
│ │ ├── data-persistence.md
│ │ ├── gateway-mode-detection.md
│ │ └── webhook.md
│ ├── fixes/
│ │ ├── CI_BUILD_FIX.md
│ │ ├── README.md
│ │ ├── SAFARI_FLASH_FIX.md
│ │ ├── mqtt-auth-analysis-v0.10.3-vs-v1.0.0.md
│ │ ├── mqtt-whitespace-auth-fix.md
│ │ └── opentherm-v42-mqtt-breaking-changes.md
│ ├── guides/
│ │ ├── BUILD.md
│ │ ├── FLASH_GUIDE.md
│ │ ├── MQTT_LWT.md
│ │ ├── MQTT_STALE_TOPICS_CLEANUP.md
│ │ ├── WEBSOCKET_LOGGING.md
│ │ ├── WIFI_RECOVERY_TRIPLE_RESET.md
│ │ └── browser-debug-console.md
│ ├── opentherm specification/
│ │ ├── Integration homeassistant.txt
│ │ ├── New OT data-ids.txt
│ │ ├── OT protocol version information.txt
│ │ ├── OT spec 2.3b.txt
│ │ ├── OT specification 2.3b.xlsx
│ │ ├── OT-specification2.3b-todo.txt
│ │ ├── OT-specification2.3b.txt
│ │ ├── OpenTherm-Protocol-Specification-v4.2-message-id-reference.md
│ │ ├── OpenTherm-Protocol-Specification-v4.2.md
│ │ └── OpenTherm-specifications.md
│ ├── plan/
│ │ ├── CPP_REFACTORING_PLAN.md
│ │ └── SETTINGS_STREAMING_REFACTOR_PLAN.md
│ ├── process/
│ │ ├── EVALUATION.md
│ │ ├── RELEASE_PROCESS.md
│ │ ├── branch-hygiene-queue.csv
│ │ ├── branch-hygiene-status.md
│ │ ├── ps1-lean-translator-refactor-plan.md
│ │ └── release-workflow.md
│ ├── releases/
│ │ ├── RELEASE_GITHUB_1.3.5.md
│ │ ├── RELEASE_GITHUB_1.4.1.md
│ │ ├── RELEASE_GITHUB_1.5.0-beta.md
│ │ ├── RELEASE_GITHUB_1.5.0.md
│ │ ├── RELEASE_NOTES_1.3.5.md
│ │ ├── RELEASE_NOTES_1.4.1.md
│ │ ├── RELEASE_NOTES_1.5.0-beta.md
│ │ ├── RELEASE_NOTES_1.5.0.md
│ │ └── archive/
│ │ ├── GITHUB_RELEASE_v1.3.0.md
│ │ ├── RELEASE_GITHUB_1.1.0.md
│ │ ├── RELEASE_GITHUB_1.2.0.md
│ │ ├── RELEASE_GITHUB_1.3.0.md
│ │ ├── RELEASE_GITHUB_1.3.1.md
│ │ ├── RELEASE_GITHUB_1.3.2.md
│ │ ├── RELEASE_GITHUB_1.3.3.md
│ │ ├── RELEASE_GITHUB_1.3.4.md
│ │ ├── RELEASE_NOTES_1.0.0.md
│ │ ├── RELEASE_NOTES_1.1.0.md
│ │ ├── RELEASE_NOTES_1.2.0.md
│ │ ├── RELEASE_NOTES_1.3.0.md
│ │ ├── RELEASE_NOTES_1.3.1.md
│ │ ├── RELEASE_NOTES_1.3.2.md
│ │ ├── RELEASE_NOTES_1.3.3.md
│ │ └── RELEASE_NOTES_1.3.4.md
│ └── reviews/
│ ├── 2026-01-17_dev-rc4-analysis/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── DEV_RC4_BRANCH_REVIEW.md
│ │ ├── EVALUATION_QUICKREF.md
│ │ ├── EVALUATION_SUMMARY.md
│ │ ├── FLASH_GUIDE.md
│ │ ├── HEAP_OPTIMIZATION_SUMMARY.md
│ │ ├── HIGH_PRIORITY_FIXES.md
│ │ ├── IMPLEMENTATION_SUMMARY.md
│ │ ├── LARGE_BUFFER_ANALYSIS.md
│ │ ├── LIBRARY_ANALYSIS.md
│ │ ├── MERGED_BINARY_GUIDE.md
│ │ ├── MQTT_STREAMING_AUTODISCOVERY.md
│ │ ├── OTGWSerial_PR_Description.md
│ │ ├── PIC_Flashing_Fix_Analysis.md
│ │ ├── README.md
│ │ ├── REVIEW_INDEX.md
│ │ ├── REVIEW_SUMMARY.md
│ │ ├── SENSOR_FIX_SUMMARY.md
│ │ ├── SENSOR_MQTT_ANALYSIS.md
│ │ └── Stream Logging.md
│ ├── 2026-01-18_post-merge-final/
│ │ ├── POST_MERGE_REVIEW.md
│ │ └── README.md
│ ├── 2026-01-19_pr364-verification/
│ │ ├── PR_364_VERIFICATION_REPORT.md
│ │ └── VERIFICATION_SUMMARY.md
│ ├── 2026-01-21_filesystem-flash-robustness/
│ │ ├── FLASH_ROBUSTNESS_ANALYSIS.md
│ │ ├── QUICK_REFERENCE.md
│ │ └── README.md
│ ├── 2026-01-23_pic-flash-update/
│ │ └── PIC_FLASH_WEBSOCKET_UPDATE.md
│ ├── 2026-01-26_browser-compatibility-review/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── BROWSER_COMPATIBILITY_AUDIT_2026.md
│ │ ├── COMPATIBILITY_SUMMARY_2026.md
│ │ ├── HIGH_PRIORITY_FIXES.md
│ │ ├── README.md
│ │ ├── REVIEW_INDEX.md
│ │ ├── SAFARI_COMPATIBILITY_ASSESSMENT.md
│ │ ├── WEBSOCKET_IMPROVEMENTS_SUMMARY.md
│ │ ├── WEBSOCKET_QUICKREF.md
│ │ ├── WEBSOCKET_ROBUSTNESS_ANALYSIS.md
│ │ └── WEBSOCKET_VISUAL_GUIDE.md
│ ├── 2026-01-27_pr384-code-review/
│ │ ├── PR384_CODE_REVIEW.md
│ │ └── README.md
│ ├── 2026-02-01_memory-management-bug-fix/
│ │ ├── BUG_FIX_ASSESSMENT.md
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ ├── QUICK_REFERENCE.md
│ │ └── README.md
│ ├── 2026-02-04_flash-approach-assessment/
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ ├── FLASH_APPROACH_ASSESSMENT.md
│ │ └── README.md
│ ├── 2026-02-06_config-strategy-analysis/
│ │ └── CONFIG_STRATEGY_EVALUATION.md
│ ├── 2026-02-11_codebase-improvements/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── BACKWARDS_COMPATIBILITY_PROOF.md
│ │ ├── CODEBASE_REVIEW.md
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ └── README.md
│ ├── 2026-02-13_codebase-review/
│ │ ├── CODEBASE_REVIEW.md
│ │ └── README.md
│ ├── 2026-02-15_opentherm-v42-compliance/
│ │ ├── OPENTHERM_V42_COMPLIANCE_PLAN.md
│ │ ├── OUT_OF_SCOPE_ANALYSIS.md
│ │ └── README.md
│ ├── 2026-02-16_restful-api-evaluation/
│ │ ├── IMPROVEMENT_PLAN.md
│ │ └── REST_API_EVALUATION.md
│ ├── 2026-02-20_issue-143-source-separation/
│ │ └── ISSUE_143_OPTIONS_ANALYSIS.md
│ ├── 2026-03-16_gpio-ota-postmortem/
│ │ ├── POSTMORTEM.md
│ │ └── README.md
│ ├── 2026-03-19_critical-review-refactoring/
│ │ └── REVIEW.md
│ ├── 2026-03-20_v1.2.0-to-v1.3.0-beta-review/
│ │ ├── FIXES_APPLIED.md
│ │ └── REVIEW.md
│ ├── 2026-04-07_issue-525-sdk-dhcp-analysis/
│ │ └── ANALYSIS_REPORT.md
│ ├── 2026-04-07_opentherm-spec-deep-audit/
│ │ ├── AUDIT_REPORT.md
│ │ ├── AUDIT_REPORT_EN.md
│ │ ├── AUDIT_REPORT_NL.md
│ │ └── README.md
│ └── 2026-04-24_v1.4.1-to-dev-handoff/
│ └── FINDINGS.md
├── evaluate.py
├── example-api/
│ ├── API_CHANGES_v1.0.0.md
│ ├── api-call-responses.txt
│ ├── hotwater_examples.md
│ └── outside_temperature_override_examples.md
├── flash_esp.py
├── flash_otgw.bat
├── flash_otgw.sh
├── logfile.txt
├── package.json
├── plan/
│ ├── OTGW_1.5.0_Beta_11.txt
│ └── process-debug-ota-filesystem-regression-1.md
├── scripts/
│ ├── README.md
│ ├── autoinc-semver.py
│ ├── branch-hygiene-queue.ps1
│ └── webui_launcher.py
├── src/
│ ├── OTGW-firmware/
│ │ ├── Debug.h
│ │ ├── FSexplorer.ino
│ │ ├── MQTTstuff.h
│ │ ├── MQTTstuff.ino
│ │ ├── OTGW-Core.h
│ │ ├── OTGW-Core.ino
│ │ ├── OTGW-ModUpdateServer-impl.h
│ │ ├── OTGW-ModUpdateServer.h
│ │ ├── OTGW-firmware.h
│ │ ├── OTGW-firmware.ino
│ │ ├── SATcontrol.ino
│ │ ├── SATcycles.ino
│ │ ├── SATpid.ino
│ │ ├── SATpressure.ino
│ │ ├── SATweather.ino
│ │ ├── data/
│ │ │ ├── FSexplorer.css
│ │ │ ├── FSexplorer.html
│ │ │ ├── FSexplorer_dark.css
│ │ │ ├── ds-tokens.css
│ │ │ ├── graph.js
│ │ │ ├── index.css
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ ├── index_common.css
│ │ │ ├── index_dark.css
│ │ │ ├── pic16f1847/
│ │ │ │ ├── diagnose.hex
│ │ │ │ ├── diagnose.ver
│ │ │ │ ├── gateway.hex
│ │ │ │ ├── gateway.ver
│ │ │ │ ├── interface.hex
│ │ │ │ └── interface.ver
│ │ │ ├── pic16f88/
│ │ │ │ ├── diagnose.hex
│ │ │ │ ├── diagnose.ver
│ │ │ │ ├── gateway-4.3.hex
│ │ │ │ ├── gateway-4.3.ver
│ │ │ │ ├── gateway.hex
│ │ │ │ ├── gateway.ver
│ │ │ │ ├── interface.hex
│ │ │ │ └── interface.ver
│ │ │ ├── settings.ini
│ │ │ └── version.hash
│ │ ├── handleDebug.ino
│ │ ├── helperStuff.ino
│ │ ├── jsonStuff.ino
│ │ ├── mqtt_configuratie.cpp
│ │ ├── mqtt_discovery_verify.cpp
│ │ ├── mqtt_discovery_verify.h
│ │ ├── networkStuff.h
│ │ ├── networkStuff.h.tmp
│ │ ├── networkStuff.ino
│ │ ├── outputs_ext.ino
│ │ ├── restAPI.ino
│ │ ├── s0PulseCount.ino
│ │ ├── safeTimers.h
│ │ ├── sensors_ext.ino
│ │ ├── settingStuff.ino
│ │ ├── updateServerHtml.h
│ │ ├── version.h
│ │ ├── versionStuff.ino
│ │ ├── webSocketStuff.ino
│ │ └── webhook.ino
│ ├── OTGW-firmware$f
│ └── libraries/
│ └── OTGWSerial/
│ ├── OTGWSerial.cpp
│ └── OTGWSerial.h
├── test_flash_automation.py
├── tests/
│ ├── README.md
│ └── test_dallas_address.cpp
└── tools/
└── opentherm_v42_spec_audit.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/.vscode/arduino.json
================================================
{
"configuration": "xtal=80,vt=flash,exception=legacy,ssl=all,eesz=4M2M,led=2,ip=lm2f,dbg=Disabled,lvl=None____,wipe=none,baud=115200",
"board": "esp8266:esp8266:nodemcuv2",
"sketch": "OTGW-firmware.ino",
"prebuild": "..\\autoinc-semver\\semver-incr-build.bat version.h",
"output": "..\\build",
"port": "COM5"
}
================================================
FILE: .claude/.vscode/c_cpp_properties.json
================================================
{
"version": 4,
"configurations": [
{
"name": "Arduino",
"compilerPath": "C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\bin\\xtensa-lx106-elf-g++",
"compilerArgs": [
"-U__STRICT_ANSI__",
"-free",
"-fipa-pta",
"-Werror=return-type",
"-mlongcalls",
"-mtext-section-literals",
"-fno-rtti",
"-falign-functions=4",
"-std=gnu++17"
],
"intelliSenseMode": "gcc-x64",
"includePath": [
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\tools\\sdk\\include",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\tools\\sdk\\lwip2\\include",
"D:\\Users\\Robert\\Documents\\GitHub\\RvdB\\build\\core",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\cores\\esp8266",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\variants\\nodemcu",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\AceTime\\src",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\AceCommon\\src",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\AceSorting\\src",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\TelnetStream\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\ESP8266WiFi\\src",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\ArduinoJson\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\Wire",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\OneWire",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\DallasTemperature",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\ESP8266WebServer\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\ESP8266mDNS\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\ESP8266HTTPClient\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\ESP8266LLMNR",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\WiFiManager",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\DNSServer\\src",
"C:\\Users\\rvdbr\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\3.1.2\\libraries\\LittleFS\\src",
"D:\\Users\\Robert\\Documents\\Arduino\\libraries\\PubSubClient\\src",
"c:\\users\\rvdbr\\appdata\\local\\arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\xtensa-lx106-elf\\include\\c++\\10.3.0",
"c:\\users\\rvdbr\\appdata\\local\\arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\xtensa-lx106-elf\\include\\c++\\10.3.0\\xtensa-lx106-elf",
"c:\\users\\rvdbr\\appdata\\local\\arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\xtensa-lx106-elf\\include\\c++\\10.3.0\\backward",
"c:\\users\\rvdbr\\appdata\\local\\arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\lib\\gcc\\xtensa-lx106-elf\\10.3.0\\include-fixed",
"c:\\users\\rvdbr\\appdata\\local\\arduino15\\packages\\esp8266\\tools\\xtensa-lx106-elf-gcc\\3.1.0-gcc10.3-e5f9fec\\xtensa-lx106-elf\\include"
],
"forcedInclude": [],
"cStandard": "c11",
"cppStandard": "c++17",
"defines": [
"__ets__",
"ICACHE_FLASH",
"_GNU_SOURCE",
"ESP8266",
"MMU_IRAM_SIZE=0x8000",
"MMU_ICACHE_SIZE=0x8000",
"NONOSDK22x_190703=1",
"F_CPU=80000000L",
"LWIP_OPEN_SRC",
"TCP_MSS=536",
"LWIP_FEATURES=1",
"LWIP_IPV6=0",
"ARDUINO=10607",
"ARDUINO_ESP8266_NODEMCU_ESP12E",
"ARDUINO_ARCH_ESP8266",
"ARDUINO_BOARD=\"ESP8266_NODEMCU_ESP12E\"",
"ARDUINO_BOARD_ID=\"nodemcuv2\"",
"LED_BUILTIN=2",
"FLASHMODE_DIO",
"__DBL_MIN_EXP__=(-1021)",
"__cpp_attributes=200809L",
"__UINT_LEAST16_MAX__=0xffff",
"__ATOMIC_ACQUIRE=2",
"__FLT_MIN__=1.1754943508222875e-38F",
"__GCC_IEC_559_COMPLEX=0",
"__cpp_aggregate_nsdmi=201304L",
"__UINT_LEAST8_TYPE__=unsigned char",
"__INTMAX_C(c)=c ## LL",
"__CHAR_BIT__=8",
"__UINT8_MAX__=0xff",
"__WINT_MAX__=0xffffffffU",
"__FLT32_MIN_EXP__=(-125)",
"__cpp_static_assert=200410L",
"__ORDER_LITTLE_ENDIAN__=1234",
"__SIZE_MAX__=0xffffffffU",
"__WCHAR_MAX__=0xffff",
"__DBL_DENORM_MIN__=double(4.9406564584124654e-324L)",
"__GCC_ATOMIC_CHAR_LOCK_FREE=1",
"__GCC_IEC_559=0",
"__FLT32X_DECIMAL_DIG__=17",
"__FLT_EVAL_METHOD__=0",
"__cpp_binary_literals=201304L",
"__FLT64_DECIMAL_DIG__=17",
"__GCC_ATOMIC_CHAR32_T_LOCK_FREE=1",
"__cpp_variadic_templates=200704L",
"__UINT_FAST64_MAX__=0xffffffffffffffffULL",
"__SIG_ATOMIC_TYPE__=int",
"__DBL_MIN_10_EXP__=(-307)",
"__FINITE_MATH_ONLY__=0",
"__cpp_variable_templates=201304L",
"__FLT32X_MAX_EXP__=1024",
"__GNUC_PATCHLEVEL__=0",
"__FLT32_HAS_DENORM__=1",
"__UINT_FAST8_MAX__=0xffffffffU",
"__cpp_rvalue_reference=200610L",
"__FLT32_MAX_10_EXP__=38",
"__INT8_C(c)=c",
"__INT_LEAST8_WIDTH__=8",
"__UINT_LEAST64_MAX__=0xffffffffffffffffULL",
"__SHRT_MAX__=0x7fff",
"__LDBL_MAX__=1.7976931348623157e+308L",
"__UINT_LEAST8_MAX__=0xff",
"__GCC_ATOMIC_BOOL_LOCK_FREE=1",
"__UINTMAX_TYPE__=long long unsigned int",
"__FLT_EVAL_METHOD_TS_18661_3__=0",
"__CHAR_UNSIGNED__=1",
"__UINT32_MAX__=0xffffffffU",
"__GXX_EXPERIMENTAL_CXX0X__=1",
"__LDBL_MAX_EXP__=1024",
"__WINT_MIN__=0U",
"__INT_LEAST16_WIDTH__=16",
"__SCHAR_MAX__=0x7f",
"__WCHAR_MIN__=0",
"__INT64_C(c)=c ## LL",
"__GCC_ATOMIC_POINTER_LOCK_FREE=1",
"__XTENSA_CALL0_ABI__=1",
"__SIZEOF_INT__=4",
"__FLT32X_MANT_DIG__=53",
"__GCC_ATOMIC_CHAR16_T_LOCK_FREE=1",
"__USER_LABEL_PREFIX__",
"__STDC_HOSTED__=1",
"__XTENSA_EL__=1",
"__cpp_decltype_auto=201304L",
"__DBL_DIG__=15",
"__FLT32_DIG__=6",
"__FLT_EPSILON__=1.1920928955078125e-7F",
"__GXX_WEAK__=1",
"__SHRT_WIDTH__=16",
"__LDBL_MIN__=2.2250738585072014e-308L",
"__cpp_threadsafe_static_init=200806L",
"__FLT32X_HAS_INFINITY__=1",
"__INT32_MAX__=0x7fffffff",
"__INT_WIDTH__=32",
"__SIZEOF_LONG__=4",
"__UINT16_C(c)=c",
"__DECIMAL_DIG__=17",
"__FLT64_EPSILON__=2.2204460492503131e-16F64",
"__INT16_MAX__=0x7fff",
"__FLT64_MIN_EXP__=(-1021)",
"__LDBL_HAS_QUIET_NAN__=1",
"__FLT64_MANT_DIG__=53",
"__GNUC__=10",
"__GXX_RTTI=1",
"__FLT_HAS_DENORM__=1",
"__SIZEOF_LONG_DOUBLE__=8",
"__BIGGEST_ALIGNMENT__=16",
"__STDC_UTF_16__=1",
"__FLT64_MAX_10_EXP__=308",
"__cpp_delegating_constructors=200604L",
"__FLT32_HAS_INFINITY__=1",
"__DBL_MAX__=double(1.7976931348623157e+308L)",
"__cpp_raw_strings=200710L",
"__INT_FAST32_MAX__=0x7fffffff",
"__DBL_HAS_INFINITY__=1",
"__HAVE_SPECULATION_SAFE_VALUE=1",
"__INTPTR_WIDTH__=32",
"__UINT_LEAST32_MAX__=0xffffffffU",
"__FLT32X_HAS_DENORM__=1",
"__INT_FAST16_TYPE__=int",
"__LDBL_HAS_DENORM__=1",
"__cplusplus=201402L",
"__cpp_ref_qualifiers=200710L",
"__INT_LEAST32_MAX__=0x7fffffff",
"__DEPRECATED=1",
"__cpp_rvalue_references=200610L",
"__DBL_MAX_EXP__=1024",
"__WCHAR_WIDTH__=16",
"__FLT32_MAX__=3.4028234663852886e+38F32",
"__GCC_ATOMIC_LONG_LOCK_FREE=1",
"__PTRDIFF_MAX__=0x7fffffff",
"__FLT32_HAS_QUIET_NAN__=1",
"__GNUG__=10",
"__LONG_LONG_MAX__=0x7fffffffffffffffLL",
"__SIZEOF_SIZE_T__=4",
"__cpp_nsdmi=200809L",
"__SIZEOF_WINT_T__=4",
"__LONG_LONG_WIDTH__=64",
"__cpp_initializer_lists=200806L",
"__FLT32_MAX_EXP__=128",
"__cpp_hex_float=201603L",
"__GXX_ABI_VERSION=1014",
"__FLT_MIN_EXP__=(-125)",
"__cpp_lambdas=200907L",
"__INT_FAST64_TYPE__=long long int",
"__FLT64_DENORM_MIN__=4.9406564584124654e-324F64",
"__DBL_MIN__=double(2.2250738585072014e-308L)",
"__SIZEOF_POINTER__=4",
"__SIZE_TYPE__=unsigned int",
"__DBL_HAS_QUIET_NAN__=1",
"__FLT32X_EPSILON__=2.2204460492503131e-16F32x",
"__FLT64_MIN_10_EXP__=(-307)",
"__REGISTER_PREFIX__",
"__UINT16_MAX__=0xffff",
"__FLT32_MIN__=1.1754943508222875e-38F32",
"__UINT8_TYPE__=unsigned char",
"__FLT_DIG__=6",
"__NO_INLINE__=1",
"__DEC_EVAL_METHOD__=2",
"__FLT_MANT_DIG__=24",
"__LDBL_DECIMAL_DIG__=17",
"__VERSION__=\"10.3.0\"",
"__UINT64_C(c)=c ## ULL",
"__cpp_unicode_characters=200704L",
"__XTENSA_SOFT_FLOAT__=1",
"__GCC_ATOMIC_INT_LOCK_FREE=1",
"__FLT32_MANT_DIG__=24",
"__FLOAT_WORD_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__SCHAR_WIDTH__=8",
"__INT32_C(c)=c",
"__ORDER_PDP_ENDIAN__=3412",
"__INT_FAST32_TYPE__=int",
"__UINT_LEAST16_TYPE__=short unsigned int",
"__DBL_HAS_DENORM__=1",
"__cpp_rtti=199711L",
"__UINT64_MAX__=0xffffffffffffffffULL",
"__INT8_TYPE__=signed char",
"__cpp_digit_separators=201309L",
"__ELF__=1",
"__xtensa__=1",
"__FLT_RADIX__=2",
"__INT_LEAST16_TYPE__=short int",
"__LDBL_EPSILON__=2.2204460492503131e-16L",
"__UINTMAX_C(c)=c ## ULL",
"__FLT32X_MIN__=2.2250738585072014e-308F32x",
"__SIG_ATOMIC_MAX__=0x7fffffff",
"__GCC_ATOMIC_WCHAR_T_LOCK_FREE=1",
"__SIZEOF_PTRDIFF_T__=4",
"__LDBL_DIG__=15",
"__FLT32X_MIN_EXP__=(-1021)",
"__INT_FAST16_MAX__=0x7fffffff",
"__FLT64_DIG__=15",
"__UINT_FAST32_MAX__=0xffffffffU",
"__UINT_LEAST64_TYPE__=long long unsigned int",
"__FLT_HAS_QUIET_NAN__=1",
"__FLT_MAX_10_EXP__=38",
"__LONG_MAX__=0x7fffffffL",
"__FLT_HAS_INFINITY__=1",
"__cpp_unicode_literals=200710L",
"__UINT_FAST16_TYPE__=unsigned int",
"__INT_FAST32_WIDTH__=32",
"__CHAR16_TYPE__=short unsigned int",
"__PRAGMA_REDEFINE_EXTNAME=1",
"__SIZE_WIDTH__=32",
"__INT_LEAST16_MAX__=0x7fff",
"__INT64_MAX__=0x7fffffffffffffffLL",
"__FLT32_DENORM_MIN__=1.4012984643248171e-45F32",
"__SIG_ATOMIC_WIDTH__=32",
"__INT_LEAST64_TYPE__=long long int",
"__INT16_TYPE__=short int",
"__INT_LEAST8_TYPE__=signed char",
"__INT_FAST8_MAX__=0x7fffffff",
"__INTPTR_MAX__=0x7fffffff",
"__cpp_sized_deallocation=201309L",
"__FLT64_HAS_QUIET_NAN__=1",
"__FLT32_MIN_10_EXP__=(-37)",
"__EXCEPTIONS=1",
"__PTRDIFF_WIDTH__=32",
"__LDBL_MANT_DIG__=53",
"__cpp_range_based_for=200907L",
"__FLT64_HAS_INFINITY__=1",
"__SIG_ATOMIC_MIN__=(-__SIG_ATOMIC_MAX__ - 1)",
"__cpp_return_type_deduction=201304L",
"__INTPTR_TYPE__=int",
"__UINT16_TYPE__=short unsigned int",
"__WCHAR_TYPE__=short unsigned int",
"__SIZEOF_FLOAT__=4",
"__UINTPTR_MAX__=0xffffffffU",
"__INT_FAST64_WIDTH__=64",
"__cpp_decltype=200707L",
"__FLT32_DECIMAL_DIG__=9",
"__INT_FAST64_MAX__=0x7fffffffffffffffLL",
"__GCC_ATOMIC_TEST_AND_SET_TRUEVAL=1",
"__FLT_NORM_MAX__=3.4028234663852886e+38F",
"__UINT_FAST64_TYPE__=long long unsigned int",
"__INT_MAX__=0x7fffffff",
"__INT64_TYPE__=long long int",
"__FLT_MAX_EXP__=128",
"__DBL_MANT_DIG__=53",
"__cpp_inheriting_constructors=201511L",
"__INT_LEAST64_MAX__=0x7fffffffffffffffLL",
"__WINT_TYPE__=unsigned int",
"__UINT_LEAST32_TYPE__=unsigned int",
"__SIZEOF_SHORT__=2",
"__FLT32_NORM_MAX__=3.4028234663852886e+38F32",
"__LDBL_MIN_EXP__=(-1021)",
"__FLT64_MAX__=1.7976931348623157e+308F64",
"__WINT_WIDTH__=32",
"__INT_LEAST8_MAX__=0x7f",
"__INT_LEAST64_WIDTH__=64",
"__FLT32X_MAX_10_EXP__=308",
"__WCHAR_UNSIGNED__=1",
"__LDBL_MAX_10_EXP__=308",
"__ATOMIC_RELAXED=0",
"__DBL_EPSILON__=double(2.2204460492503131e-16L)",
"__UINT8_C(c)=c",
"__FLT64_MAX_EXP__=1024",
"__INT_LEAST32_TYPE__=int",
"__SIZEOF_WCHAR_T__=2",
"__FLT64_NORM_MAX__=1.7976931348623157e+308F64",
"__INTMAX_MAX__=0x7fffffffffffffffLL",
"__INT_FAST8_TYPE__=int",
"__LDBL_HAS_INFINITY__=1",
"__GNUC_STDC_INLINE__=1",
"__FLT64_HAS_DENORM__=1",
"__FLT32_EPSILON__=1.1920928955078125e-7F32",
"__DBL_DECIMAL_DIG__=17",
"__STDC_UTF_32__=1",
"__INT_FAST8_WIDTH__=32",
"__FLT32X_MAX__=1.7976931348623157e+308F32x",
"__DBL_NORM_MAX__=double(1.7976931348623157e+308L)",
"__BYTE_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__XTENSA__=1",
"__INTMAX_WIDTH__=64",
"__ORDER_BIG_ENDIAN__=4321",
"__cpp_runtime_arrays=198712L",
"__UINT64_TYPE__=long long unsigned int",
"__UINT32_C(c)=c ## U",
"__cpp_alias_templates=200704L",
"__FLT_DENORM_MIN__=1.4012984643248171e-45F",
"__INT8_MAX__=0x7f",
"__LONG_WIDTH__=32",
"__UINT_FAST32_TYPE__=unsigned int",
"__FLT32X_NORM_MAX__=1.7976931348623157e+308F32x",
"__CHAR32_TYPE__=unsigned int",
"__FLT_MAX__=3.4028234663852886e+38F",
"__cpp_constexpr=201304L",
"__INT32_TYPE__=int",
"__SIZEOF_DOUBLE__=8",
"__cpp_exceptions=199711L",
"__FLT_MIN_10_EXP__=(-37)",
"__FLT64_MIN__=2.2250738585072014e-308F64",
"__INT_LEAST32_WIDTH__=32",
"__INTMAX_TYPE__=long long int",
"__FLT32X_HAS_QUIET_NAN__=1",
"__ATOMIC_CONSUME=1",
"__GNUC_MINOR__=3",
"__INT_FAST16_WIDTH__=32",
"__UINTMAX_MAX__=0xffffffffffffffffULL",
"__FLT32X_DENORM_MIN__=4.9406564584124654e-324F32x",
"__DBL_MAX_10_EXP__=308",
"__LDBL_DENORM_MIN__=4.9406564584124654e-324L",
"__INT16_C(c)=c",
"__STDC__=1",
"__FLT32X_DIG__=15",
"__PTRDIFF_TYPE__=int",
"__ATOMIC_SEQ_CST=5",
"__UINT32_TYPE__=unsigned int",
"__FLT32X_MIN_10_EXP__=(-307)",
"__UINTPTR_TYPE__=unsigned int",
"__LDBL_MIN_10_EXP__=(-307)",
"__cpp_generic_lambdas=201304L",
"__SIZEOF_LONG_LONG__=8",
"__cpp_user_defined_literals=200809L",
"__GCC_ATOMIC_LLONG_LOCK_FREE=1",
"__FLT_DECIMAL_DIG__=9",
"__UINT_FAST16_MAX__=0xffffffffU",
"__LDBL_NORM_MAX__=1.7976931348623157e+308L",
"__GCC_ATOMIC_SHORT_LOCK_FREE=1",
"__UINT_FAST8_TYPE__=unsigned int",
"__cpp_init_captures=201304L",
"__ATOMIC_ACQ_REL=4",
"__ATOMIC_RELEASE=3",
"USBCON"
]
}
]
}
================================================
FILE: .claude/.vscode/settings.json
================================================
{
"editor.suggest.snippetsPreventQuickSuggestions": false,
"aiXcoder.showTrayIcon": true,
"DevChat.Language": "en",
"DevChat.PythonForChat": "c:\\Users\\rvdbr\\.vscode\\extensions\\merico.devchat-0.1.47\\tools\\python-3.11.6-embed-amd64\\python.exe",
"cmake.sourceDirectory": "D:/Users/Robert/Documents/GitHub/RvdB/OTGW-firmware/libraries/ArduinoJson",
"chat.tools.terminal.autoApprove": {
"/^python evaluate\\.py --report --verbose$/": {
"approve": true,
"matchCommandLine": true
},
"/^Write-Host \"Stashing local changes temporarily\\.\\.\\.\" -ForegroundColor Yellow\ngit stash push -m \"Temporary stash for cherry-pick\"\n\nWrite-Host \"`nNow attempting cherry-pick again\\.\\.\\.\" -ForegroundColor Yellow\nWrite-Host \"\"\n\n# Cherry-pick commit 1: Evaluation framework\nWrite-Host \"Cherry-picking fc63a60 \\(Evaluation framework\\)\\.\\.\\.\" -ForegroundColor Cyan\ngit cherry-pick fc63a60\n\nif \\(\\$LASTEXITCODE -eq 0\\) \\{\n Write-Host \"✓ Successfully cherry-picked fc63a60\" -ForegroundColor Green\n\\} else \\{\n Write-Host \"✗ Cherry-pick failed\" -ForegroundColor Red\n git status\n\\}\n$/": {
"approve": true,
"matchCommandLine": true
}
},
"chat.agentSessionProjection.enabled": true,
"chat.customAgentInSubagent.enabled": true,
"chat.unifiedAgentsBar.enabled": true,
"github.copilot.chat.switchAgent.enabled": true,
"claudeCodeChat.permissions.yoloMode": true,
"chatgpt.openOnStartup": false
}
================================================
FILE: .claude/.vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"type": "cmake",
"label": "CMake: build",
"command": "build",
"targets": [
""
],
"group": "build",
"problemMatcher": [],
"detail": "CMake template build task"
},
{
"label": "build-firmware",
"type": "shell",
"command": "python",
"args": [
"build.py"
],
"isBackground": false,
"group": "build"
},
{
"label": "build-firmware",
"type": "shell",
"command": "python",
"args": [
"build.py"
],
"isBackground": false,
"group": "build"
}
]
}
================================================
FILE: .claude/adr-kit-guide.md
================================================
<!-- adr-kit-guide v0.13.0 -->
<!-- Canonical project-side ADR guide. Copied from the plugin's templates/adr-kit-guide.md to .claude/adr-kit-guide.md by /adr-kit:init, /adr-kit:upgrade, and /adr-kit:setup. -->
<!-- This file is plain markdown — readable by Claude Code, headless `claude -p`, shell scripts in pre-commit hooks, evaluator scripts, and any agent that doesn't process @-imports. Do not embed Claude-Code-specific syntax inside this file. -->
# ADR Kit Guide
This project uses [adr-kit](https://github.com/rvdbreemen/adr-kit) to manage Architecture Decision Records. The kit ships:
- a project-side guide (this file) referenced from `CLAUDE.md`,
- a library of slash commands and a subagent for ADR authorship,
- a pre-commit hook that catches code changes drifting outside accepted ADRs.
ADR files live at `docs/adr/ADR-NNN-kebab-case-title.md`. They are versioned, immutable once accepted, and the durable record of *why* the codebase looks the way it does.
## Three operating modes
| Mode | When | Entry point |
|---|---|---|
| **Init / bootstrap** | Once per project: scan source + docs, propose a starter ADR set, hook the kit into `CLAUDE.md`, install the pre-commit hook | `/adr-kit:init` |
| **Per-commit verification** | Every `git commit`: declarative-rule check **plus** Claude Sonnet LLM judge for `llm_judge: true` ADRs in one batched call. Default-on as of v0.13.0. Falls back to declarative-only when the `claude` CLI is unavailable | `.githooks/pre-commit` (auto) |
| **On-demand invocation** | Mid-session: write a new ADR, judge a staged diff, supersede an existing decision | `/adr-kit:adr`, `/adr-kit:judge`, `adr-generator` subagent |
## Slash commands
| Command | Purpose | User-only? |
|---|---|---|
| `/adr-kit:init` | One-shot project bootstrap (audit codebase, generate ADRs, install hook). Combines `setup` + audit + `install-hooks`. | yes |
| `/adr-kit:adr` | Author a single ADR (delegates to `adr-generator` subagent; runs four verification gates). | no — model can self-call |
| `/adr-kit:judge` | Interactive judge against a staged diff. Runs declarative checks + in-session LLM check for `llm_judge: true` ADRs. Walks resolution paths on violation. | no — model can self-call |
| `/adr-kit:lint` | Validate existing ADRs against the four verification gates. | yes |
| `/adr-kit:migrate` | Rewrite legacy ADRs into canonical format. | yes |
| `/adr-kit:setup` | Append `## ADR Kit` block to `CLAUDE.md` (idempotent). | yes |
| `/adr-kit:upgrade` | Migrate v0.11 → v0.12 footprint without re-running the heavy audit. | yes |
| `/adr-kit:install-hooks` | Install or uninstall the pre-commit hook. | yes |
## The four verification gates
An ADR cannot move from `Proposed` to `Accepted` until all four pass.
1. **Completeness** — every required section is present and non-empty: Status, Context, Decision, Alternatives Considered (≥ 2), Consequences (positive + negative), Related Decisions, References. Plus filename matches `ADR-NNN-kebab-case.md` and the heading number agrees.
2. **Evidence** — Context or References cites at least one concrete external/internal artefact (incident, profiling data, code path, RFC, vendor doc). No hand-waving justifications.
3. **Clarity** — Decision section names a single concrete choice (not a survey), uses imperative voice, no hedging language ("perhaps", "we should consider"). Identifiers (file paths, function names, config keys) are traceable.
4. **Consistency** — filename, heading number, and any cross-references resolve. No duplicate ADR numbers in the directory.
`bin/adr-lint` enforces Completeness and Consistency deterministically. Evidence and Clarity are heuristic; opt in via `--gates evidence,clarity` or run `/adr-kit:lint` to use the LLM-aware skill.
## Authoring workflow (`/adr-kit:adr` or `adr-generator`)
1. Identify the architecturally significant change (architecture, NFRs, interfaces, dependencies, build/CI tooling). Refactors and bug fixes within existing patterns do NOT need an ADR.
2. Invoke `/adr-kit:adr` (or the `adr-generator` subagent). Provide: title, context with concrete forces, ≥ 2 alternatives with rejection reasons, consequences (both directions), related ADRs.
3. The agent applies the four gates and writes `docs/adr/ADR-NNN-…md` with `Status: Proposed`.
4. Human review. Iterate until all gates pass.
5. Flip Status to `Accepted, YYYY-MM-DD` after explicit human approval. **Never self-approve.**
6. If the decision touches code in a mechanically expressible way, add an `Enforcement` block (see below) so the pre-commit hook can guard the boundary.
## Enforcement block (v0.12+)
Optional `## Enforcement` section at the end of an ADR. Fenced JSON code block, parsed by `bin/adr-judge`. Schema: plugin's `schemas/adr-enforcement.schema.json`.
```json
{
"forbid_pattern": [
{ "pattern": "\\bArduinoJson\\b", "path_glob": "src/**/*.{ino,cpp,h}",
"message": "Use snprintf_P + sendJsonMapEntry; ArduinoJson fragments the heap (ADR-042)." }
],
"forbid_import": [
{ "pattern": "^#include\\s+<ArduinoJson\\.h>", "path_glob": "src/**" }
],
"require_pattern": [],
"llm_judge": false
}
```
**Rules:**
- `forbid_pattern` — regex must NOT match any added line in the diff (lines starting with `+`, excluding `+++ ` markers).
- `forbid_import` — same engine as `forbid_pattern`; the separate name documents intent.
- `require_pattern` — regex must match at least once in the post-diff content of any file matching `path_glob`.
- `llm_judge: true` — Claude Sonnet evaluates the diff against this ADR's `## Decision` text at commit time (default-on as of v0.13.0). The pre-commit hook batches all `llm_judge: true` ADRs into one Sonnet call and blocks the commit on `VIOLATION`. Falls back gracefully (advisory only, exit 0) when the `claude` CLI is missing.
- ADRs with no Enforcement block are skipped silently by the judge.
**Path globs** support `**` (recursive). Examples: `src/**/*.py`, `tests/**`, `**/Makefile`.
## Pre-commit hook
After `/adr-kit:init` (or `/adr-kit:install-hooks`), every `git commit` runs `bin/adr-judge` on the staged diff with two passes:
- **Declarative pass** — fast, regex-only, no LLM. A violation exits non-zero and blocks the commit.
- **LLM pass (Sonnet, default-on as of v0.13.0)** — all `llm_judge: true` ADRs are batched into one `claude -p --model claude-sonnet-4-6` call. Sonnet returns a per-ADR JSON verdict; any `VIOLATION` blocks the commit with the model's one-sentence reason. Falls back gracefully when the `claude` CLI is missing or unauthenticated — never blocks a legitimate commit due to tooling drift.
**Cost shape** (typical project, 50 `llm_judge` ADRs, small diff): roughly $0.10–0.30 per commit on Sonnet 4.6 with prompt caching. Latency 5–10s. Configurable via `judge.llm_model` / `judge.llm_timeout_seconds` / `judge.llm_cmd` in `docs/adr/.adr-kit.json`.
**Knobs:**
- Disable LLM pass per commit: `ADR_KIT_NO_LLM=1 git commit -m "…"`
- Disable hook entirely per commit: `ADR_KIT_HOOK_DISABLE=1 git commit -m "…"`
- Switch model: set `judge.llm_model: "claude-haiku-4-5"` in `.adr-kit.json` for higher throughput at lower cost.
- Remove permanently: `/adr-kit:install-hooks --uninstall`
## Supersession (changing a decision)
Accepted ADRs are immutable. To change a decision:
1. Author a new ADR with the next number. Status `Proposed`. The Decision should explain what changes and why now.
2. In its Related Decisions: `Supersedes ADR-OLD`.
3. After the new ADR is `Accepted`: edit ONLY the old ADR's Status line to `Superseded by ADR-NEW, YYYY-MM-DD.` Leave every other section untouched — the old decision's content is the historical record.
Never edit Decision, Context, Consequences, or Alternatives of an Accepted/Deprecated ADR. The Status line is the only permitted change.
## Code review checks
When reviewing a PR, apply these seven checks (Check 7 added in v0.12):
1. **ADR exists** for any architecturally significant change in the PR (new dep, interface change, NFR shift, build tooling change). Missing → request the author to invoke `/adr-kit:adr` or `adr-generator`.
2. **ADR is linked** in the PR description (path or relative URL).
3. **No violation** of Accepted ADRs in the diff. Cross-reference against `docs/adr/README.md` and the Enforcement blocks. The pre-commit hook should have caught this; if it didn't, the ADR is missing rules or wasn't installed.
4. **Supersession chain is correct** — old ADR's Status updated, new ADR cross-references, content immutability preserved.
5. **All four gates pass** on any new/modified ADR. Cite the failing gate when blocking ("fails Evidence gate — no concrete reference in Context").
6. **Legacy non-compliance has a remediation plan** — pre-existing violations that this PR doesn't fix should at least carry a `// TODO(ADR-NNN): align` or a backlog entry, not be silently ignored.
7. **Enforcement block is set appropriately** on any new Accepted ADR with a code surface. Either declarative rules, OR `llm_judge: true`, OR an explicit "manual review only" note in the ADR body explaining why the rule cannot be expressed mechanically. Missing block on a code-touching ADR is a smell.
## Anti-rationalisation guards
When `/adr-kit:adr` is asked to write or accept an ADR, it actively pushes back on these nine common excuses (see plugin's `skills/adr/SKILL.md` for the full text):
- "It's just a small change" — the rule is "architecturally significant", not "large".
- "We can decide later" — later is now; defer = decide.
- "Everyone knows this" — undocumented tacit knowledge is the problem ADRs solve.
- "It's documented in the code" — code shows what, not why.
- "We'll do it the same as last time" — name "last time" with an ADR reference.
- "There's only one option" — there are always alternatives; "do nothing" is one.
- "It's reversible" — most architecture is partially reversible; the ADR captures the *current* commitment.
- "It's a refactor" — pure refactors don't need ADRs; *new patterns* introduced during refactoring do.
- "We don't have time" — opportunity cost of skipping is a future maintainer hunting for the why.
## Plugin-side deep dives
This guide is the project's own copy. For agents inside Claude Code, the plugin auto-loads richer instructions:
- Plugin path (locale-dependent): `~/.claude/plugins/cache/rvdbreemen-adr-kit/adr-kit/<version>/`
- `instructions/adr.coding.md` — per-developer rules (when to invoke the agent, supersession workflow, Definition of Done).
- `instructions/adr.review.md` — the seven review checks with citation templates.
- `skills/adr/SKILL.md` — full anti-rationalisation guard list, gate definitions, code examples.
- `agents/adr-generator.md` — the subagent prompt.
If you're working outside Claude Code (in a hook, a CI job, or a different agent), this file (`.claude/adr-kit-guide.md`) is your one-stop reference. Keep it in version control with the rest of the project.
================================================
FILE: .claude/backlog-cli-reference.md
================================================
# Instructions for the usage of Backlog.md CLI Tool
## Backlog.md: Comprehensive Project Management Tool via CLI
### Assistant Objective
Efficiently manage all project tasks, status, and documentation using the Backlog.md CLI, ensuring all project metadata
remains fully synchronized and up-to-date.
### Core Capabilities
- ✅ **Task Management**: Create, edit, assign, prioritize, and track tasks with full metadata
- ✅ **Search**: Fuzzy search across tasks, documents, and decisions with `backlog search`
- ✅ **Acceptance Criteria**: Granular control with add/remove/check/uncheck by index
- ✅ **Definition of Done checklists**: Per-task DoD items with add/remove/check/uncheck
- ✅ **Board Visualization**: Terminal-based Kanban board (`backlog board`) and web UI (`backlog browser`)
- ✅ **Git Integration**: Automatic tracking of task states across branches
- ✅ **Dependencies**: Task relationships and subtask hierarchies
- ✅ **Documentation & Decisions**: Structured docs and architectural decision records
- ✅ **Export & Reporting**: Generate markdown reports and board snapshots
- ✅ **AI-Optimized**: `--plain` flag provides clean text output for AI processing
### Why This Matters to You (AI Agent)
1. **Comprehensive system** - Full project management capabilities through CLI
2. **The CLI is the interface** - All operations go through `backlog` commands
3. **Unified interaction model** - You can use CLI for both reading (`backlog task 1 --plain`) and writing (
`backlog task edit 1`)
4. **Metadata stays synchronized** - The CLI handles all the complex relationships
### Key Understanding
- **Tasks** live in `backlog/tasks/` as `task-<id> - <title>.md` files
- **You interact via CLI only**: `backlog task create`, `backlog task edit`, etc.
- **Use `--plain` flag** for AI-friendly output when viewing/listing
- **Never bypass the CLI** - It handles Git, metadata, file naming, and relationships
---
# ⚠️ CRITICAL: NEVER EDIT TASK FILES DIRECTLY. Edit Only via CLI
**ALL task operations MUST use the Backlog.md CLI commands**
- ✅ **DO**: Use `backlog task edit` and other CLI commands
- ✅ **DO**: Use `backlog task create` to create new tasks
- ✅ **DO**: Use `backlog task edit <id> --check-ac <index>` to mark acceptance criteria
- ❌ **DON'T**: Edit markdown files directly
- ❌ **DON'T**: Manually change checkboxes in files
- ❌ **DON'T**: Add or modify text in task files without using CLI
**Why?** Direct file editing breaks metadata synchronization, Git tracking, and task relationships.
---
## 1. Source of Truth & File Structure
### 📖 **UNDERSTANDING** (What you'll see when reading)
- Markdown task files live under **`backlog/tasks/`** (drafts under **`backlog/drafts/`**)
- Files are named: `task-<id> - <title>.md` (e.g., `task-42 - Add GraphQL resolver.md`)
- Project documentation is in **`backlog/docs/`**
- Project decisions are in **`backlog/decisions/`**
### 🔧 **ACTING** (How to change things)
- **All task operations MUST use the Backlog.md CLI tool**
- This ensures metadata is correctly updated and the project stays in sync
- **Always use `--plain` flag** when listing or viewing tasks for AI-friendly text output
---
## 2. Common Mistakes to Avoid
### ❌ **WRONG: Direct File Editing**
```markdown
# DON'T DO THIS:
1. Open backlog/tasks/task-7 - Feature.md in editor
2. Change "- [ ]" to "- [x]" manually
3. Add notes or final summary directly to the file
4. Save the file
```
### ✅ **CORRECT: Using CLI Commands**
```bash
# DO THIS INSTEAD:
backlog task edit 7 --check-ac 1 # Mark AC #1 as complete
backlog task edit 7 --notes "Implementation complete" # Add notes
backlog task edit 7 --final-summary "PR-style summary" # Add final summary
backlog task edit 7 -s "In Progress" -a @agent-k # Multiple commands: change status and assign the task when you start working on the task
```
---
## 3. Understanding Task Format (Read-Only Reference)
⚠️ **FORMAT REFERENCE ONLY** - The following sections show what you'll SEE in task files.
**Never edit these directly! Use CLI commands to make changes.**
### Task Structure You'll See
```markdown
---
id: task-42
title: Add GraphQL resolver
status: To Do
assignee: [@sara]
labels: [backend, api]
---
## Description
Brief explanation of the task purpose.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 First criterion
- [x] #2 Second criterion (completed)
- [ ] #3 Third criterion
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Tests pass
- [ ] #2 Docs updated
<!-- DOD:END -->
## Implementation Plan
1. Research approach
2. Implement solution
## Implementation Notes
Progress notes captured during implementation.
## Final Summary
PR-style summary of what was implemented.
```
### How to Modify Each Section
| What You Want to Change | CLI Command to Use |
|-------------------------|----------------------------------------------------------|
| Title | `backlog task edit 42 -t "New Title"` |
| Status | `backlog task edit 42 -s "In Progress"` |
| Assignee | `backlog task edit 42 -a @sara` |
| Labels | `backlog task edit 42 -l backend,api` |
| Description | `backlog task edit 42 -d "New description"` |
| Add AC | `backlog task edit 42 --ac "New criterion"` |
| Add DoD | `backlog task edit 42 --dod "Ship notes"` |
| Check AC #1 | `backlog task edit 42 --check-ac 1` |
| Check DoD #1 | `backlog task edit 42 --check-dod 1` |
| Uncheck AC #2 | `backlog task edit 42 --uncheck-ac 2` |
| Uncheck DoD #2 | `backlog task edit 42 --uncheck-dod 2` |
| Remove AC #3 | `backlog task edit 42 --remove-ac 3` |
| Remove DoD #3 | `backlog task edit 42 --remove-dod 3` |
| Add Plan | `backlog task edit 42 --plan "1. Step one\n2. Step two"` |
| Add Notes (replace) | `backlog task edit 42 --notes "What I did"` |
| Append Notes | `backlog task edit 42 --append-notes "Another note"` |
| Add Final Summary | `backlog task edit 42 --final-summary "PR-style summary"` |
| Append Final Summary | `backlog task edit 42 --append-final-summary "Another detail"` |
| Clear Final Summary | `backlog task edit 42 --clear-final-summary` |
---
## 4. Defining Tasks
### Creating New Tasks
**Always use CLI to create tasks:**
```bash
# Example
backlog task create "Task title" -d "Description" --ac "First criterion" --ac "Second criterion"
```
### Title (one liner)
Use a clear brief title that summarizes the task.
### Description (The "why")
Provide a concise summary of the task purpose and its goal. Explains the context without implementation details.
### Acceptance Criteria (The "what")
**Understanding the Format:**
- Acceptance criteria appear as numbered checkboxes in the markdown files
- Format: `- [ ] #1 Criterion text` (unchecked) or `- [x] #1 Criterion text` (checked)
**Managing Acceptance Criteria via CLI:**
⚠️ **IMPORTANT: How AC Commands Work**
- **Adding criteria (`--ac`)** accepts multiple flags: `--ac "First" --ac "Second"` ✅
- **Checking/unchecking/removing** accept multiple flags too: `--check-ac 1 --check-ac 2` ✅
- **Mixed operations** work in a single command: `--check-ac 1 --uncheck-ac 2 --remove-ac 3` ✅
```bash
# Examples
# Add new criteria (MULTIPLE values allowed)
backlog task edit 42 --ac "User can login" --ac "Session persists"
# Check specific criteria by index (MULTIPLE values supported)
backlog task edit 42 --check-ac 1 --check-ac 2 --check-ac 3 # Check multiple ACs
# Or check them individually if you prefer:
backlog task edit 42 --check-ac 1 # Mark #1 as complete
backlog task edit 42 --check-ac 2 # Mark #2 as complete
# Mixed operations in single command
backlog task edit 42 --check-ac 1 --uncheck-ac 2 --remove-ac 3
# ❌ STILL WRONG - These formats don't work:
# backlog task edit 42 --check-ac 1,2,3 # No comma-separated values
# backlog task edit 42 --check-ac 1-3 # No ranges
# backlog task edit 42 --check 1 # Wrong flag name
# Multiple operations of same type
backlog task edit 42 --uncheck-ac 1 --uncheck-ac 2 # Uncheck multiple ACs
backlog task edit 42 --remove-ac 2 --remove-ac 4 # Remove multiple ACs (processed high-to-low)
```
### Definition of Done checklist (per-task)
Definition of Done items are a second checklist in each task. Defaults come from `definition_of_done` in `backlog/config.yml` (or Web UI Settings) and can be disabled per task.
**Managing Definition of Done via CLI:**
```bash
# Add DoD items (MULTIPLE values allowed)
backlog task edit 42 --dod "Run tests" --dod "Update docs"
# Check/uncheck DoD items by index (MULTIPLE values supported)
backlog task edit 42 --check-dod 1 --check-dod 2
backlog task edit 42 --uncheck-dod 1
# Remove DoD items by index
backlog task edit 42 --remove-dod 2
# Create without defaults
backlog task create "Feature" --no-dod-defaults
```
**Key Principles for Good ACs:**
- **Outcome-Oriented:** Focus on the result, not the method.
- **Testable/Verifiable:** Each criterion should be objectively testable
- **Clear and Concise:** Unambiguous language
- **Complete:** Collectively cover the task scope
- **User-Focused:** Frame from end-user or system behavior perspective
Good Examples:
- "User can successfully log in with valid credentials"
- "System processes 1000 requests per second without errors"
- "CLI preserves literal newlines in description/plan/notes/final summary; `\\n` sequences are not auto‑converted"
Bad Example (Implementation Step):
- "Add a new function handleLogin() in auth.ts"
- "Define expected behavior and document supported input patterns"
### Task Breakdown Strategy
1. Identify foundational components first
2. Create tasks in dependency order (foundations before features)
3. Ensure each task delivers value independently
4. Avoid creating tasks that block each other
### Task Requirements
- Tasks must be **atomic** and **testable** or **verifiable**
- Each task should represent a single unit of work for one PR
- **Never** reference future tasks (only tasks with id < current task id)
- Ensure tasks are **independent** and don't depend on future work
---
## 5. Implementing Tasks
### 5.1. First step when implementing a task
The very first things you must do when you take over a task are:
* set the task in progress
* assign it to yourself
```bash
# Example
backlog task edit 42 -s "In Progress" -a @{myself}
```
### 5.2. Review Task References and Documentation
Before planning, check if the task has any attached `references` or `documentation`:
- **References**: Related code files, GitHub issues, or URLs relevant to the implementation
- **Documentation**: Design docs, API specs, or other materials for understanding context
These are visible in the task view output. Review them to understand the full context before drafting your plan.
### 5.3. Create an Implementation Plan (The "how")
Previously created tasks contain the why and the what. Once you are familiar with that part you should think about a
plan on **HOW** to tackle the task and all its acceptance criteria. This is your **Implementation Plan**.
First do a quick check to see if all the tools that you are planning to use are available in the environment you are
working in.
When you are ready, write it down in the task so that you can refer to it later.
```bash
# Example
backlog task edit 42 --plan "1. Research codebase for references\n2Research on internet for similar cases\n3. Implement\n4. Test"
```
## 5.4. Implementation
Once you have a plan, you can start implementing the task. This is where you write code, run tests, and make sure
everything works as expected. Follow the acceptance criteria one by one and MARK THEM AS COMPLETE as soon as you
finish them.
### 5.5 Implementation Notes (Progress log)
Use Implementation Notes to log progress, decisions, and blockers as you work.
Append notes progressively during implementation using `--append-notes`:
```
backlog task edit 42 --append-notes "Investigated root cause" --append-notes "Added tests for edge case"
```
```bash
# Example
backlog task edit 42 --notes "Initial implementation done; pending integration tests"
```
### 5.6 Final Summary (PR description)
When you are done implementing a task you need to prepare a PR description for it.
Because you cannot create PRs directly, write the PR as a clean summary in the Final Summary field.
**Quality bar:** Write it like a reviewer will see it. A one‑liner is rarely enough unless the change is truly trivial.
Include the key scope so someone can understand the impact without reading the whole diff.
```bash
# Example
backlog task edit 42 --final-summary "Implemented pattern X because Reason Y; updated files Z and W; added tests"
```
**IMPORTANT**: Do NOT include an Implementation Plan when creating a task. The plan is added only after you start the
implementation.
- Creation phase: provide Title, Description, Acceptance Criteria, and optionally labels/priority/assignee.
- When you begin work, switch to edit, set the task in progress and assign to yourself
`backlog task edit <id> -s "In Progress" -a "..."`.
- Think about how you would solve the task and add the plan: `backlog task edit <id> --plan "..."`.
- After updating the plan, share it with the user and ask for confirmation. Do not begin coding until the user approves the plan or explicitly tells you to skip the review.
- Append Implementation Notes during implementation using `--append-notes` as progress is made.
- Add Final Summary only after completing the work: `backlog task edit <id> --final-summary "..."` (replace) or append using `--append-final-summary`.
## Phase discipline: What goes where
- Creation: Title, Description, Acceptance Criteria, labels/priority/assignee.
- Implementation: Implementation Plan (after moving to In Progress and assigning to yourself) + Implementation Notes (progress log, appended as you work).
- Wrap-up: Final Summary (PR description), verify AC and Definition of Done checks.
**IMPORTANT**: Only implement what's in the Acceptance Criteria. If you need to do more, either:
1. Update the AC first: `backlog task edit 42 --ac "New requirement"`
2. Or create a new follow up task: `backlog task create "Additional feature"`
---
## 6. Typical Workflow
```bash
# 1. Identify work
backlog task list -s "To Do" --plain
# 2. Read task details
backlog task 42 --plain
# 3. Start work: assign yourself & change status
backlog task edit 42 -s "In Progress" -a @myself
# 4. Add implementation plan
backlog task edit 42 --plan "1. Analyze\n2. Refactor\n3. Test"
# 5. Share the plan with the user and wait for approval (do not write code yet)
# 6. Work on the task (write code, test, etc.)
# 7. Mark acceptance criteria as complete (supports multiple in one command)
backlog task edit 42 --check-ac 1 --check-ac 2 --check-ac 3 # Check all at once
# Or check them individually if preferred:
# backlog task edit 42 --check-ac 1
# backlog task edit 42 --check-ac 2
# backlog task edit 42 --check-ac 3
# 8. Add Final Summary (PR Description)
backlog task edit 42 --final-summary "Refactored using strategy pattern, updated tests"
# 9. Mark task as done
backlog task edit 42 -s Done
```
---
## 7. Definition of Done (DoD)
A task is **Done** only when **ALL** of the following are complete:
### ✅ Via CLI Commands:
1. **All acceptance criteria checked**: Use `backlog task edit <id> --check-ac <index>` for each
2. **All Definition of Done items checked**: Use `backlog task edit <id> --check-dod <index>` for each
3. **Final Summary added**: Use `backlog task edit <id> --final-summary "..."`
4. **Status set to Done**: Use `backlog task edit <id> -s Done`
### ✅ Via Code/Testing:
5. **Tests pass**: Run test suite and linting
6. **Documentation updated**: Update relevant docs if needed
7. **Code reviewed**: Self-review your changes
8. **No regressions**: Performance, security checks pass
⚠️ **NEVER mark a task as Done without completing ALL items above**
---
## 8. Finding Tasks and Content with Search
When users ask you to find tasks related to a topic, use the `backlog search` command with `--plain` flag:
```bash
# Search for tasks about authentication
backlog search "auth" --plain
# Search only in tasks (not docs/decisions)
backlog search "login" --type task --plain
# Search with filters
backlog search "api" --status "In Progress" --plain
backlog search "bug" --priority high --plain
```
**Key points:**
- Uses fuzzy matching - finds "authentication" when searching "auth"
- Searches task titles, descriptions, and content
- Also searches documents and decisions unless filtered with `--type task`
- Always use `--plain` flag for AI-readable output
---
## 9. Quick Reference: DO vs DON'T
### Viewing and Finding Tasks
| Task | ✅ DO | ❌ DON'T |
|--------------|-----------------------------|---------------------------------|
| View task | `backlog task 42 --plain` | Open and read .md file directly |
| List tasks | `backlog task list --plain` | Browse backlog/tasks folder |
| Check status | `backlog task 42 --plain` | Look at file content |
| Find by topic| `backlog search "auth" --plain` | Manually grep through files |
### Modifying Tasks
| Task | ✅ DO | ❌ DON'T |
|---------------|--------------------------------------|-----------------------------------|
| Check AC | `backlog task edit 42 --check-ac 1` | Change `- [ ]` to `- [x]` in file |
| Add notes | `backlog task edit 42 --notes "..."` | Type notes into .md file |
| Add final summary | `backlog task edit 42 --final-summary "..."` | Type summary into .md file |
| Change status | `backlog task edit 42 -s Done` | Edit status in frontmatter |
| Add AC | `backlog task edit 42 --ac "New"` | Add `- [ ] New` to file |
---
## 10. Complete CLI Command Reference
### Task Creation
| Action | Command |
|------------------|-------------------------------------------------------------------------------------|
| Create task | `backlog task create "Title"` |
| With description | `backlog task create "Title" -d "Description"` |
| With AC | `backlog task create "Title" --ac "Criterion 1" --ac "Criterion 2"` |
| With final summary | `backlog task create "Title" --final-summary "PR-style summary"` |
| With references | `backlog task create "Title" --ref src/api.ts --ref https://github.com/issue/123` |
| With documentation | `backlog task create "Title" --doc https://design-docs.example.com` |
| With all options | `backlog task create "Title" -d "Desc" -a @sara -s "To Do" -l auth --priority high --ref src/api.ts --doc docs/spec.md` |
| Create draft | `backlog task create "Title" --draft` |
| Create subtask | `backlog task create "Title" -p 42` |
### Task Modification
| Action | Command |
|------------------|---------------------------------------------|
| Edit title | `backlog task edit 42 -t "New Title"` |
| Edit description | `backlog task edit 42 -d "New description"` |
| Change status | `backlog task edit 42 -s "In Progress"` |
| Assign | `backlog task edit 42 -a @sara` |
| Add labels | `backlog task edit 42 -l backend,api` |
| Set priority | `backlog task edit 42 --priority high` |
### Acceptance Criteria Management
| Action | Command |
|---------------------|-----------------------------------------------------------------------------|
| Add AC | `backlog task edit 42 --ac "New criterion" --ac "Another"` |
| Remove AC #2 | `backlog task edit 42 --remove-ac 2` |
| Remove multiple ACs | `backlog task edit 42 --remove-ac 2 --remove-ac 4` |
| Check AC #1 | `backlog task edit 42 --check-ac 1` |
| Check multiple ACs | `backlog task edit 42 --check-ac 1 --check-ac 3` |
| Uncheck AC #3 | `backlog task edit 42 --uncheck-ac 3` |
| Mixed operations | `backlog task edit 42 --check-ac 1 --uncheck-ac 2 --remove-ac 3 --ac "New"` |
### Task Content
| Action | Command |
|------------------|----------------------------------------------------------|
| Add plan | `backlog task edit 42 --plan "1. Step one\n2. Step two"` |
| Add notes | `backlog task edit 42 --notes "Implementation details"` |
| Add final summary | `backlog task edit 42 --final-summary "PR-style summary"` |
| Append final summary | `backlog task edit 42 --append-final-summary "More details"` |
| Clear final summary | `backlog task edit 42 --clear-final-summary` |
| Add dependencies | `backlog task edit 42 --dep task-1 --dep task-2` |
| Add references | `backlog task edit 42 --ref src/api.ts --ref https://github.com/issue/123` |
| Add documentation | `backlog task edit 42 --doc https://design-docs.example.com --doc docs/spec.md` |
### Multi‑line Input (Description/Plan/Notes/Final Summary)
The CLI preserves input literally. Shells do not convert `\n` inside normal quotes. Use one of the following to insert real newlines:
- Bash/Zsh (ANSI‑C quoting):
- Description: `backlog task edit 42 --desc $'Line1\nLine2\n\nFinal'`
- Plan: `backlog task edit 42 --plan $'1. A\n2. B'`
- Notes: `backlog task edit 42 --notes $'Done A\nDoing B'`
- Append notes: `backlog task edit 42 --append-notes $'Progress update line 1\nLine 2'`
- Final summary: `backlog task edit 42 --final-summary $'Shipped A\nAdded B'`
- Append final summary: `backlog task edit 42 --append-final-summary $'Added X\nAdded Y'`
- POSIX portable (printf):
- `backlog task edit 42 --notes "$(printf 'Line1\nLine2')"`
- PowerShell (backtick n):
- `backlog task edit 42 --notes "Line1`nLine2"`
Do not expect `"...\n..."` to become a newline. That passes the literal backslash + n to the CLI by design.
Descriptions support literal newlines; shell examples may show escaped `\\n`, but enter a single `\n` to create a newline.
### Implementation Notes Formatting
- Keep implementation notes concise and time-ordered; focus on progress, decisions, and blockers.
- Use short paragraphs or bullet lists instead of a single long line.
- Use Markdown bullets (`-` for unordered, `1.` for ordered) for readability.
- When using CLI flags like `--append-notes`, remember to include explicit
newlines. Example:
```bash
backlog task edit 42 --append-notes $'- Added new API endpoint\n- Updated tests\n- TODO: monitor staging deploy'
```
### Final Summary Formatting
- Treat the Final Summary as a PR description: lead with the outcome, then add key changes and tests.
- Keep it clean and structured so it can be pasted directly into GitHub.
- Prefer short paragraphs or bullet lists and avoid raw progress logs.
- Aim to cover: **what changed**, **why**, **user impact**, **tests run**, and **risks/follow‑ups** when relevant.
- Avoid single‑line summaries unless the change is truly tiny.
**Example (good, not rigid):**
```
Added Final Summary support across CLI/MCP/Web/TUI to separate PR summaries from progress notes.
Changes:
- Added `finalSummary` to task types and markdown section parsing/serialization (ordered after notes).
- CLI/MCP/Web/TUI now render and edit Final Summary; plain output includes it.
Tests:
- bun test src/test/final-summary.test.ts
- bun test src/test/cli-final-summary.test.ts
```
### Task Operations
| Action | Command |
|--------------------|----------------------------------------------|
| View task | `backlog task 42 --plain` |
| List tasks | `backlog task list --plain` |
| Search tasks | `backlog search "topic" --plain` |
| Search with filter | `backlog search "api" --status "To Do" --plain` |
| Filter by status | `backlog task list -s "In Progress" --plain` |
| Filter by assignee | `backlog task list -a @sara --plain` |
| Archive task | `backlog task archive 42` |
| Demote to draft | `backlog task demote 42` |
---
## Common Issues
| Problem | Solution |
|----------------------|--------------------------------------------------------------------|
| Task not found | Check task ID with `backlog task list --plain` |
| AC won't check | Use correct index: `backlog task 42 --plain` to see AC numbers |
| Changes not saving | Ensure you're using CLI, not editing files |
| Metadata out of sync | Re-edit via CLI to fix: `backlog task edit 42 -s <current-status>` |
---
## Remember: The Golden Rule
**🎯 If you want to change ANYTHING in a task, use the `backlog task edit` command.**
**📖 Use CLI to read tasks, exceptionally READ task files directly, never WRITE to them.**
Full help available: `backlog --help`
================================================
FILE: .claude/commands/backlog_discord.md
================================================
# /backlog_discord — Respond to backlog commands from Discord
Monitor a Discord channel for backlog-related requests, execute them via the Backlog MCP, and post results back to Discord.
## Configuration
- **Bot channel**: `#dev-sat-mqtt` — channel ID `1105556725714649128`
- **Timestamp file**: `.claude/discord_backlog_last_checked.txt`
- **Bot user ID to ignore**: `384411356616720384` (maintainer, not the bot itself — adjust if needed)
## Workflow
### Phase 1: Connect and read new messages
The Discord MCP server is the ExilProductions fork (`discord-mcp-exil`), started as a stdio process by Claude Code via `uv run python -m discord_mcp.main --transport stdio`. There is **no separate login step** — the `DISCORD_TOKEN` is injected via the MCP server config. The first tool call doubles as the connection check. **Tool namespace is `mcp__discord-mcp__*`.** Always use these MCP tools, never curl or direct Discord API calls (curl is fine for the CDN attachment downloads in Phase 1b — see below).
1. **Read the last-checked timestamp** from `.claude/discord_backlog_last_checked.txt`. If the file does not exist, default to the last 1 hour.
2. **Read messages** from the bot channel using `mcp__discord-mcp__fetch_channel_history` with `channel_id="1105556725714649128"` and `limit=30`. The response payload includes per-message **attachment metadata** (attachment ID, filename, MIME type, size, signed CDN URL).
3. **Filter** to messages posted after the last-checked timestamp.
4. **Ignore** messages sent by bots (including yourself).
5. **Save the current timestamp** to `.claude/discord_backlog_last_checked.txt`.
### Phase 1b: Fetch attachment contents (when a relevant message has them)
If a backlog-actionable message carries attachments, fetch and inspect them. **The bot is no longer blind to logs and screenshots.** Stop replying with "I cannot read attachments through the bot, only message text" — that limitation is obsolete.
| Type | Goal | How |
|---|---|---|
| Text (`.txt`, `.log`, `.json`, `.md`) | AI-summarised quick read | `WebFetch(url, prompt="…")` — small model returns processed answers, not always verbatim |
| Text (`.txt`, `.log`, `.json`, `.md`) | Verbatim, line-precise diagnosis or `Grep` over content | PowerShell download → `Read` / `Grep` on the local file |
| Image (`.png`, `.jpg`, `.webp`) | See the screenshot, extract on-screen text | PowerShell download → `Read` on the **Windows path** |
**Download recipe (Windows-safe — do NOT use Git-Bash `/tmp/`, the Read tool cannot resolve those paths):**
```powershell
$dir = "$env:TEMP\discord-attach"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
Invoke-WebRequest -Uri "<signed CDN URL from message>" -OutFile "$dir\<filename>" -UseBasicParsing
```
Then call `Read` with the full Windows path, e.g. `C:\Users\rvdbr\AppData\Local\Temp\discord-attach\<filename>`. Discord CDN URLs are signed with `ex=<hex-epoch>` and expire ~7 days after the message was posted — if 403, fetch a fresh signed URL via `mcp__discord-mcp__fetch_channel_history` (Discord rolls a new one each call).
When the bot uses an attachment to inform a reply, name the finding briefly so the reporter knows the bot actually read their evidence.
### Phase 2: Identify actionable messages
Scan each new message for backlog-related intent. A message is actionable if it:
- Mentions the bot AND asks about tasks, backlog, status, assignments, etc.
- Contains an explicit command pattern (see below)
- Is a follow-up reply in a thread where the bot previously responded about a task
**Supported intents** (match flexibly — these are examples, not exact strings):
| Intent | Example messages |
|--------|-----------------|
| List tasks | "list tasks", "what's on the board?", "show backlog", "tasks in progress" |
| Show task | "show task 42", "details on task 42", "what's task 42 about?" |
| Task status | "status of task 42", "is task 42 done?" |
| Update status | "move task 42 to in progress", "mark task 42 done" |
| Assign task | "assign task 42 to @sara" |
| Add note | "add note to task 42: started refactoring" |
| Search | "find tasks about mqtt", "search auth" |
| Board summary | "board", "show the board", "kanban" |
| Help | "help", "how does this work?", "what can you do?", "commands" |
If a message is not backlog-related, skip it entirely — do not respond.
### Phase 3: Execute and respond
For each actionable message, do the following:
1. **Parse the intent** and extract parameters (task ID, status, search query, etc.)
2. **Execute the corresponding backlog operation**:
| Intent | Backlog command |
|--------|----------------|
| List tasks | `backlog task list --plain` (optionally with `-s "Status"`) |
| Show task | `backlog task <id> --plain` |
| Update status | `backlog task edit <id> -s "New Status"` |
| Assign | `backlog task edit <id> -a @name` |
| Add note | `backlog task edit <id> --append-notes "note text"` |
| Search | `backlog search "query" --plain` |
| Board summary | `backlog board --plain` |
| Help | No backlog command needed — respond with the help message (see below) |
3. **Format the response for Discord**. Keep it readable:
- Use Discord markdown (bold, code blocks, bullet lists)
- For task lists: show ID, title, status, assignee — one line per task
- For task details: show title, status, assignee, description, and acceptance criteria
- For board view: group tasks by status column
- Keep responses under 1900 characters (Discord limit is 2000). If longer, summarize and offer to show more.
4. **Post the response** to the same channel using `mcp__discord-mcp__send_message_to_channel` with `channel_id="1105556725714649128"` and `content="<reply text>"`.
### Phase 4: Handle conversational follow-ups
If a message is a **reply in a thread** where the bot previously posted task details, treat it as a contextual update:
- "mark AC 1 done" → `backlog task edit <id from context> --check-ac 1`
- "assign this to @dev" → `backlog task edit <id from context> -a @dev`
- "add a note: fixed the bug" → `backlog task edit <id from context> --append-notes "fixed the bug"`
- "what are the open ACs?" → re-fetch and show unchecked acceptance criteria
Use the task ID from the earlier message in the thread for context.
## Response formatting guidelines
### Task list response
```
**Backlog Tasks** (In Progress)
- **#7** Setup MQTT reconnect — `In Progress` (@rob)
- **#12** Add REST endpoint for sensors — `In Progress` (@sara)
- **#15** Fix watchdog timeout — `In Progress` (unassigned)
_3 tasks shown. Say "show task <id>" for details._
```
### Task detail response
```
**Task #7 — Setup MQTT reconnect**
**Status:** In Progress | **Assignee:** @rob | **Priority:** high
**Description:**
Implement automatic MQTT reconnection with exponential backoff.
**Acceptance Criteria:**
- [ ] #1 Reconnect within 30s of disconnect
- [x] #2 Exponential backoff (1s, 2s, 4s, max 60s)
- [ ] #3 Log reconnection attempts via DebugTln
```
### Update confirmation
```
Done — Task #7 status changed to **Done**.
```
### Help response
```
**Backlog Bot — How it works**
I manage the project task board. You can ask me things in plain language or use short commands. Here's what I can do:
**View tasks**
- `list tasks` — show all tasks
- `list tasks in progress` — filter by status (To Do, In Progress, Done)
- `show task 7` — full details for a specific task
- `board` — Kanban-style overview
**Search**
- `search mqtt` — find tasks mentioning a topic
- `find tasks about reconnect` — same thing, natural language
**Update tasks**
- `move task 7 to in progress` — change status
- `assign task 7 to @rob` — assign someone
- `mark task 7 done` — mark as done
- `add note to task 7: fixed the timeout issue` — append a note
**In a thread** (after I show a task):
- `mark AC 1 done` — check acceptance criterion #1
- `what are the open ACs?` — show remaining criteria
- `assign this to @sara` — assign the task from context
Just ask — I understand plain language too!
```
## Important rules
- **Never modify tasks without explicit user request** — read operations are safe, write operations need clear intent
- **Be concise** — Discord is chat, not a document viewer
- **Respect the backlog CLI** — always use CLI commands, never edit task files directly
- **If a command is ambiguous**, ask for clarification in the Discord response rather than guessing
- **If backlog CLI returns an error**, post a friendly error message (e.g. "Task 99 not found. Use `list tasks` to see available tasks.")
- **Skip non-backlog messages entirely** — don't respond to general chat
================================================
FILE: .claude/commands/check_otgw_issues.md
================================================
# /check_otgw_issues — Monitor Discord, GitHub and Tweakers for user-reported issues
Scan the OTGW-firmware Discord server **and** GitHub issue tracker for new issues reported by users since the last check, analyze them, propose a fix, and implement it on a dedicated branch after developer approval.
## Workflow
Follow these phases strictly and in order.
### Phase 1: Read Discord messages
The Discord MCP server is the ExilProductions fork (`discord-mcp-exil`), started as a stdio process by Claude Code via `uv run python -m discord_mcp.main --transport stdio`. There is **no separate login step** — the `DISCORD_TOKEN` is injected via the MCP server config. The first tool call doubles as the connection check. **Tool namespace is `mcp__discord-mcp__*`.** Always use these MCP tools, never curl or direct Discord API calls (curl is fine for the CDN attachment downloads in Phase 1d — see below).
1. **Read the last-checked timestamp** from `.claude/discord_last_checked.txt`. If the file does not exist, default to messages from the last 7 days.
2. **Read messages** from the following channels using `mcp__discord-mcp__fetch_channel_history` with `limit=50`:
- `#beta-testing` — `channel_id="914498730001072149"`
- `#devs-esp-firmware` — `channel_id="924989767966425158"`
- `#english-support` — `channel_id="931267109726593116"`
- `#nederlandse-ondersteuning` — `channel_id="815561033036333076"`
The response payload includes per-message **attachment metadata** (attachment ID, filename, MIME type, size, signed CDN URL). There is no separate `get_attachment` tool in this server — use the CDN URL from the message payload directly.
3. **Filter messages** to only those posted after the last-checked timestamp.
4. **Exclude** messages from the maintainer (user ID `384411356616720384`, username `number3nl`) and bot accounts.
5. **Save the current timestamp** to `.claude/discord_last_checked.txt` for next run.
### Phase 1b: Fetch GitHub issues
1. **Read the last-checked GitHub timestamp** from `.claude/github_last_checked.txt`. If the file does not exist, default to the last 7 days.
2. **List new open GitHub issues** created or updated since the last check:
```bash
gh issue list --repo rvdbreemen/OTGW-firmware --state open --limit 50 --json number,title,body,createdAt,updatedAt,labels,author,url
```
3. **Filter** to issues created or updated after the last-checked GitHub timestamp.
4. **Exclude** issues authored by the maintainer (`rvdbreemen`) or bot accounts (login contains `[bot]`).
5. **For each new issue**, fetch its comments for full context:
```bash
gh issue view <number> --repo rvdbreemen/OTGW-firmware --json number,title,body,comments,labels,author,url
```
6. **Save the current timestamp** to `.claude/github_last_checked.txt` for next run.
### Phase 1c: Fetch Tweakers forum posts
1. **Read the last-checked Tweakers timestamp** from `.claude/tweakers_last_checked.txt`. If the file does not exist, default to posts from the last 7 days.
2. **Fetch the Tweakers RSS feed** using curl and pipe through Python to strip surrogate characters before parsing (WebFetch is blocked by Tweakers; direct XML parse of the raw bytes fails on Windows with Python 3.14+ due to surrogate chars `\udc8d` in the feed):
```bash
curl -s --max-time 10 -A "Mozilla/5.0" "https://gathering.tweakers.net/rss/list_messages/1653967" > .tmp/tweakers_rss.bin
python3 -c "
data = open('.tmp/tweakers_rss.bin', 'rb').read().decode('utf-8', errors='replace')
open('.tmp/tweakers_rss.xml', 'w', encoding='utf-8').write(data)
"
```
Then parse `.tmp/tweakers_rss.xml` as UTF-8 text. The `errors='replace'` step ensures surrogate bytes become `\ufffd` (replacement character) instead of raising `UnicodeEncodeError`.
3. **Parse the RSS XML** to extract individual `<item>` elements with:
- `<dc:creator>` — post author (username)
- `<pubDate>` — post timestamp (RFC 822 format)
- `<description>` or `<content:encoded>` — post content
- `<link>` — direct permalink to the post
4. **Filter** to only posts with `<pubDate>` newer than the last-checked Tweakers timestamp.
5. **Exclude** posts by the maintainer (Tweakers username `number3` or `rvdbreemen`) or purely social/off-topic messages.
6. **Save the current timestamp** to `.claude/tweakers_last_checked.txt` for next run.
7. **Note**: Tweakers is a Dutch forum — posts will be in Dutch. Summarize them in English for the triage list.
### Phase 1d: Fetch attachment contents (logs, screenshots)
Discord support channels (especially `#beta-testing`) carry the bulk of the **diagnostic evidence** as attached files: telnet logs, MQTT captures, screenshots of failing UIs. **Triage without reading the attachment is triage on partial information.** Earlier replies that said "I cannot read attachments through the bot, only message text" are obsolete; the bot can now both fetch and analyse them.
For every message that survives Phase 1 filtering and carries one or more attachments, fetch the contents before drafting the triage entry. Same applies to GitHub issue bodies/comments that link CDN-hosted screenshots or paste log gists, and to Tweakers posts with image links.
| Type | Goal | How |
|---|---|---|
| Text (`.txt`, `.log`, `.json`, `.md`) | AI-summarised quick read | `WebFetch(url, prompt="…")` — small model returns processed/summarised answers, not always verbatim |
| Text (`.txt`, `.log`, `.json`, `.md`) | Verbatim, line-precise diagnosis or `Grep` over content | PowerShell download → `Read` / `Grep` on the local file |
| Image (`.png`, `.jpg`, `.webp`) | See the screenshot, extract on-screen text or layout | PowerShell download → `Read` on the **Windows path** (Read can open images in Claude Code) |
**Download recipe (Windows-safe — do NOT use Git-Bash `/tmp/`, the Read tool cannot resolve those paths):**
```powershell
$dir = "$env:TEMP\otgw-issues-attach"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
Invoke-WebRequest -Uri "<signed CDN URL from message>" -OutFile "$dir\<filename>" -UseBasicParsing
```
Then call `Read` with the full Windows path, e.g. `C:\Users\rvdbr\AppData\Local\Temp\otgw-issues-attach\<filename>`. To grep a long log: `Grep` on that same path.
**Caveats:**
- Discord CDN URLs are signed with `ex=<hex-epoch>` and expire roughly 7 days after the message was posted. If the download returns 403, request a fresh signed URL via `mcp__discord-mcp__fetch_channel_history` (Discord rolls a new one each call) or ask the poster to re-share.
- `WebFetch` runs the content through a small AI model — for **forensic / line-precise** log diagnosis prefer the download-and-`Grep` route. Use `WebFetch` only for the "what does this log roughly show" first-pass question.
- Windows `$env:TEMP` rotates on its own; no clean-up needed. Don't commit downloaded attachments anywhere.
**Triage output (Phase 2) MUST include a one-line attachment summary** for each item that carried evidence: e.g. `"Attached telnet log (58 KB, build 168bd9e): five clean SAT cycles, then stale-temp fallback at 20:33:54 driving room=0.0 → CS=62.0"`. This is what makes the triage actionable rather than cosmetic.
### Phase 2: Identify and triage issues
Combine Discord messages, GitHub issues, and Tweakers forum posts into a single triage list.
1. **Classify each item** as one of: bug report, feature request, question, general discussion, or not relevant.
2. **Focus only on bug reports and actionable issues.** Skip feature requests, questions, and general chat.
3. If **no new issues** are found from any source, report "No new issues since last check" and stop.
4. **Present a numbered list** of identified issues to the developer. For each item include:
- **Source**: Discord (channel name), GitHub (issue number + link, e.g. `GitHub #542`), or Tweakers (post link)
- **Reporter**: Discord username (strip trailing 4-digit suffixes), GitHub username, or Tweakers username
- **Summary**: short description of the issue (in English, even if the original post was in Dutch)
- **Excerpt**: relevant message or issue body snippet, plus any key replies/comments
5. **Cross-reference**: if the same issue appears in multiple sources, merge them into one entry and note all sources.
### Phase 2b: Backlog triage for every identified issue
For **every** bug report or actionable issue found in Phase 2 — regardless of whether the developer selects it for immediate work — do the following:
1. **Check the backlog** for an existing task covering this issue:
```bash
backlog search "<short issue description>" --plain
```
2. **If no task exists**, create one immediately:
```bash
backlog task create "Fix: <short description>" \
-d "<what the issue is, who reported it, where>" \
--ac "<testable acceptance criterion>" \
-l "bug,needs-info" --priority medium
```
Add a reference to the source (GitHub issue URL, Discord channel + username):
```bash
backlog task edit <id> --ref "https://github.com/rvdbreemen/OTGW-firmware/issues/NNN"
backlog task edit <id> --ref "Discord #channel, user <username>, <date>"
```
3. **Assess information readiness.** A task is ready to pick up only when ALL of the following are available:
- A reproducible description of the problem
- Enough context to identify the likely code area (logs, MQTT traces, or telnet output)
- At least one reporter who can validate a fix
4. **If information is insufficient**, keep the task in `To Do` with label `needs-info` and add a note:
```bash
backlog task edit <id> --append-notes "Waiting for: <what is missing, e.g. telnet logs from reporter>"
```
**Do NOT move such a task to In Progress.** It stays `To Do / needs-info` until the missing information arrives.
5. **If information is sufficient**, remove the `needs-info` label and present the task to the developer as a candidate for work.
6. **When checking issues in future runs**, always cross-reference new messages against open `needs-info` tasks in the backlog. If new information has arrived (e.g., logs shared on Discord), update the task notes and re-assess readiness:
```bash
backlog task edit <id> --append-notes "<date>: Reporter shared telnet logs. Ready to investigate."
backlog task edit <id> -l "bug" # remove needs-info once sufficient
```
**CHECKPOINT: Ask the developer which issue(s) to work on. Do not proceed until they select one.**
### Phase 3: Analyze the selected issue
1. **Read all related messages** in the thread/conversation for full context.
2. **Search the codebase** for relevant code paths related to the reported issue.
3. **Draft a suggested solution** with:
- Root cause analysis (what's likely going wrong)
- Proposed fix (which files to change, what to change)
- Potential risks or side effects
- Testing approach
4. **Present the plan** to the developer.
**CHECKPOINT: Wait for developer approval of the plan. Do not write any code until approved. Adjust the plan if the developer requests changes.**
### Phase 4: Create branch and bump version
1. **Ensure working tree is clean** — check `git status`. If there are uncommitted changes, warn the developer and stop.
2. **Derive a short kebab-case description** from the issue (max 5 words, e.g., `mqtt-reconnect-crash`, `ot-log-missing-data`).
3. **Create and checkout a new branch** from `dev`:
```
git checkout dev
git pull
git checkout -b fix-issue-<short-description>
```
4. **Bump the patch version** in `src/OTGW-firmware/version.h`:
- Increment `_VERSION_PATCH` by 1 (e.g., 2 -> 3)
- Update ALL related defines consistently:
- `_SEMVER_CORE` — `"X.Y.Z"`
- `_SEMVER_BUILD` — `"X.Y.Z+BUILD"`
- `_SEMVER_GITHASH` — `"X.Y.Z+HASH"`
- `_SEMVER_FULL` — `"X.Y.Z-beta+HASH"`
- `_SEMVER_NOBUILD` — `"X.Y.Z-beta (DATE)"`
- `_VERSION` — `"X.Y.Z-beta+HASH (DATE)"`
- Keep `_VERSION_BUILD`, `_VERSION_GITHASH`, `_VERSION_DATE`, `_VERSION_TIME` unchanged (the build system updates these)
5. **Run `python scripts/autoinc-semver.py`** from the `src/OTGW-firmware/` directory to update build number, githash, and timestamps:
```
python scripts/autoinc-semver.py src/OTGW-firmware --filename version.h --update-all --increment-build 0
```
Using `--increment-build 0` ensures only the derived strings and hash are refreshed without bumping the build number again.
6. **Commit the version bump**:
```
git add src/OTGW-firmware/version.h src/OTGW-firmware/data/version.hash
git commit -m "chore: bump version to vX.Y.Z-beta for fix-issue-<description>"
```
### Phase 5: Implement the fix
1. **Follow the approved plan** from Phase 3.
2. **Respect all project coding rules** from CLAUDE.md:
- Use PROGMEM (`F()`, `PSTR()`, `snprintf_P`) for all string literals
- No `String` class in hot paths
- Use `char[]` buffers with `strlcpy`, `strncat`, `snprintf_P`
- Never write to Serial (use `DebugTln()`, `DebugTf()`)
- Use `addOTWGcmdtoqueue()` for OTGW commands
- Feed watchdog in long loops: `feedWatchDog()`
- Validate buffer sizes before string operations
3. **Run the build** to verify compilation:
```
python build.py --firmware
```
4. **Run the evaluator** to check code quality:
```
python evaluate.py --quick
```
5. **Fix any build or evaluation errors** before proceeding.
### Phase 6: Report results
1. **Commit all changes** with a descriptive message referencing the source issue:
- If the issue originated from GitHub, include `Fixes #<number>` (or `Refs #<number>` if not fully resolved) in the commit message body so GitHub auto-closes or links the issue.
- If the issue originated from Discord only, describe it in plain text.
- Example: `fix: resolve MQTT reconnect crash after router reboot\n\nFixes #542`
2. **Present a summary** to the developer:
- What was the issue (source: Discord channel or GitHub #NNN + link)
- What was changed (files modified, approach taken)
- Build status (pass/fail)
- Evaluation status (pass/fail)
- Branch name for review
- Any follow-up items or risks
3. **Do NOT push** the branch — let the developer review first.
## Important rules
- **Never skip a checkpoint** — always wait for developer approval
- **Never push to remote** without explicit developer permission
- **Never force-push** or use destructive git commands
- **All code changes must follow OTGW-firmware coding rules** (PROGMEM, no String, etc.)
- **All release-related text must be in English** (international audience)
- **Strip Discord username suffixes** for display (4-digit trailing numbers)
- **GitHub issue numbers** must be referenced in commit messages when fixing a GitHub-tracked issue (`Fixes #NNN`)
- **Tweakers posts are in Dutch** — always summarize them in English for the triage list and any developer-facing output
- **Tweakers maintainer username** is `rvdbreemen` — exclude posts by this account from new issue detection
================================================
FILE: .claude/docs/discord-backlog-bridge.md
================================================
# Discord ↔ Backlog.md Bridge — Setup & Operations Guide
Handoff document for any Claude Code instance that needs to operate the Discord-Backlog bridge for the OTGW-firmware project.
## What This Is
A Claude Code–powered bot that monitors a Discord channel for task-related messages, queries the Backlog.md task board, and posts responses back to Discord. No custom bot code — it runs entirely through Claude Code with two MCP servers.
## Architecture
```
Discord channel
↕ (mcp-discord MCP server)
Claude Code session
↕ (backlog MCP server)
Backlog.md task files (backlog/tasks/)
```
Claude Code acts as the glue: it reads Discord messages, interprets intent, runs backlog CLI commands, and posts formatted results back to Discord.
## Prerequisites
### 1. Node.js packages
```bash
npm install -g mcp-discord # or use npx (already configured)
npm install -g backlog-md # the Backlog.md CLI
```
Verify both work:
```bash
backlog --help
npx mcp-discord --help
```
### 2. Discord Bot Token
You need a Discord bot token with these permissions:
- Read Messages / View Channels
- Send Messages
- Read Message History
The token is passed via environment variable `DISCORD_TOKEN`. In the MCP config it uses a prompt input so you'll be asked on startup.
### 3. Discord Channel ID
You need the numeric channel ID for the channel the bot monitors. To get it:
1. Discord Settings → Advanced → enable **Developer Mode**
2. Right-click the target channel → **Copy Channel ID**
Currently configured channel: `#devs-esp-firmware` — `924989767966425158`
**TODO**: Update to `#dev-sat-mqtt` once the channel ID is provided.
### 4. MCP Server Configuration
The MCP servers are configured in `.claude/.vscode/mcp.json`:
```json
{
"inputs": [
{
"type": "promptString",
"id": "discord-token",
"description": "Discord Bot Token",
"password": true
}
],
"servers": {
"backlog": {
"type": "stdio",
"command": "backlog",
"args": ["mcp", "start"],
"env": {
"BACKLOG_CWD": "${workspaceFolder}"
}
},
"discord": {
"type": "stdio",
"command": "npx",
"args": ["mcp-discord"],
"env": {
"DISCORD_TOKEN": "${input:discord-token}"
}
}
}
}
```
### 5. Permissions
In `.claude/settings.json`, the Discord MCP tools and Bash are pre-allowed:
```json
{
"permissions": {
"allow": [
"mcp__discord__*",
"Bash(*)"
]
}
}
```
## How to Run
### One-shot (check once and respond)
```
/backlog_discord
```
### Continuous polling
```
/loop 2m /backlog_discord
```
This runs `/backlog_discord` every 2 minutes. Adjust the interval as needed (e.g., `5m` for less frequent checks).
## How It Works — Step by Step
When `/backlog_discord` runs, Claude Code does the following:
1. **Login** to Discord via `mcp__discord__discord_login`
2. **Read timestamp** from `.claude/discord_backlog_last_checked.txt` (tracks what's already been processed)
3. **Fetch messages** from the configured channel via `mcp__discord__discord_read_messages`
4. **Filter** to new messages only (after the stored timestamp), ignoring bot messages
5. **Classify** each message — is it a backlog command, help request, or unrelated chat?
6. **Execute** the matching backlog CLI command for each actionable message
7. **Format** the result as Discord-friendly markdown
8. **Post** the response back to Discord via `mcp__discord__discord_send_message`
9. **Update** the timestamp file
## Key MCP Tools
### Discord MCP (`mcp-discord`)
| Tool | Purpose |
|------|---------|
| `mcp__discord__discord_login` | Authenticate the bot |
| `mcp__discord__discord_read_messages` | Read messages from a channel (params: channel_id, limit) |
| `mcp__discord__discord_send_message` | Post a message to a channel (params: channel_id, content) |
### Backlog MCP (`backlog mcp start`)
The backlog MCP exposes the same operations as the `backlog` CLI. Key commands:
| Operation | CLI equivalent |
|-----------|---------------|
| List tasks | `backlog task list --plain` |
| Show task | `backlog task <id> --plain` |
| Edit task | `backlog task edit <id> -s "Status"` |
| Search | `backlog search "query" --plain` |
| Board view | `backlog board --plain` |
Always use `--plain` flag for AI-readable output.
## Supported Discord Commands
Users in the channel can type these (matched flexibly, not exact strings):
| What they type | What happens |
|----------------|-------------|
| `list tasks` | Shows all tasks with ID, title, status, assignee |
| `list tasks in progress` | Filtered by status |
| `show task 7` | Full task details including ACs |
| `board` | Kanban-style board summary |
| `search mqtt` | Fuzzy search across tasks |
| `move task 7 to done` | Updates task status |
| `assign task 7 to @rob` | Assigns a task |
| `add note to task 7: details here` | Appends implementation note |
| `help` | Shows the help message with all commands |
### Thread follow-ups
When the bot posts task details, users can reply in the thread with contextual commands:
- `mark AC 1 done` — checks acceptance criterion #1 on the task from the parent message
- `what are the open ACs?` — re-fetches and shows unchecked criteria
- `assign this to @sara` — assigns without repeating the task ID
## File Layout
```
.claude/
├── commands/
│ ├── backlog_discord.md # The slash command (full workflow spec)
│ └── check_discord_issues.md # Separate: monitors for bug reports
├── docs/
│ └── discord-backlog-bridge.md # This document
├── discord_backlog_last_checked.txt # Timestamp tracker (auto-managed)
├── discord_last_checked.txt # Used by check_discord_issues
├── settings.json # Permissions config
└── .vscode/
└── mcp.json # MCP server definitions
```
## Changing the Channel
To point the bot at a different Discord channel:
1. Get the new channel ID (Developer Mode → right-click → Copy Channel ID)
2. Edit `.claude/commands/backlog_discord.md`
3. Replace the channel ID in two places:
- Line ~7: `**Bot channel**: #channel-name — channel ID \`NEW_ID\``
- Line ~17: `mcp__discord__discord_read_messages` with channel ID `NEW_ID`
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Bot doesn't respond | Check that `/loop` is running, or run `/backlog_discord` manually |
| "Not logged in" errors | The Discord token may have expired — restart the Claude Code session to re-enter it |
| Bot responds to old messages | Delete `.claude/discord_backlog_last_checked.txt` and restart — it will default to the last 1 hour |
| Backlog commands fail | Verify `backlog` CLI is installed: `backlog --help` |
| Messages are being skipped | The bot ignores non-backlog messages by design. Users must mention tasks, status, or use known command patterns |
| Response too long for Discord | The command limits responses to 1900 chars. If data is larger, it summarizes and offers "say show task X for details" |
## Important Rules for the Operating Instance
- **Never edit task files directly** — always use `backlog` CLI commands
- **Never modify tasks without explicit user request** — reads are safe, writes need clear intent
- **Be concise** — Discord is chat, keep responses short and scannable
- **Skip non-backlog messages** — don't respond to general conversation
- **If ambiguous**, ask for clarification in the Discord response rather than guessing
- **Post friendly errors** — e.g. "Task 99 not found. Use `list tasks` to see available tasks."
================================================
FILE: .claude/settings.20260421_085354.bak
================================================
{
"permissions": {
"allow": [
"mcp__discord__*",
"Bash(*)"
]
},
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "HOOK_DATA=$(cat); SESSION_ID=$(echo \"$HOOK_DATA\" | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json,re; s=json.load(sys.stdin).get('session_id',''); print(re.sub(r'[^A-Za-z0-9_-]','_',s))\" 2>/dev/null); TRANSCRIPT=$(echo \"$HOOK_DATA\" | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; { cozempic digest inject ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest inject ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; [ -n \"$SESSION_ID\" ] && (export COZEMPIC_NO_GLOBAL_INIT=1; if command -v flock >/dev/null 2>&1; then flock -n 9 || exit 0; fi; PRE=$(cozempic --version 2>/dev/null | awk '/^cozempic /{print $2; exit}'); { uv pip install --upgrade cozempic --quiet 2>/dev/null || pip install --upgrade cozempic --quiet --disable-pip-version-check 2>/dev/null; } && POST=$(cozempic --version 2>/dev/null | awk '/^cozempic /{print $2; exit}') && [ -n \"$POST\" ] && [ \"$PRE\" != \"$POST\" ] && { cozempic guard --reload-self ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --reload-self ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; }; { cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; ) 9>\"/tmp/cozempic_hook_${SESSION_ID:0:12}.lock\" >/dev/null 2>&1 & echo 'Cozempic: guard active' # cozempic-hook-schema=v4"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 -c \"import sys,json; d=json.load(sys.stdin); f=d.get('tool_input',{}).get('file_path','') or d.get('tool_response',{}).get('filePath',''); exit(0 if f.endswith(('.ino','.cpp','.h')) else 1)\" && cd 'D:/Users/Robert/Documents/GitHub/RvdB/OTGW-firmware' && python evaluate.py --quick 2>/dev/null || true",
"timeout": 30,
"statusMessage": "Evaluating code quality..."
}
]
},
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "{ cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true # cozempic-hook-schema=v4"
}
]
},
{
"matcher": "TaskCreate|TaskUpdate",
"hooks": [
{
"type": "command",
"command": "{ cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true # cozempic-hook-schema=v4"
}
]
},
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "{ cozempic remind 2>/dev/null || python3 -m cozempic remind 2>/dev/null; } || true # cozempic-hook-schema=v4"
}
]
}
],
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "TRANSCRIPT=$(cat | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true; { cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; echo 'Cozempic: context checkpointed' # cozempic-hook-schema=v4"
}
]
}
],
"PostCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "{ cozempic post-compact 2>/dev/null || python3 -m cozempic post-compact 2>/dev/null; } || true; { cozempic digest inject 2>/dev/null || python3 -m cozempic digest inject 2>/dev/null; } || true; echo 'Cozempic: context recovered' # cozempic-hook-schema=v4"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "TRANSCRIPT=$(cat | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true; { cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true # cozempic-hook-schema=v4"
}
]
}
]
}
}
================================================
FILE: .claude/settings.json
================================================
{
"permissions": {
"allow": [
"mcp__discord__*",
"Bash(*)"
]
},
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "HOOK_DATA=$(cat); SESSION_ID=$(echo \"$HOOK_DATA\" | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json,re; s=json.load(sys.stdin).get('session_id',''); print(re.sub(r'[^A-Za-z0-9_-]','_',s))\" 2>/dev/null); TRANSCRIPT=$(echo \"$HOOK_DATA\" | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; { cozempic digest inject ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest inject ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; [ -n \"$SESSION_ID\" ] && (export COZEMPIC_NO_GLOBAL_INIT=1; if command -v flock >/dev/null 2>&1; then flock -n 9 || exit 0; fi; PRE=$(cozempic --version 2>/dev/null | awk '/^cozempic /{print $2; exit}'); { uv pip install --upgrade cozempic --quiet 2>/dev/null || pip install --upgrade cozempic --quiet --disable-pip-version-check 2>/dev/null || python3 -m pip install --upgrade cozempic --quiet --disable-pip-version-check 2>/dev/null; } && POST=$(cozempic --version 2>/dev/null | awk '/^cozempic /{print $2; exit}') && [ -n \"$POST\" ] && [ \"$PRE\" != \"$POST\" ] && { cozempic guard --reload-self ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --reload-self ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; }; { cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic guard --daemon ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; ) 9>\"/tmp/cozempic_hook_${SESSION_ID:0:12}.lock\" >/dev/null 2>&1 & echo 'Cozempic: guard active' # cozempic-hook-schema=v5"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 -c \"import sys,json; d=json.load(sys.stdin); f=d.get('tool_input',{}).get('file_path','') or d.get('tool_response',{}).get('filePath',''); exit(0 if f.endswith(('.ino','.cpp','.h')) else 1)\" && cd 'D:/Users/Robert/Documents/GitHub/RvdB/OTGW-firmware' && python evaluate.py --quick 2>/dev/null || true",
"timeout": 30,
"statusMessage": "Evaluating code quality..."
}
]
},
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "{ cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true # cozempic-hook-schema=v5"
}
]
},
{
"matcher": "TaskCreate|TaskUpdate",
"hooks": [
{
"type": "command",
"command": "{ cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true # cozempic-hook-schema=v5"
}
]
},
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "{ cozempic remind 2>/dev/null || python3 -m cozempic remind 2>/dev/null; } || true # cozempic-hook-schema=v5"
}
]
}
],
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "TRANSCRIPT=$(cat | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true; { cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true; echo 'Cozempic: context checkpointed' # cozempic-hook-schema=v5"
}
]
}
],
"PostCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "{ cozempic post-compact 2>/dev/null || python3 -m cozempic post-compact 2>/dev/null; } || true; { cozempic digest inject 2>/dev/null || python3 -m cozempic digest inject 2>/dev/null; } || true; echo 'Cozempic: context recovered' # cozempic-hook-schema=v5"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "TRANSCRIPT=$(cat | PYTHONIOENCODING=utf-8 python3 -c \"import sys,json; print(json.load(sys.stdin).get('transcript_path',''))\" 2>/dev/null); { cozempic checkpoint 2>/dev/null || python3 -m cozempic checkpoint 2>/dev/null; } || true; { cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null || python3 -m cozempic digest flush ${TRANSCRIPT:+--session \"$TRANSCRIPT\"} 2>/dev/null; } || true # cozempic-hook-schema=v5"
}
]
}
]
}
}
================================================
FILE: .claude/skills/adr/SKILL.md
================================================
---
name: adr
description: 'Architecture Decision Record (ADR) management skill. Creates, maintains, and enforces architectural decisions. Ensures code changes align with documented decisions. Documents alternatives considered and rejected. Facilitates architectural planning and human decision documentation.'
license: MIT
---
# ADR-Skill: Architecture Decision Record Management
## Overview
This skill enables systematic creation, maintenance, and enforcement of Architecture Decision Records (ADRs) for the OTGW-firmware project. ADRs document significant architectural choices along with their context, alternatives considered, and consequences. They serve as living documentation to help current and future developers understand why the system is built the way it is.
## When to Use
### Automatic Trigger Scenarios
Use this skill **automatically** when:
- **Code review or PR analysis** - Verify changes align with existing ADRs
- **CI/CD automation review** - Enforce architectural compliance
- **Major code changes** - Check if new ADR is needed
- **Architecture planning** - Document important decisions before implementation
- **Refactoring proposals** - Validate against existing decisions or create new ADR
### Explicit User Requests
Use this skill when user mentions:
- "Create an ADR"
- "Document this decision"
- "Architecture decision"
- "Why did we choose..."
- "Alternatives considered"
- "Document my choice"
### Decision Triggers
Create a new ADR when making a decision that:
- Has **long-term impact** on architecture
- Affects **multiple components** or modules
- Involves **trade-offs** between alternatives
- **Constrains future** development choices
- Addresses a **significant technical challenge**
- **Changes existing** architectural patterns
- Requires **human decision** that should be preserved
### Do NOT Create ADR For
- Bug fixes that don't change architecture
- Code refactoring maintaining same structure
- Configuration changes
- Documentation updates (non-architectural)
- Minor feature additions within existing patterns
- Temporary workarounds or experiments
---
## Initial Codebase Analysis
### First-Time Use: Discovering Undocumented Decisions
**IMPORTANT:** On first use or when introducing this skill to an existing codebase, perform a comprehensive architectural analysis to identify and document existing but undocumented decisions.
#### Analysis Workflow
**Step 1: Identify Architectural Patterns**
```bash
# Areas to analyze:
1. Platform choices (ESP8266, Arduino, frameworks)
2. Memory management patterns (static buffers, PROGMEM)
3. Network architecture (protocols, security models)
4. Integration patterns (MQTT, APIs, WebSocket)
5. Core system design (timers, scheduling, persistence)
6. Hardware interfaces (sensors, watchdog, GPIO)
7. Build and development tools
```
**Step 2: Ask Critical Questions**
```
For each pattern discovered:
- WHY was this approach chosen? (context, constraints)
- WHAT alternatives exist? (at least 2-3 viable options)
- WHY were alternatives rejected? (specific technical reasons)
- WHAT are the consequences? (benefits, costs, risks)
- HOW is this implemented? (code examples, key files)
- WHEN was this decided? (estimate if unknown)
```
**Step 3: Generate ADRs Systematically**
```
For each undocumented architectural decision:
1. Use the explore agent to understand the pattern
2. Review code, comments, git history for context
3. Identify constraints (memory, performance, compatibility)
4. Research alternatives (even if obvious)
5. Document consequences (positive AND negative)
6. Create ADR with Status: Accepted (since implemented)
7. Link to actual implementation (files, commits)
```
**Step 4: Prioritize Documentation**
```
Start with foundational decisions that:
- Affect multiple components
- Constrain future choices
- Are non-obvious or counterintuitive
- Have significant trade-offs
- Are frequently questioned
```
#### Initial Analysis Prompts
**Trigger codebase analysis:**
```
"Analyze this codebase to identify undocumented architectural decisions"
"Generate ADRs for existing architectural patterns in this codebase"
"What architectural decisions should be documented in this project?"
```
**For specific areas:**
```
"Identify and document memory management architectural decisions"
"What network architecture decisions are undocumented?"
"Analyze platform choices and create ADRs"
```
#### Example: Discovering ADR-009 (PROGMEM)
**Pattern discovered:** String literals use F() and PSTR() macros throughout codebase
**Critical questions:**
- WHY? → ESP8266 has only 40KB RAM; string literals waste 5-8KB
- Alternatives? → Keep in RAM, external RAM, compressed strings, string table
- Why rejected? → RAM too limited, hardware changes, complexity, doesn't solve problem
- Consequences? → +5-8KB heap (positive), verbose code (negative), flash slower than RAM (accepted)
**Result:** ADR-009 documents mandatory PROGMEM usage with clear rationale
---
## ADR Principles
### The Golden Rules
1. **One Decision Per ADR** - Each ADR captures a single architectural choice
2. **Immutable History** - Never modify accepted ADRs; supersede with new ones instead
3. **Context is King** - Explain WHY the decision was made, not just WHAT
4. **Alternatives Matter** - Document what was considered but rejected
5. **Human Decisions Marked** - Clearly indicate when decision came from user/stakeholder
6. **Critical Analysis** - Be thorough, question assumptions, document trade-offs honestly
7. **Understandable Language** - Write for developers unfamiliar with the decision; avoid unexplained jargon
### ADR Best Practices
```
✓ Write for future developers who weren't there
✓ Include code examples and diagrams
✓ Reference related ADRs
✓ Use clear, simple language
✓ Document constraints that drove the decision
✓ Explain consequences (positive and negative)
✓ Link to implementation (files, PRs, commits)
✓ Be critical - question the decision, document risks
✓ Provide specific evidence (measurements, benchmarks)
✓ Explain technical terms on first use
✗ Don't use jargon without explanation
✗ Don't assume reader knows the context
✗ Don't skip alternatives (even obvious ones)
✗ Don't make assumptions unstated
✗ Don't forget to update status when superseding
✗ Don't be vague ("it's better", "improves performance")
✗ Don't skip negative consequences
✗ Don't write marketing copy - be honest about trade-offs
```
---
## ADR Template
Use this comprehensive template for all new ADRs:
```markdown
# ADR-XXX: [Concise Decision Title]
**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-XXX
**Date:** YYYY-MM-DD
**Decision Maker:** [Copilot Agent | User: Name | Team Discussion]
## Context
### Problem Statement
[What problem are we solving? What is the situation or challenge?]
### Background
[Relevant history, current state, or technical context]
### Constraints
[What constraints apply? Hardware, memory, security, compatibility, budget, timeline?]
### Stakeholders
[Who is affected by this decision? Users, developers, operations, integrations?]
## Decision
[Clear statement of the choice made and rationale]
### Why This Choice
[Explain reasoning behind the decision]
### Implementation Summary
[High-level description of how this will be implemented]
## Alternatives Considered
### Alternative 1: [Name]
**Description:** [What is this alternative?]
**Pros:**
- Benefit 1
- Benefit 2
**Cons:**
- Drawback 1
- Drawback 2
**Why Not Chosen:** [Clear explanation]
### Alternative 2: [Name]
[Repeat structure for each alternative]
[Include at least 2-3 alternatives. If none exist, explain why this is the only viable option.]
## Consequences
### Positive
- **[Benefit Category]:** Specific benefit
- **[Another Category]:** Another benefit
### Negative
- **[Cost/Limitation]:** Specific drawback
- **[Trade-off]:** What we're giving up
### Risks & Mitigation
- **Risk:** [Description]
**Mitigation:** [How we address this]
### Impact Areas
- **Performance:** [Impact on system performance]
- **Maintainability:** [Impact on code maintenance]
- **Security:** [Security implications]
- **Scalability:** [Scaling implications]
- **Developer Experience:** [Impact on development]
## Implementation Notes
### Key Files/Modules Affected
- `path/to/file.ext` - [Brief description of changes]
- `another/file.ext` - [Brief description]
### Code Examples
```language
// Example showing how this decision is implemented
function example() {
// Demonstrate the pattern
}
```
### Migration Required
[If this changes existing code, describe migration steps. Otherwise state "None."]
## Verification
### How to Verify This Decision
[How can a developer verify this decision is being followed?]
### Testing Requirements
[What testing ensures this decision is properly implemented?]
### Monitoring/Metrics
[What metrics indicate this decision is working?]
## Related Decisions
- **Depends on:** ADR-XXX ([Title])
- **Related to:** ADR-XXX ([Title])
- **Supersedes:** ADR-XXX ([Title]) - if applicable
- **Superseded by:** ADR-XXX ([Title]) - if applicable
## References
- [Link to relevant documentation]
- [Link to code examples]
- [Link to related issues/PRs]
- [External resources]
- [Standards or specifications]
## Timeline
- **YYYY-MM-DD:** Initial proposal
- **YYYY-MM-DD:** Discussion/review
- **YYYY-MM-DD:** Accepted
- **YYYY-MM-DD:** Implemented
- **YYYY-MM-DD:** Superseded (if applicable)
---
**Metadata:**
- **ADR Number:** XXX
- **Status:** [Current status]
- **Category:** [Platform/Memory/Network/Integration/etc.]
- **Impact:** [High/Medium/Low]
```
---
## Naming Convention
### ADR File Naming
```
Format: ADR-XXX-short-descriptive-title.md
Where:
- XXX = Zero-padded sequential number (001, 002, ..., 029, 030, etc.)
- short-descriptive-title = Kebab-case description
Examples:
✓ ADR-001-esp8266-platform-selection.md
✓ ADR-009-progmem-string-literals.md
✓ ADR-029-simple-xhr-ota-flash.md
✗ ADR-1-esp8266.md (not zero-padded)
✗ ADR-030-This_Is_Wrong.md (not kebab-case)
✗ adr-030-lowercase-adr.md (ADR prefix must be uppercase)
```
### Number Assignment
- Sequential numbering starting from 001
- Check `docs/adr/` for highest number and increment
- Don't reuse numbers from deprecated/superseded ADRs
- Don't leave gaps in numbering
---
## ADR Categories
Group ADRs by architectural domain for easier navigation:
- **Platform & Build System** - Platform choice, build tools, frameworks
- **Memory Management** - RAM optimization, buffer strategies, PROGMEM
- **Network & Security** - Protocols, encryption, authentication
- **Integration & Communication** - APIs, MQTT, WebSocket
- **System Architecture** - Core patterns, scheduling, persistence
- **Hardware & Reliability** - Hardware interface, watchdog, sensors
- **Development & Build** - Developer tools, CI/CD, testing
- **Core Services** - Time management, queuing, configuration
- **Features & Extensions** - Specific features, sensor integration
- **Browser & Client** - Frontend, browser compatibility, UX
- **OTA & Updates** - Firmware updates, version management
---
## ADR Workflow
### 1. Before Making Architectural Changes
```bash
# Step 1: Review existing ADRs
- Read docs/adr/README.md for navigation
- Search for related decisions
- Check if change conflicts with existing ADRs
- Understand architectural constraints
# Step 2: Determine if new ADR needed
- Will this have long-term impact?
- Does it affect multiple components?
- Are there multiple alternatives?
- Will future developers need context?
# Step 3: If ADR needed, draft it
- Use the template above
- Fill in all sections thoughtfully
- Include code examples
- Document alternatives thoroughly
```
### 2. During Implementation
```bash
# Step 1: Create ADR with Status: Proposed
- Get next ADR number
- Write comprehensive ADR
- Include decision maker (Copilot Agent or User: Name)
# Step 2: Reference ADR in code
- Add comments linking to ADR
- Example: // See ADR-030 for why we use this pattern
# Step 3: Implement according to decision
- Follow patterns established in ADR
- Ensure code aligns with decision
- Implement mitigation for identified risks
```
### 3. After Implementation
```bash
# Step 1: Update ADR status to Accepted
- Change Status: Proposed → Accepted
- Add implementation date to timeline
- Link to PR/commit that implemented it
# Step 2: Update docs/adr/README.md
- Add entry to ADR index
- Categorize appropriately
- Update reference counts if applicable
# Step 3: Store memory (if using Copilot)
- Store key facts for future reference
- Include ADR number in citations
```
### 4. When Superseding an ADR
```bash
# Step 1: Create new ADR
- Write new ADR explaining change
- Reference original ADR
- Explain why change needed
# Step 2: Update old ADR
- Change status to "Superseded by ADR-XXX"
- Do NOT delete or modify decision/context
- Add superseded date to timeline
# Step 3: Update README
- Update both ADRs in index
- Note the supersession relationship
```
---
## Code Review Integration
### During Code Reviews
**Automatic Checks:**
1. Do changes violate any existing ADRs?
2. Do changes require a new ADR?
3. Are ADR references in code accurate?
4. Is ADR status up to date?
**Example Review Comments:**
```
✗ "This change violates ADR-004 (Static Buffer Allocation).
The String class should not be used here."
✓ "This change aligns with ADR-007 (Timer-Based Scheduling).
Good use of DECLARE_TIMER_SEC macro."
? "This introduces a new architectural pattern.
Please create an ADR documenting the decision."
! "ADR-029 is referenced but hasn't been updated to Accepted status.
Please update the status since this is now implemented."
```
### PR Checklist with ADRs
```markdown
## ADR Compliance Checklist
- [ ] Changes reviewed against existing ADRs
- [ ] No violations of architectural decisions
- [ ] New ADR created if needed (Status: Proposed)
- [ ] ADR status updated if implementing existing ADR
- [ ] Code comments reference relevant ADRs
- [ ] docs/adr/README.md updated if new ADR added
```
---
## CI/CD Integration
### Pre-Commit Checks
```bash
# Check ADR compliance
python evaluate.py # Includes ADR pattern enforcement
# Verify ADR references are valid
grep -r "ADR-[0-9]" src/ | while read line; do
# Extract ADR number and verify file exists
# Report broken references
done
# Check for architectural pattern violations
# (e.g., String usage, missing PROGMEM, etc.)
```
### PR Automation
```yaml
# .github/workflows/adr-check.yml
name: ADR Compliance Check
on: [pull_request]
jobs:
adr-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check ADR compliance
run: |
# Run automated checks
# Verify no ADR violations
# Check if new ADRs needed
- name: Comment on PR
if: violations found
# Post comment suggesting ADR review
```
---
## Human Decision Documentation
### When User Makes Architectural Choice
If a user explicitly makes an architectural decision, document it clearly:
```markdown
# ADR-XXX: [Title]
**Status:** Accepted
**Date:** YYYY-MM-DD
**Decision Maker:** User: [Name/Role] ← IMPORTANT: Mark as human decision
## Context
[User's problem or request]
## Decision
**User Decision:** [What the user chose]
The user explicitly chose [X] over [Y] because [reason given].
## Alternatives Considered
[What was presented to the user]
### Alternative 1: [Option presented]
**User Feedback:** [User's reasoning for/against]
### Alternative 2: [Option presented]
**User Feedback:** [User's reasoning for/against]
## Rationale
**User's Stated Reasons:**
- Reason 1
- Reason 2
**Technical Context:**
[Agent's analysis of the decision's technical implications]
```
### Example Human Decision ADR
```markdown
# ADR-030: Use PostgreSQL for Sensor Data Storage
**Status:** Accepted
**Date:** 2026-02-06
**Decision Maker:** User: Rob van den Breemen
## Context
User requested evaluation of database options for storing sensor historical data.
## Decision
**User Decision:** Use PostgreSQL instead of SQLite
The user explicitly chose PostgreSQL after being presented with both options,
citing need for multi-client access and superior analytics capabilities.
## Alternatives Considered
### Alternative 1: SQLite
**Pros:** Lightweight, embedded, no server needed
**Cons:** Single writer limitation
**User Feedback:** "Need multiple Home Assistant instances to query simultaneously"
### Alternative 2: PostgreSQL
**Pros:** Multi-client, powerful queries, excellent HA integration
**Cons:** Requires separate server, more complex setup
**User Feedback:** "Worth the complexity for analytics and flexibility"
## Rationale
**User's Stated Reasons:**
- Need concurrent access from multiple HA instances
- Plan to use TimescaleDB extension for time-series data
- Want to run complex queries for energy analytics
- Already running PostgreSQL for other home automation
**Technical Context:**
This decision means we'll implement a RESTful API for sensor data
instead of direct database access from the firmware.
```
---
## ADR Index Management
### Maintaining docs/adr/README.md
The README is the **navigation hub** for all ADRs. Keep it up to date:
**Required Sections:**
1. **What are ADRs?** - Explanation for new readers
2. **Quick Navigation** - By topic with counts
3. **ADR Index** - Full categorized list
4. **ADR Template** - Link or embed template
5. **Key Architectural Themes** - Cross-cutting concerns
6. **Architectural Dependencies** - Which ADRs depend on which
7. **When to Create an ADR** - Guidance
8. **Superseding ADRs** - How to handle changes
9. **Resources** - Links to best practices
**Update Process:**
```bash
# When adding new ADR:
1. Add entry under appropriate category
2. Update category count
3. Update "Foundational ADRs" if highly referenced
4. Update "Decision Timeline" if appropriate
5. Add to "Related Decisions" in other ADRs
```
---
## Code Examples in ADRs
### Good ADR Code Examples
**Principle:** Show, don't just tell
```markdown
## Implementation Notes
### WRONG: No example
Use PROGMEM for string literals.
### RIGHT: Clear example
```cpp
// WRONG - String literal wastes RAM
DebugTln("Starting WiFi");
// CORRECT - F() macro stores in flash
DebugTln(F("Starting WiFi"));
// WRONG - strcmp on PROGMEM
strcmp(value, "ON");
// CORRECT - strcmp_P for PROGMEM
strcmp_P(value, PSTR("ON"));
```
```
### Include Diagrams When Helpful
```markdown
## Implementation Notes
### Architecture Diagram
```
[WebSocket Client] ─── ws:// ───> [ESP8266:81]
│
├─> [Buffer] (1KB)
│ │
│ ▼
[HTTP Client] ──── http:// ─> [ESP8266:80] [Queue]
│ │
└──────┴─> [PIC Serial]
```
### Data Flow
```
User Request → REST API → Command Queue → Serial → PIC → OpenTherm Boiler
↓ ↓ ↓ ↓
Validate JSON Parse Dedup Check Protocol
```
```
---
## ADR Metrics & Maintenance
### Health Indicators
**Healthy ADR Repository:**
- ✓ All ADRs have clear status
- ✓ Superseded ADRs reference replacement
- ✓ Code references match existing ADRs
- ✓ README index is up to date
- ✓ Recent ADRs include implementation dates
- ✓ Each ADR has at least 2 alternatives documented
**Needs Attention:**
- ✗ Multiple ADRs with "Proposed" status for >30 days
- ✗ ADR numbers with gaps
- ✗ Code comments reference non-existent ADRs
- ✗ ADRs without alternatives section
- ✗ README categories don't match actual ADRs
### Periodic Review
**Quarterly ADR Review:**
```bash
# Review checklist
1. Are any "Proposed" ADRs abandoned? (Mark deprecated)
2. Are any "Accepted" ADRs being violated? (Update enforcement)
3. Do new patterns need ADRs? (Create them)
4. Are superseded ADRs properly linked? (Verify)
5. Is README accurately reflecting current state? (Update)
```
---
## Integration with Copilot Instructions
### How This Skill Connects to .github/copilot-instructions.md
The copilot instructions file **references** ADRs but doesn't duplicate them:
**In copilot-instructions.md:**
```markdown
## Architecture Decision Records (ADRs)
**IMPORTANT:** This project maintains ADRs documenting key architectural choices.
- **ADR Index:** `docs/adr/README.md`
- **Before making changes:** Review relevant ADRs
- **ADR Compliance:** Follow patterns in ADRs
- **Creating ADRs:** Use the ADR-skill for guidance
**Key Decisions:**
- ADR-003: HTTP-Only (no HTTPS/WSS) ← Link, don't duplicate
- ADR-004: Static Buffer Allocation ← Link, don't duplicate
- ADR-009: PROGMEM String Literals ← Link, don't duplicate
```
**In this skill:**
- Full ADR creation process
- Templates and examples
- Decision guidance
- Workflow details
**Division of Responsibility:**
- **Copilot Instructions:** Quick reference, links to ADRs, enforcement rules
- **ADR-Skill:** Comprehensive ADR creation and management process
- **Individual ADRs:** Detailed context, decisions, and consequences
---
## Examples from OTGW-Firmware
### Example 1: Memory Management ADR (ADR-004)
**What makes it good:**
- Clear problem statement (heap fragmentation)
- Specific measurements (3,130-3,730 bytes saved)
- Multiple alternatives with detailed pros/cons
- Implementation patterns with code examples
- Risk mitigation strategies
- References to evaluation framework
**Key Sections:**
```markdown
## Consequences
### Positive
- **Stability:** Eliminates most out-of-memory crashes
- **Scalability:** Can add features without exhausting RAM
- **Measurable:** RAM usage is stable and predictable
### Negative
- **Code verbosity:** Requires buffer size management
- Accepted: Necessary trade-off for stability
```
### Example 2: Protocol Decision ADR (ADR-003)
**What makes it good:**
- Explains why not HTTPS (memory constraints)
- Documents security model (local network only)
- Lists 4 rejected alternatives with clear reasoning
- Includes documentation requirements
- References related ADRs (dependencies)
**Key Sections:**
```markdown
## Alternatives Considered
### Alternative 1: HTTPS with Self-Signed Certificates
**Cons:**
- Requires 20-30KB RAM for TLS handshake (50-75% of available heap)
- OTA updates may fail due to insufficient memory
**Why not chosen:** Memory constraints prohibitive
```
### Example 3: Feature ADR (ADR-029)
**What makes it good:**
- Quantifies improvement (68.5% code reduction)
- Shows before/after code comparison
- Explains Safari-specific bug being fixed
- Links to the problem it solves (ADR-025)
- Includes migration path
---
## Quick Reference
### ADR Creation Checklist
```markdown
Creating a new ADR? Check these:
- [ ] Next sequential number assigned (check docs/adr/)
- [ ] Filename follows ADR-XXX-kebab-case-title.md format
- [ ] Status field present (Proposed/Accepted/etc.)
- [ ] Date field present (YYYY-MM-DD)
- [ ] Decision Maker identified (Copilot Agent or User: Name)
- [ ] Context section explains problem clearly
- [ ] At least 2-3 alternatives documented
- [ ] Pros and cons listed for each alternative
- [ ] "Why not chosen" for each rejected alternative
- [ ] Consequences section complete (positive, negative, risks)
- [ ] Code examples included (if applicable)
- [ ] Related ADRs referenced
- [ ] Implementation notes with affected files
- [ ] Added to docs/adr/README.md index
- [ ] Category assigned
- [ ] References/links included
```
### Common ADR Mistakes to Avoid
```markdown
❌ Writing "We should use X" without explaining why
❌ Skipping alternatives ("This is the only way")
❌ No code examples for technical decisions
❌ Forgetting to update status after implementation
❌ Not referencing related ADRs
❌ Vague consequences ("It will be better")
❌ No decision maker attribution
❌ Missing constraints that drove the decision
❌ Modifying accepted ADRs instead of superseding
❌ Not updating README.md index
❌ Using jargon without defining it (e.g., "TLS handshake" without explaining)
❌ Being superficial - not digging into the "why" behind constraints
❌ Hiding negative consequences or pretending there are none
❌ Writing marketing copy instead of honest technical analysis
❌ Skipping measurements ("faster" vs "68.5% code reduction")
❌ Not explaining technical trade-offs in understandable terms
```
### Critical Analysis Guidelines
**Be thorough and questioning:**
```
✓ Challenge assumptions - "Why is this constraint real?"
✓ Quantify impacts - "Saves 5-8KB RAM" not "saves memory"
✓ Be honest about negatives - "Code is more verbose (accepted trade-off)"
✓ Question the decision - "Is this still the right choice?"
✓ Document risks explicitly - "Risk: X. Mitigation: Y."
✓ Use real measurements - "Requires 20-30KB RAM (50-75% of heap)"
✓ Explain technical concepts - "TLS/SSL = encrypted network protocol"
```
**Write in understandable language:**
```
✓ Define acronyms on first use: "PROGMEM (Program Memory)"
✓ Explain technical terms: "heap fragmentation = memory becoming unusable"
✓ Use analogies for complex concepts: "like trying to park a bus in scattered car spaces"
✓ Provide context: "ESP8266 has 40KB RAM total, after libraries ~20-25KB available"
✓ Show concrete examples: Include code snippets showing the pattern
✓ Break down complex ideas: Use bullet points and clear structure
✓ Avoid assumed knowledge: Don't assume reader knows ESP8266 specifics
```
**Example of critical, understandable writing:**
```markdown
## Consequences
### Positive
- **Stability:** Eliminates most out-of-memory crashes
- Measured: Heap available increased from ~15KB to ~20KB
- Evidence: No OOM crashes in 30-day stress test after implementation
### Negative
- **Code verbosity:** Every string needs F() macro wrapper
- Impact: ~10-15% more characters per debug statement
- Accepted because: Stability more important than typing convenience
- Example: `DebugTln(F("Message"))` vs `DebugTln("Message")`
### Risks & Mitigation
- **Risk:** Developers forget to use F() macro
- **Impact:** RAM gradually consumed, eventual crashes
- **Mitigation 1:** Evaluation framework catches violations (evaluate.py)
- **Mitigation 2:** Code review checklist includes PROGMEM check
- **Mitigation 3:** Copilot instructions enforce pattern
```
### When in Doubt
**Ask these questions:**
1. **Will a developer in 6 months understand WHY we did this?**
2. **Have I explained what we REJECTED, not just what we chose?**
3. **Could this ADR help someone avoid making a wrong decision?**
4. **Have I included enough code examples?**
5. **Is the decision maker clearly identified?**
6. **Can someone unfamiliar with this technology understand the core trade-offs?**
7. **Have I been honest about negative consequences?**
8. **Have I quantified impacts with actual measurements?**
If you answered "No" to any of these, improve the ADR.
---
## Skill Invocation
### How Copilot Uses This Skill
**Automatic triggers:**
- When analyzing code for architectural changes
- When creating/reviewing pull requests
- When user asks architectural questions
- When refactoring requires decision documentation
**Manual invocation:**
- User: "Create an ADR for this"
- User: "Document this architectural decision"
- User: "Why did we choose X?"
**Workflow:**
```
1. Copilot detects architectural change
2. Checks existing ADRs for conflicts
3. If new pattern: Suggests creating ADR
4. Uses this skill to generate comprehensive ADR
5. Updates README and references
6. Stores facts for future sessions
```
### Integration with Copilot Instructions
This skill is integrated with GitHub Copilot through multiple instruction layers:
**Repository-wide instructions** (`.github/copilot-instructions.md`):
- Defines ADR workflow for all Copilot interactions
- Establishes ADR lifecycle (Proposed → Accepted → Superseded)
- Specifies when ADRs are required
- Enforces immutability of accepted ADRs
**Path-specific instructions** (`.github/instructions/`):
- `adr.coding-agent.instructions.md` - Specific guidance for coding agent
- Before/during implementation ADR requirements
- Creating new ADRs checklist
- Supersession workflow
- `adr.code-review.instructions.md` - Specific guidance for code review
- ADR compliance checks
- Review checklist for architectural changes
- Review comment examples
**How it works:**
1. Copilot reads repository-wide instructions for all operations
2. Path-specific instructions apply based on context (coding vs review)
3. This skill provides the comprehensive ADR template and best practices
4. Together, they ensure consistent ADR governance across all Copilot interactions
**Verification:**
You can verify custom instructions are being used by checking the "References" section in Copilot Chat responses, where the instruction files will appear as references.
---
## Resources
### Official ADR Resources
- **ADR Best Practices:** https://adr.github.io/
- **Michael Nygard's Original Post:** https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
- **MADR Template:** https://github.com/adr/madr
- **Joel Parker Henderson Collection:** https://github.com/joelparkerhenderson/architecture-decision-record
- **Microsoft Azure ADR Guide:** https://learn.microsoft.com/en-us/azure/well-architected/architect-role/architecture-decision-record
- **ThoughtWorks Technology Radar:** ADR mentioned as "Adopt"
### ADR Tooling Ecosystem
- **adr-tools** (npryce) - CLI for creating and managing ADRs: https://github.com/npryce/adr-tools
- **Log4brains** - ADR management with static site generation: https://github.com/thomvaill/log4brains
- **ADR Tools Catalog** - Comprehensive tooling list: https://adr.github.io/#tooling
### Project-Specific Resources
- **OTGW-Firmware ADR Index:** `/docs/adr/README.md`
- **Copilot Instructions:** `/.github/copilot-instructions.md`
- **Coding Agent Instructions:** `/.github/instructions/adr.coding-agent.instructions.md`
- **Code Review Instructions:** `/.github/instructions/adr.code-review.instructions.md`
- **Evaluation Framework:** `/evaluate.py` (enforces ADR decisions)
---
**Remember:** ADRs are **living documentation** stored as docs-as-code in the same repository as the implementation. They should be consulted during development, referenced in code reviews, and evolved through supersession (not modification). Good ADRs make architectural decisions visible, understandable, and enforceable.
================================================
FILE: .claude/skills/flash/SKILL.md
================================================
---
name: flash
description: Build firmware + filesystem and flash to ESP via USB. Auto-detects serial port. No user input needed.
disable-model-invocation: true
---
# /flash - Build and flash OTGW-firmware
Build firmware and filesystem, then flash both to the connected ESP via USB.
Fully automated, no user interaction required.
## Steps
Run these commands sequentially. Stop and report if any step fails.
```bash
cd D:\Users\Robert\Documents\GitHub\RvdB\OTGW-firmware
python build.py
python flash_esp.py --build --no-interactive
```
The `build.py` without flags builds both firmware AND filesystem.
The `flash_esp.py --build --no-interactive` flashes both to the ESP without prompts, auto-detecting the serial port.
## After flashing
Report:
- Build result (firmware size, filesystem size)
- Flash result (port used, success/failure)
- The firmware version that was flashed (from version.h)
================================================
FILE: .claude/skills/release/SKILL.md
================================================
---
name: release
description: Prepare and execute a full OTGW-firmware release following the documented release process
disable-model-invocation: true
---
# /release - OTGW-firmware Release Skill
Prepare and execute a complete release of the OTGW-firmware project.
## Usage
```
/release <version>
```
Example: `/release 1.3.2`
The version argument is the target release version (without `v` prefix). The previous version is auto-detected from the latest git tag.
## Writing style rules
- **Never use em dashes** in any output: not in release notes, GitHub release body, Discord messages, commit messages, README updates, or conversation text. Use colons, periods, commas, or parentheses instead.
- **All release notes and GitHub release messages MUST be in English** (international audience).
- **No emojis** in release notes unless the existing format uses them (README uses them in headings).
## Process
Follow these phases in order. There are only **2 mandatory checkpoints** (marked with CHECKPOINT). All other phases proceed automatically unless something unexpected happens.
### Phase 0: Prepare - clean state & detect previous release
Start every release by ensuring a clean working state and detecting the baseline.
1. **Ensure you are on `dev`**: `git checkout dev`
2. **Commit and push any uncommitted changes**:
- `git status` - if there are modified or untracked files, stage, commit, and push them
- `git pull` - incorporate any remote changes
- `git push origin dev` - ensure local and remote are in sync
- Verify: `git status` must show `nothing to commit, working tree clean`
3. **Detect the latest GitHub release** (this is the authoritative previous release, not a local git tag):
```bash
gh release view --json tagName,name,publishedAt --jq '{tag: .tagName, title: .name, date: .publishedAt}'
```
Store the tag name (e.g., `v1.3.2`) and published date for use in later phases.
4. **Verify the release tag exists locally**: `git fetch --tags && git log <prev-tag> --oneline -1`
5. **List code changes since that release**: `git log <prev-tag>..HEAD --oneline -- src/ | grep -v "CI: update version.h"`
- If there are no code changes, warn the user and ask whether to proceed. (Conditional stop.)
### Phase 1: ADR validation
Check whether any architectural changes since the previous release require new or updated ADRs.
1. Review the code commits from Phase 0 step 5
2. For each significant change: does it affect architecture, NFRs, API contracts, new/replaced dependencies, or build/CI tooling?
3. Check `docs/adr/` for existing ADRs that may need their Related section updated
4. If new ADRs are needed, create them now on `dev` before proceeding
**Conditional stop:** Only pause for user input if ADRs are actually needed. If no ADRs are required, report that and proceed automatically.
### Phase 2: Stabilize dev branch
1. Commit all open/uncommitted changes on `dev` and push to remote
2. Run `python build.py` to verify the build works
3. Commit version.h changes from build.py and push to remote
4. If the build fails, fix the issue, commit and push again. Repeat until green.
**No checkpoint.** If the build succeeds, proceed automatically to Phase 3.
### Phase 3: Merge dev to main
1. `git checkout main && git pull origin main`
2. `git merge dev`
3. Verify merge succeeded without conflicts
**Conditional stop:** Only pause if there are merge conflicts. Otherwise proceed.
### Phase 4: Gather changes, contributors & generate documentation
On `main`, gather all information AND generate all documentation in one pass. Present everything together for a single review.
**Changes:**
1. Detect the previous release tag: `git describe --tags --abbrev=0`
2. List all commits since that tag: `git log <prev-tag>..HEAD --oneline` (exclude "CI: update version.h" commits)
3. Categorize each commit as: new feature, bug fix, internal improvement, or breaking change
4. Check `docs/adr/` for new or updated ADRs since the previous release
**Contributors (automated from 3 sources):**
*Source 1 - GitHub Issues & PRs:*
- `gh issue list --state closed --search "closed:>YYYY-MM-DD" --json author,title --jq '.[] | "\(.author.login): \(.title)"'`
- `gh pr list --state merged --search "merged:>YYYY-MM-DD" --json author,title --jq '.[] | "\(.author.login): \(.title)"'`
*Source 2 - Discord #beta-testing channel:*
- Read messages from `#beta-testing` using `mcp__discord-mcp__fetch_channel_history` with `channel_id="914498730001072149"` and `limit=100`
- Filter messages since the previous release date
- Extract unique contributors (exclude bot accounts and the maintainer `number3nl` / user ID `384411356616720384`)
- For each contributor, note what they did: tested builds, reported bugs, shared logs, provided diagnostic insights
- **Username formatting**: Discord usernames often have a 4-digit numeric suffix (e.g., `fuzzyduck3793`, `simontemplar6623`). Strip the trailing digits to get the display name (e.g., `fuzzyduck`, `simontemplar`). Exception: if removing digits makes the name ambiguous or clearly wrong, keep the original.
*Source 3 - Discord #devs-esp-firmware channel:*
- Read messages from `#devs-esp-firmware` (channel ID: `924989767966425158`) with limit 100
- Filter messages since the previous release date
- Issues and bug reports are also reported here; extract them alongside contributors
**Discord server reference:** Guild ID `812969634638725140` (OTGW-firmware community).
**Compile the contributor list:**
- Deduplicate across all sources (same person may appear on GitHub and Discord)
- Identify the **most active contributor** for a special shoutout
- Present as: shoutout paragraph + bullet list of remaining contributors with their contribution
**Documentation (generate all files):**
1. **`RELEASE_NOTES_<version>.md`** (repository root): Full technical release notes following the template in `docs/process/RELEASE_PROCESS.md`
2. **`RELEASE_GITHUB_<version>.md`** (repository root): Concise GitHub release body with bug fixes, improvements, upgrade notes, and Thank You section (shoutout + contributor list + Discord invite link)
3. **`docs/BREAKING_CHANGES.md`**: Prepend a new version section. Always declare explicitly whether there are breaking changes or not.
4. **`README.md`**: Demote current "What's New" to "What was new", add new "What's New in v<version>" section with highlights
**CHECKPOINT 1: Present the categorized changes, contributor list, AND all generated documentation content to the user for review. Wait for approval before proceeding.**
### Phase 5: Release execution
Proceed directly after Phase 4 approval. No additional confirmation needed.
1. **Commit all outstanding changes on `main`** and push to remote
2. **Remove pre-release from `version.h`**: Comment out `_VERSION_PRERELEASE` so the build produces a clean `v<version>` without `-beta`. Verify: `grep -n "PRERELEASE" src/OTGW-firmware/version.h`
3. **Run `python build.py`** to produce the release build. Fix any issues.
4. **Commit the release build** and push `main` to remote
5. **Create draft GitHub release with tag**: Derive a short title (3-6 words) summarizing the release theme. Use format `v<version> - <Short Title>`. Examples: `v1.3.2 - File Explorer Reliability Fix`, `v1.4.0 - REST API v3 & Prometheus`. Command: `gh release create v<version> --target main --title "v<version> - <Short Title>" --notes-file RELEASE_GITHUB_<version>.md --draft`
6. **Upload build artifacts to the draft release**: `gh release upload v<version> build/*.ino.bin build/*.littlefs.bin flash_otgw.sh flash_otgw.bat --clobber`
7. **Verify artifacts are attached**: `gh release view v<version> --json assets --jq '.assets[].name'`
8. **Publish the release**: `gh release edit v<version> --draft=false --latest`
### Phase 6: Post-release, Discord announcement & sync dev
1. Verify release artifacts are attached to the GitHub release
2. Remind user to flash a device and check `GET /api/v2/device/info`
3. **Prepare Discord announcements** for both community channels:
- Dutch in `#nederlandse-ondersteuning` (channel ID: `815561033036333076`)
- English in `#english-support` (channel ID: `931267109726593116`)
- Both messages include: version, summary, contributor shoutout, download link
**CHECKPOINT 2: Show both Discord messages to the user before sending.**
4. **Send Discord messages** after approval
5. **Sync dev branch:**
- `git checkout dev && git merge main`
- Bump `version.h`: increment patch version, uncomment `_VERSION_PRERELEASE` and set to `beta`
- Run `autoinc-semver.py` to update derived strings
- Commit: `feat: Bump version to v<next>-beta for development`
- Push `dev`
## Phase 7: Post-publication corrections (when release notes need updating after publish)
Sometimes a user asks to correct text in an already-published release. Editing `RELEASE_GITHUB_<version>.md` in the repo does **not** update the GitHub release page automatically: the release body is a copy taken at `gh release create` time and lives on GitHub, not in the repo.
Whenever you update release notes, README, or the GitHub release body after publication, do all three in the same round:
1. **Edit the files in the repo** (`RELEASE_NOTES_<version>.md`, `README.md`, `RELEASE_GITHUB_<version>.md`) on `main`.
2. **Commit and push** to `main`.
3. **Update the live GitHub release body** with: `gh release edit v<version> --notes-file RELEASE_GITHUB_<version>.md`
4. **Merge `main` back into `dev`** so both branches reflect the correction: `git checkout dev && git merge main && git push origin dev`
Skipping step 3 leaves the repo and the GitHub release page out of sync. Skipping step 4 means the next beta cycle starts from stale release docs.
## Important rules
- **Never use em dashes** in any generated text (release notes, Discord messages, commit messages, README, conversation). Use colons, periods, commas, or parentheses.
- **Always push to remote after every commit**: keep local and remote in sync throughout the release
- **Always create releases as draft first**: upload artifacts, verify, then publish. Once published, releases are immutable.
- **No CI workflows for releases**: builds are done locally via `python build.py`, artifacts uploaded via `gh release upload`
- **Only 2 mandatory checkpoints**: Phase 4 (content review) and Phase 6 (Discord messages). Do not add unnecessary confirmation prompts.
- **Conditional stops**: only pause for merge conflicts, missing ADRs, build failures, or zero code changes.
- **Never force-push**: all pushes are normal pushes
- **Read `docs/process/RELEASE_PROCESS.md`** at the start of every release for the latest process updates
- **All release notes and GitHub release messages MUST be in English**: international audience
- **No emojis** in release notes unless the existing format uses them (README uses them in headings)
- **GitHub release body is decoupled from the repo file**: editing `RELEASE_GITHUB_<version>.md` does NOT update the published release page. After any edit, run `gh release edit v<version> --notes-file RELEASE_GITHUB_<version>.md` to push the change to GitHub, then merge main back into dev so both branches carry the correction.
================================================
FILE: .claude/skills/update-docs/SKILL.md
================================================
---
name: update-docs
description: Update OTGW-firmware documentation in one sequential, backlog-tracked workflow (dev / 1.5.x line)
disable-model-invocation: true
---
# /update-docs : Documentation Update Workflow (dev branch)
Update project documentation in one well-tracked sequential pass. Can be invoked standalone or as part of a release.
This is the **dev / 1.5.x line** copy of the skill. The 2.0.0 feature line carries a richer variant with manual chapters (EN/NL) and C4 architecture docs that do not exist on dev. Sections that depend on those docs are absent here. If dev later grows `docs/manuals/` or `docs/c4/`, port the corresponding phases back from the 2.0.0 SKILL.md.
## Why sequential and backlog-tracked
This workflow used to spawn one subagent per affected doc area in parallel. Fast in theory, but on releases that touched several areas it tripped Claude API concurrency limits and the run failed mid-flight. The new model has two properties:
- **Exactly one subagent runs at a time.** Slower wall-clock, no rate-limit hits, and a deterministic order of operations.
- **Every doc area is an AC on a single backlog task.** The run is replayable from the task on its own; progress is visible via `backlog task <id> --plain` while the workflow runs in the background; partial failures leave a clear breadcrumb trail.
## Usage
```
/update-docs # Standalone: scope from git changes
/update-docs --release 1.5.1 # Release mode: also generate release documents
/update-docs --scope full # Force-update all docs regardless of git diff
```
## Writing style rules
- **Never use em dashes.** Use colons, periods, commas, or parentheses.
- **All release documents in English** (international audience).
- **No emojis** in technical documentation.
- **Concise and correct over long and impressive.**
## Backlog rule (dev-specific)
Use the `backlog` CLI for every task operation. This project does NOT use the backlog MCP server in mixed-worktree scenarios because it indexes one tree only and serves stale content cross-tree (see `CLAUDE.md` worktree section). Substitute every `mcp__backlog__task_*` reference in the original 2.0.0 skill with `backlog task ...` here.
---
## Phase 1: Scope detection
Determine what changed since the last release and which documentation is affected.
```bash
PREV_TAG=$(gh release view --json tagName --jq '.tagName' 2>/dev/null || git describe --tags --abbrev=0)
git diff --name-only $PREV_TAG..HEAD
```
Categorize using this mapping (dev-line files only):
| Changed files match | Subsystem | Docs affected |
|---|---|---|
| `networkStuff.ino` | Network | docs/guides/WIFI_RECOVERY.md (if WiFi-related), docs/api/README.md |
| `MQTTstuff.ino` | MQTT | docs/api/MQTT.md, docs/api/openapi.yaml, docs/guides/MQTT_LWT.md |
| `restAPI.ino` | REST API | docs/api/openapi.yaml, docs/api/README.md |
| `OTGW-Core.ino` | OpenTherm core | docs/api/MQTT-message-id-echo-audit.md (if message-table change), API docs |
| `SAT*.ino` (`SATcontrol.ino`, `SATcycles.ino`, `SATpid.ino`, `SATpressure.ino`, `SATweather.ino`) | SAT thermostat | API docs, relevant `docs/features/` or `docs/fixes/` notes |
| `sensors_ext.ino` | Sensors | docs/api/DALLAS_SENSOR_LABELS_API.md, docs/api/openapi-dallas-sensors.yaml |
| `settingStuff.ino`, `OTGW-firmware.h` | Settings/State | API docs (settings schema sections), `docs/api/MQTT.md` (if topic shape change) |
| `data/index*.html`, `data/*.js`, `data/*.css` | Web UI | docs/api/WEBSOCKET_FLOW.md (if WebSocket change), `docs/api/WEBSOCKET_QUICK_REFERENCE.md` |
| `webSocketStuff.ino` | WebSocket | docs/api/WEBSOCKET_FLOW.md, docs/api/WEBSOCKET_QUICK_REFERENCE.md |
| `build.py`, `evaluate.py`, top-level `*.sh`/`*.bat` | Build/QA | docs/guides/BUILD.md (if build flow change) |
| New ADR added under `docs/adr/` | Architecture | always: confirm cross-references in `docs/adr/README.md` if it exists |
If `--scope full` or six or more subsystems changed, treat all docs as affected.
**Output of Phase 1:** an ordered list of affected doc areas, the PREV_TAG hash, and a categorized commit summary. Pass this to Phase 2 verbatim. Do NOT wait for user confirmation in standalone mode.
---
## Phase 2: Plan as a backlog task
Before any documentation is written, capture the run as one backlog task.
**Create the task** via `backlog task create`:
```bash
backlog task create "docs: update for changes since <PREV_TAG>" \
-s "In Progress" \
-a @claude \
-l docs,update-docs \
-d "<paste Phase 1 output verbatim>" \
--ac "API documentation (openapi.yaml, README.md, MQTT.md, ...) updated" \
--ac "Cleanup phase complete (archive old releases, move misplaced files)"
```
Add one AC per affected area in execution order:
1. API documentation (only if API changed)
2. Subsystem-specific guides under `docs/guides/` (only if affected)
3. Feature / fix notes under `docs/features/` or `docs/fixes/` (only if a feature shipped or a bug fix landed in this window)
4. Cleanup phase
5. (--release only) Release documents generated (`RELEASE_NOTES_<v>.md`, `RELEASE_GITHUB_<v>.md`, `BREAKING_CHANGES.md` update, `README.md` What's New)
6. (--release only) `CHANGELOG.md` updated with new version section
**Record the assigned `TASK-NNN` ID** returned by the CLI. Phase 5's commit message uses it; the commit-msg hook may block the commit if the task file is not staged (the bump-prerelease hook on dev doesn't enforce TASK-NNN today, but stage the task file regardless for traceability).
---
## Phase 3: Sequential execution
**Exactly one subagent runs at a time.** When it finishes, update the backlog task and start the next.
For each AC in order:
1. Spawn ONE subagent in the background (`run_in_background=true`) with the relevant prompt template (3A / 3B / 3C below).
2. Wait for the completion notification. Do not spawn the next subagent before this notification arrives.
3. Read the agent's summary. If it reports errors, `backlog task edit <id> --append-notes "..."` with the failure detail and either retry once with a corrected prompt or escalate to the user.
4. On success: `backlog task edit <id> --check-ac <N>` to tick the AC, and `--append-notes "..."` with a one-line summary of what was changed.
5. Move to the next AC.
### 3A: API documentation subagent
Single subagent, only if REST or MQTT or WebSocket changed.
Files in scope: `docs/api/openapi.yaml`, `docs/api/README.md`, `docs/api/MQTT.md`, `docs/api/WEBSOCKET_FLOW.md` (only if WebSocket changed), `docs/api/WEBSOCKET_QUICK_REFERENCE.md`, `docs/api/DALLAS_SENSOR_LABELS_API.md` (only if Dallas API changed), `docs/api/openapi-dallas-sensors.yaml`, `docs/api/MQTT-message-id-echo-audit.md` (only if message-table change).
**Prompt template:**
> Read the current docs in `docs/api/`. Read the git diff: `git diff [PREV_TAG]..HEAD -- src/OTGW-firmware/restAPI.ino src/OTGW-firmware/MQTTstuff.ino src/OTGW-firmware/webSocketStuff.ino`. Update the affected API docs to match the current implementation. For openapi.yaml: ensure every endpoint present in `kV2Routes[]` in `restAPI.ino` has a spec entry. Add new endpoints, remove removed ones, update changed response schemas. For MQTT.md: verify topic paths and payload formats match the current `MQTTstuff.ino` publish calls. For WebSocket docs: verify event types and payload structure against `webSocketStuff.ino`. Report endpoints / topics / event types added, removed, and changed.
### 3B: Guides / features / fixes subagent
Single subagent. Only if a build-flow / WiFi / MQTT-LWT or similar operational change shipped in this window, OR a feature / fix note belongs in `docs/features/` or `docs/fixes/`.
**Prompt template:**
> Read the relevant files under `docs/guides/`, `docs/features/`, `docs/fixes/` listed in the Phase 1 affected-areas list. Read the git diff: `git diff [PREV_TAG]..HEAD -- <relevant source files>`. Update the docs to reflect what actually changed. Preserve all existing content that is still correct. Targeted updates only, no rewrites from scratch. Report which files and which sections were touched, and flag anything that needed an architectural decision rather than a doc edit.
### 3C: Release documents (`--release` mode only)
Five sub-ACs, executed sequentially in order.
**3C-1: Gather changes and contributors.** Single subagent.
```bash
git log $PREV_TAG..HEAD --oneline | grep -v "CI: update version.h"
```
> Categorize each commit into: new feature, bug fix, internal improvement, breaking change. Scan `docs/adr/` for ADRs added or modified since `[PREV_TAG]`. Pull contributors from three sources sequentially: (1) `gh pr list --state merged --search "merged:>[PREV_DATE]" --json author,title --jq '.[] | "\(.author.login): \(.title)"'`. (2) Discord `#beta-testing` (channel `914498730001072149`) since `[PREV_DATE]`. (3) Discord `#devs-esp-firmware` (channel `924989767966425158`). Strip trailing digits from Discord usernames. Exclude bot IDs and maintainer `384411356616720384`. Output a structured commit-classification table and contributor list.
**3C-2: Generate `RELEASE_NOTES_<version>.md`** at repo root. Single subagent.
> Write `RELEASE_NOTES_<version>.md` with sections: release summary (2-3 sentences), what's new (features grouped by subsystem), bug fixes, breaking changes (always explicit, either "none" or list), upgrade notes, known issues, contributors. Use the categorized commit list from 3C-1. English. No em dashes. No emojis. If a `docs/process/RELEASE_PROCESS.md` template exists, follow its structure; otherwise mirror the most recent prior `docs/releases/RELEASE_NOTES_*.md` for shape.
**3C-3: Generate `RELEASE_GITHUB_<version>.md`** at repo root. Single subagent.
> Concise GitHub release body. Sections: short intro (one sentence), highlights (bullet list, max eight items), bug fixes (bullet list), upgrade notes (only if needed), thank you (shoutout to most active contributor, bullet list of others, Discord invite link). English. No em dashes.
**3C-4: Update `docs/BREAKING_CHANGES.md`.** Single subagent.
> Prepend a new version section to `docs/BREAKING_CHANGES.md`. Explicitly state whether there are breaking changes for this version: either "None" or a list. Read the existing file first to match its format.
**3C-5: Update `README.md` What's New section.** Single subagent.
> In `README.md`: demote the current "What's New in v<prev>" section to "What was new in v<prev>". Add a new "What's New in v<version>" section with four to six bullet highlights drawn from `RELEASE_NOTES_<version>.md`.
**3C-6: Update `CHANGELOG.md`** at repo root. Single subagent.
> Prepend a new version section to `CHANGELOG.md` following the [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) format. Read the current file first. Use the categorized commit list from 3C-1 to populate the sections. Only include sections (Added/Changed/Fixed/Removed/Deprecated/Security) that have content. Breaking changes go under Changed or Removed with a "(breaking)" note. Move the content from `[Unreleased]` to the new version section. Add a new empty `[Unreleased]` section at the top. Add a version comparison link at the bottom of the file pointing to the GitHub release tag. No em dashes. No emojis. English only.
---
## Phase 4: Docs folder cleanup
Inline shell operations, no subagents. Always runs, regardless of mode. Idempotent.
### 4A: Archive old release notes
If a `docs/releases/` directory exists and has more than ten release-note files, move the oldest into `docs/releases/archive/`, keeping the four newest in place.
```bash
ls docs/releases/RELEASE_NOTES_*.md 2>/dev/null | sort | head -n -4
ls docs/releases/RELEASE_GITHUB_*.md 2>/dev/null | sort | head -n -4
```
### 4B: Move misplaced files
Identify and move release documents from the repo root to `docs/releases/`:
```bash
ls RELEASE_NOTES_*.md RELEASE_GITHUB_*.md GITHUB_RELEASE_*.md 2>/dev/null
```
Exception: the CURRENT release's documents stay at root during the release phase, then move after publication.
### 4C: Verify docs/archive
Check `docs/*.md` for clearly outdated files (version-specific or superseded) and move them to `docs/archive/`. `BREAKING_CHANGES.md` always stays at root.
---
## Phase 5: Verify, finalize, commit
After all ACs are checked and cleanup is done:
1. `git diff --name-only` to see what changed.
2. Append a final summary to the backlog task: `backlog task edit <id> --final-summary "<one paragraph: areas updated, contributors counted, anything notable>"`.
3. Flip the task to `Done`: `backlog task edit <id> -s Done`.
4. Stage all docs PLUS the task file (the commit-msg hook on dev does not currently enforce TASK-NNN, but stage the task file regardless for traceability):
```bash
git add docs/ README.md CHANGELOG.md backlog/tasks/task-<NNN>-*.md
```
5. Commit with the task ID in the message:
```
docs: update documentation for changes since <PREV_TAG> (TASK-<NNN>)
```
6. **Standalone mode:** `git push origin dev` (allowed under the standing push permission documented in `CLAUDE.md`; docs-only commits skip the firmware build / evaluator gates per the same policy).
7. **Release mode:** do NOT push. The `/release` skill commits and pushes everything together.
If `git diff --name-only` returns empty after Phase 3, skip the commit and report "no documentation changes detected." Still flip the backlog task to `Done` with the final-summary noting "no changes required."
---
## Integration with /release
The `/release` skill (when present on dev) calls this workflow in its docs phase. When called from `/release`:
- Pass `--release <version>` so Phase 3C runs.
- Phase 5 commit is skipped (release skill commits everything together with the version bump).
- The release skill's review checkpoint reviews the generated release documents before commit.
- The sequential model still applies in `--release` mode: Phase 3C is itself five sequential subagents, not a parallel fan-out.
---
## Important rules
- **Sequential and backlog-tracked.** Exactly one subagent at a time. Every doc area is its own AC.
- **One task per run.** Every standalone `/update-docs` invocation creates a NEW backlog task; do not reuse a previous task.
- **Read before writing.** Every subagent must read the current version of a file before updating it.
- **Scope discipline.** Only update what actually changed. Do not rewrite docs for unchanged subsystems.
- **Preserve structure.** Keep existing heading levels, table formats, file organization.
- **Accuracy over completeness.** A correct partial update beats a comprehensive inaccurate one.
- **OpenAPI matches implementation.** Always verify endpoints against `kV2Routes[]` in `restAPI.ino`.
- **Backlog CLI only on dev.** Never use `mcp__backlog__task_*`; use `backlog task ...` per the CLAUDE.md project policy.
- **Commit and push at end of standalone runs.** Never leave doc changes uncommitted.
- **Release-mode commit is owned by /release.** Do not commit from inside `/update-docs` when invoked from the release skill.
================================================
FILE: .codex
================================================
================================================
FILE: .copilot-tracking/research/20260306-mqtt-json-refactor-research.md
================================================
<!-- markdownlint-disable-file -->
# Task Research Notes: MQTT command matching and JSON escape declaration cleanup
## Research Executed
### File Analysis
- src/OTGW-firmware/MQTTstuff.ino
- Verified the recent topic-matching change is isolated to `handleMQTTcallback()`, using `setcmds[] PROGMEM`, `MQTT_set_cmd_t`, and inline PROGMEM reads to accept either long MQTT command names or two-letter OTGW commands.
- src/OTGW-firmware/jsonStuff.ino
- Verified `escapeJsonStringTo()` is defined at file top and currently has no shared declaration except a local forward declaration added in `settingStuff.ino`.
- src/OTGW-firmware/settingStuff.ino
- Verified `writeJsonStringKV()` is the only current caller of `escapeJsonStringTo()` and it relies on global scratch buffer `cMsg` to avoid heap allocation.
- src/OTGW-firmware/OTGW-firmware.h
- Verified this is the existing shared declaration point for cross-module helpers and late-defined `.ino` functions that Arduino auto-prototype generation may miss.
- src/OTGW-firmware/OTGW-firmware.ino
- Verified the sketch includes `OTGW-firmware.h` from the main `.ino`, so declarations placed there are visible after sketch concatenation.
- docs/adr/ADR-002-modular-ino-architecture.md
- Verified accepted guidance that multi-file `.ino` sketches share one translation unit but still require explicit declarations to avoid hidden compile-order dependencies.
- docs/adr/ADR-004-static-buffer-allocation.md
- Verified this refactor must preserve static buffers, bounded copies, and no new `String` usage in hot paths.
- docs/adr/ADR-006-mqtt-integration-pattern.md
- Verified MQTT command handling is part of an accepted MQTT integration pattern and should stay buffer-bounded and lightweight.
- docs/adr/ADR-009-progmem-string-literals.md
- Verified PROGMEM use is mandatory for string literals and `_P` functions should remain in place for PROGMEM comparisons.
### Code Search Results
- escapeJsonStringTo|setcmds|MQTT_settopic|two-letter|long command|forward declaration
- Found the relevant edits only in `src/OTGW-firmware/MQTTstuff.ino`, `src/OTGW-firmware/jsonStuff.ino`, `src/OTGW-firmware/settingStuff.ino`, and existing shared declaration patterns in `src/OTGW-firmware/OTGW-firmware.h`.
- ^void [A-Za-z0-9_]+\([^;]*\);
- Found local forward declarations in `MQTTstuff.ino`, `settingStuff.ino`, and `outputs_ext.ino`, confirming the codebase already uses explicit declarations when `.ino` auto-prototypes are unreliable or readability benefits.
- escapeJsonStringTo\(|escapeJsonString\(
- Found `escapeJsonStringTo()` defined once in `jsonStuff.ino` and called once in `settingStuff.ino`; `escapeJsonString()` remains separate and is only used in `jsonStuff.ino` file-writing helpers.
- setcmds\[|MQTT_set_cmd_t|nrcmds|s_otgw_|s_cmd_
- Found all MQTT command metadata in `MQTTstuff.ino` with long names and OTGW two-letter commands stored as PROGMEM tables and consumed only by `handleMQTTcallback()`.
### External Research
- #githubRepo:"arduino/arduino-cli sketch build process concatenated .ino files starting with file matching folder name then alphabetical order auto-generated prototypes documentation"
- Verified from Arduino CLI build-process docs/source that sketch preprocessing concatenates the main `.ino` first, then other `.ino` files alphabetically, adds `#include <Arduino.h>`, and attempts auto-generated prototypes that can fail for complex cases; manual declarations remain the documented fallback.
- #fetch:https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
- Verified ESP8266 `PROGMEM`, `PSTR()`, `F()`, and `_P` string helpers are the correct way to keep literals in flash and compare/read flash-backed strings safely.
### Project Conventions
- Standards referenced: `.github/copilot-instructions.md`, ADR-002, ADR-004, ADR-006, ADR-009
- Instructions followed: modular `.ino` architecture, explicit shared declarations for cross-module helpers, PROGMEM-resident command tables, bounded buffers, no architecture-significant changes without ADR
## Key Discoveries
### Project Structure
This workspace uses a modular Arduino sketch layout under `src/OTGW-firmware/`. The main file `OTGW-firmware.ino` includes `OTGW-firmware.h`, then Arduino preprocessing merges the remaining `.ino` files into a single generated `.cpp` in this order: main file first, then other `.ino` files alphabetically. For the touched files that means `jsonStuff.ino` is merged before `MQTTstuff.ino` and before `settingStuff.ino`. Even though that currently makes `escapeJsonStringTo()` visible before `settingStuff.ino`, ADR-002 and Arduino CLI docs both warn against relying on hidden merge order or auto-generated prototypes for maintainability.
### Implementation Patterns
- MQTT command metadata is centralized in `MQTTstuff.ino` as `const MQTT_set_cmd_t setcmds[] PROGMEM`, with each row holding long topic name, OTGW two-letter command, and command type.
- `handleMQTTcallback()` currently performs topic parsing, then scans `setcmds[]` inline and reads `setcmd`, `otgwcmd`, and `ottype` from PROGMEM on each loop iteration.
- The recent two-name matching enhancement is correct but embedded directly in the callback, which now mixes namespace parsing, command lookup, raw-vs-typed command branching, and queue submission.
- `escapeJsonStringTo()` is defined in `jsonStuff.ino`, but its declaration was added locally to `settingStuff.ino` instead of the shared header already used for cross-module declarations.
- `writeJsonStringKV()` intentionally uses the global `cMsg` scratch buffer to satisfy ADR-004 static-buffer guidance.
### Complete Examples
```cpp
// Source: src/OTGW-firmware/MQTTstuff.ino
for (i=0; i<nrcmds; i++){
// Read setcmd and otgwcmd pointers from Flash
PGM_P pSetCmd = (PGM_P)pgm_read_ptr(&setcmds[i].setcmd);
PGM_P pOtgwCmd = (PGM_P)pgm_read_ptr(&setcmds[i].otgwcmd);
// Match either the long command name (e.g. "setpoint") or the short raw command (e.g. "TT")
if ((strcasecmp_P(token, pSetCmd) == 0) || ((strcasecmp_P(token, pOtgwCmd) == 0) && strlen_P(pOtgwCmd) > 0)){
//found a match
// Read ottype from Flash
PGM_P pOtType = (PGM_P)pgm_read_ptr(&setcmds[i].ottype);
if (strcasecmp_P("raw", pOtType) == 0){
snprintf_P(otgwcmd, sizeof(otgwcmd), PSTR("%s"), msgPayload);
addOTWGcmdtoqueue((char *)otgwcmd, strlen(otgwcmd), true);
} else {
char cmdBuf[10];
strncpy_P(cmdBuf, pOtgwCmd, sizeof(cmdBuf));
cmdBuf[sizeof(cmdBuf)-1] = 0;
snprintf_P(otgwcmd, sizeof(otgwcmd), PSTR("%s=%s"), cmdBuf, msgPayload);
addOTWGcmdtoqueue((char *)otgwcmd, strlen(otgwcmd), true);
}
break;
}
}
```
### API and Schema Documentation
- MQTT command topic contract is `<Base_Topic>/set/<Node_ID>/<command>`.
- The current implementation accepts both long topic names (for example `setpoint`) and two-letter OTGW command names (for example `TT`) by looking up both `setcmd` and `otgwcmd` from `setcmds[]`.
- The `command` entry is a special raw-command path whose `otgwcmd` is the empty PROGMEM string and whose `ottype` is `raw`.
- `escapeJsonStringTo(const char* src, char* dest, size_t destSize)` is a bounded escaping helper that writes into caller-owned storage and returns results via `dest` only.
### Configuration Examples
```text
Sketch merge order relevant here:
1. OTGW-firmware.ino
2. FSexplorer.ino
3. handleDebug.ino
4. helperStuff.ino
5. jsonStuff.ino
6. MQTTstuff.ino
7. OTGW-Core.ino
8. outputs_ext.ino
9. restAPI.ino
10. s0PulseCount.ino
11. sensors_ext.ino
12. settingStuff.ino
13. versionStuff.ino
14. webhook.ino
15. webSocketStuff.ino
```
### Technical Requirements
- Preserve `setcmds[] PROGMEM` and `_P` comparison functions; do not introduce RAM copies for the table itself.
- Preserve the empty-short-command guard for the raw `command` topic entry; otherwise the blank `otgwcmd` row can become a false match path.
- Preserve case-insensitive behavior because the current code uses `strcasecmp()`/`strcasecmp_P()` for both long and short command tokens.
- Keep stack and static buffer usage bounded; no new `String` use should be introduced into MQTT callback hot paths.
- The declaration move for `escapeJsonStringTo()` is not architecturally significant; no new ADR appears required.
- There is one touched-area convention conflict to note: `escapeJsonStringTo()` currently uses normal C string literals like `"\\n"` and `"\\\\"`, which does not align with the repo’s strict ADR-009 PROGMEM guidance. That is outside the approved cleanup scope unless explicitly bundled.
- No current compile errors were reported by the editor for the touched files, and the latest recorded `python build.py` completed successfully.
## Recommended Approach
Use one small helper extraction in `MQTTstuff.ino` plus one shared declaration move in `OTGW-firmware.h`.
For MQTT readability, the minimal codebase-consistent refactor is to extract the inline `setcmds[]` scan into a file-local helper that encapsulates PROGMEM reads and dual-name matching, returning the resolved `otgwcmd` and `ottype` for the caller. This keeps the command table and callback in the same file, avoids new headers or data-structure churn, and reduces `handleMQTTcallback()` to topic parsing plus command execution. A helper shaped like `static bool findMqttSetCommand(const char* token, PGM_P& otgwCmd, PGM_P& otType)` or equivalent best matches existing file-local utility style.
For the JSON helper cleanup, the minimal change is to move `escapeJsonStringTo()`'s declaration from `settingStuff.ino` into `OTGW-firmware.h`, beside other cross-module forward declarations. That removes the local one-off prototype and aligns with the existing shared-header pattern already used for `readSettings()`, `writeSettings()`, `updateSetting()`, and other later-defined `.ino` functions.
This approach avoids relying on sketch merge order, does not change public behavior, and stays inside accepted ADR boundaries.
## Implementation Guidance
- **Objectives**: simplify `handleMQTTcallback()` without changing topic behavior; centralize `escapeJsonStringTo()` declaration in the normal shared header location.
- **Key Tasks**: extract one file-local MQTT command lookup helper; replace the inline lookup call site; add one declaration to `OTGW-firmware.h`; remove the local declaration from `settingStuff.ino`.
- **Dependencies**: `setcmds[] PROGMEM`, `MQTT_set_cmd_t`, `_P` string helpers from `pgmspace.h`, shared globals from `OTGW-firmware.h`, Arduino sketch preprocessing behavior.
- **Success Criteria**: long-name MQTT topics and two-letter MQTT topics both still enqueue identical OTGW commands; raw `command` topic behavior is unchanged; `escapeJsonStringTo()` is declared only once in shared header scope; build remains clean with no new warnings or memory-pattern regressions in touched files.
================================================
FILE: .copilot-tracking/research/20260306-ui-fixes-otmonitor-panel-spacing-research.md
================================================
<!-- markdownlint-disable-file -->
# Task Research Notes: OT monitor panel fill and command spacing
## Research Executed
### File Analysis
- src/OTGW-firmware/data/index.html
- The OT monitor UI places the command bar (`.ot-cmd-bar`) directly above the black log container and defines the gateway mode indicator in the same panel.
- src/OTGW-firmware/data/index.js
- `refreshOTmonitor()` creates each OT monitor row with `.otmonrow`, adds `.no-data-row` when `entry.epoch == 0`, stores `entry.epoch` in a hidden input, and removes `.no-data-row` once values arrive.
- `refreshDevTime()` updates `isPSmode` from `/v2/device/time`, and `applyPSmodeState()` keeps OT monitor polling active in PS=1 mode.
- `sendOTGWcommand()` wires the short command input/button/status, but there is no JS-side spacing logic for the command bar.
- src/OTGW-firmware/data/index.css
- `.otmonrow` is always rendered with a filled background in the light theme, but there is no `.no-data-row`, `.ot-cmd-bar`, `.ot-cmd-input`, or `.ot-cmd-status` rule in the stylesheet.
- src/OTGW-firmware/data/index_dark.css
- The dark theme mirrors the same issue: `.otmonrow` always has a filled background and there are no command-bar-specific spacing/layout rules.
- src/OTGW-firmware/src/OTGW-firmware/restAPI.ino
- `/v2/otgw/otmonitor` emits per-field `lastupdated` values as `epoch`, which the frontend already consumes as `entry.epoch`.
- src/OTGW-firmware/src/OTGW-firmware/OTGW-Core.ino
- `msglastupdated[]` is the authoritative timestamp store for OT values and is cleared when `PS=1` is detected, confirming that row freshness is intended to be timestamp-driven.
- docs/adr/ADR-037-gateway-mode-detection.md
- Gateway/monitor mode and PS=1 interaction are documented constraints; PS=1 suppresses time sync but the UI still needs to expose mode correctly.
- docs/adr/ADR-045-ps1-print-summary-parsing.md
- PS=1 summary parsing intentionally continues feeding the normal OT data pipeline, while WebSocket log display stays in summary mode.
- docs/adr/ADR-005-websocket-real-time-streaming.md
- WebSocket use is limited to OT streaming over `ws://`; frontend changes must preserve this model.
- docs/adr/ADR-025-safari-websocket-connection-management.md
- Frontend behavior must remain Safari-safe and avoid fragile browser-specific handling.
### Code Search Results
- `PS=1|summary|No UI updates`
- Found PS=1 handling in `src/OTGW-firmware/OTGW-Core.ino`, `src/OTGW-firmware/data/index.js`, and ADRs `ADR-037` / `ADR-045`.
- `no-data-row|epoch`
- Found `.no-data-row` only in `src/OTGW-firmware/data/index.js`; no matching CSS exists in either theme stylesheet.
- `ot-cmd-bar|ot-cmd-input|ot-cmd-status`
- Found only in `src/OTGW-firmware/data/index.html` and JS event handlers; no dedicated CSS exists in either theme stylesheet.
- `mode-status|gatewayMode`
- Found in `src/OTGW-firmware/data/index.js` and both theme stylesheets, confirming gateway mode indicator styling is separate from OT row freshness styling.
- `sendOTmonitorV2|msglastupdated`
- Found in `src/OTGW-firmware/restAPI.ino` and `src/OTGW-firmware/OTGW-Core.ino`, confirming the backend already exposes the timestamps needed for a freshness-based UI.
### External Research
- #githubRepo:"mdn/browser-compat-data css.properties.gap flex_context api.WebSocket readyState"
- MDN browser-compat-data models `gap` with flex-specific compatibility history, which matches the project instruction to validate browser support when using layout spacing changes across Safari/Firefox/Chrome.
- #fetch:https://developer.mozilla.org/en-US/docs/Web/CSS/gap
- MDN documents `gap` as valid for flex/grid/multi-column layouts; flex-layout support is listed as Safari 14.1+, Chrome 84+, Firefox 63+, which fits this project's browser support floor.
- #fetch:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
- MDN confirms `WebSocket.readyState` values and broad browser support, reinforcing the existing instruction that any WebSocket-related UI changes must continue using ready-state-safe logic.
### Project Conventions
- Standards referenced: frontend browser compatibility rules in `.github/copilot-instructions.md`; local-network `ws://` / no-HTTPS constraints; OT monitor data freshness via `epoch`; dual theme parity across `index.css` and `index_dark.css`.
- Instructions followed: `.github/instructions/adr.coding-agent.instructions.md`, `.github/instructions/adr.code-review.instructions.md`, `.github/copilot-instructions.md`, `.github/skills/adr/SKILL.md`.
## Key Discoveries
### Project Structure
The relevant UI is concentrated in the LittleFS frontend bundle under `src/OTGW-firmware/data/`:
- `index.html` defines the OT monitor panel, command bar, log container, statistics tab, and graph tab.
- `index.js` polls `/v2/otgw/otmonitor` and `/v2/device/time`, manages PS=1 state, and dynamically builds OT monitor rows from API data.
- `index.css` and `index_dark.css` provide separate light/dark theme styles and must both be updated for consistent behavior.
The data source is server-side:
- `restAPI.ino` exposes `/v2/otgw/otmonitor` with per-field `epoch` timestamps.
- `OTGW-Core.ino` updates `msglastupdated[]` on live OT data and clears it when PS=1 mode begins.
### Implementation Patterns
The frontend already contains the right semantic hook for issue 1 but never completes it visually:
- `refreshOTmonitor()` adds `.no-data-row` when `entry.epoch == 0`.
- It later removes `.no-data-row` when the field starts receiving data.
- There is no corresponding CSS rule, so the base `.otmonrow` background remains fully filled from first render in both themes.
For issue 2, the command bar markup exists, but spacing is accidental/default:
- `.ot-cmd-bar`, `.ot-cmd-input`, and `.ot-cmd-status` exist in HTML only.
- Neither theme stylesheet defines margin, gap, flex wrapping, or separation from `.ot-log-container`.
- The visual closeness to the black log panel is therefore a missing-style issue, not a JS layout bug.
### Complete Examples
```javascript
// Existing row lifecycle in refreshOTmonitor()
if ((document.getElementById("otmon_" + entry.name)) == null) {
var rowDiv = document.createElement("div");
rowDiv.setAttribute("class", "otmonrow");
if (entry.epoch == 0) rowDiv.classList.add('no-data-row');
var epoch = document.createElement("INPUT");
epoch.setAttribute("type", "hidden");
epoch.setAttribute("id", "otmon_epoch_" + entry.name);
epoch.value = entry.epoch;
rowDiv.appendChild(epoch);
// ... append name/value/unit columns
} else {
var update = document.getElementById("otmon_" + entry.name);
if (update.parentNode) {
if (entry.epoch == 0) {
update.parentNode.classList.add('no-data-row');
} else {
update.parentNode.classList.remove('no-data-row');
}
}
}
// Existing command bar markup in index.html
// <div class="ot-cmd-bar">
// <input id="otCmdInput" class="ot-cmd-input" ... />
// <button id="btnSendCmd" class="btn-log-control">Send</button>
// <span id="otCmdStatus" class="ot-cmd-status"></span>
// </div>
```
### API and Schema Documentation
- `/v2/otgw/otmonitor` is the OT monitor data source.
- Each field includes a freshness timestamp (`epoch`) derived from backend `lastupdated` values.
- `refreshDevTime()` reads `/v2/device/time` and updates `isPSmode` from `devtime.psmode`.
- `OTGW-Core.ino` clears `msglastupdated[]` when `PS=1` is detected, so zero timestamps are an intentional empty-state signal.
### Configuration Examples
```css
/* Current state in both themes */
.otmonrow {
background: lightblue; /* dark theme uses #2c2c2e */
display: flex;
align-items: center;
padding: 2px 0;
}
.ot-log-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
/* Missing today in both themes:
.no-data-row
.ot-cmd-bar
.ot-cmd-input
.ot-cmd-status
*/
```
### Technical Requirements
- Issue 1 must respect PS=1 behavior documented in ADR-037 and ADR-045: PS mode changes how data arrives, but OT monitor values still flow through the standard pipeline and carry timestamps.
- Any UI fix should rely on the existing timestamp/epoch mechanism rather than infer freshness from current value text.
- Any spacing/layout fix must be added to both `index.css` and `index_dark.css` to avoid theme regressions.
- Frontend changes must remain compatible with Chrome, Firefox, and Safari, per `.github/copilot-instructions.md`.
- WebSocket-related behavior must remain `ws://`-based and ready-state-safe; no HTTPS/WSS or browser-specific hacks.
## Recommended Approach
Use the existing `entry.epoch` / `msglastupdated[]` pipeline as the single source of truth for OT monitor row fill state, and complete the missing frontend styling in both themes.
Why this is the strongest fit:
- The backend already exposes the exact freshness signal needed.
- The frontend already toggles a semantic class (`.no-data-row`) from that signal.
- The current bug appears to be an incomplete UI implementation rather than a missing data source.
- The command-spacing issue is likewise a missing stylesheet concern, not an API or command-queue problem.
This keeps the fix localized to UI assets unless user-visible “timestamp-based fill” means a more advanced progressive-fill animation than the current binary seen/unseen behavior. If the desired effect is a gradual fill proportional to recency, that expectation is not yet encoded anywhere in the current code and needs clarification.
## Implementation Guidance
- **Objectives**: Restore a true empty-state appearance for OT monitor rows until data has been observed; add explicit separation between the command bar and the log container.
- **Key Tasks**: Add missing row-state CSS for both themes; add explicit command-bar layout/spacing styles for both themes; verify PS=1 transitions still clear/repopulate row freshness correctly.
- **Dependencies**: Existing `epoch` values from `/v2/otgw/otmonitor`; `msglastupdated[]` updates in firmware; theme-specific stylesheets; browser compatibility rules from project instructions.
- **Success Criteria**: Rows do not appear fully filled on initial load when their `epoch` is zero; rows visually transition once data is seen; the short command area has clear breathing room above the black log panel in both light and dark themes; no conflict with PS=1 mode handling or Safari/Firefox/Chrome compatibility expectations.
================================================
FILE: .external-reviews/HANDOFF-claude-review-c-codebase-303Qj.md
================================================
# Code Review Handoff — OTGW-firmware
**Reviewer:** Claude (Opus 4.7, 1M context), `claude/review-c-codebase-303Qj`
**Date:** 2026-04-21
**Scope:** Full C/C++ review of `src/OTGW-firmware/` (~24k LoC), architecture + quality + dead-code.
This document is a durable handoff. The review was produced in a single agentic session by three parallel specialist agents (architecture, quality, dead-code) and synthesized into the prioritized findings below. The findings exist here so they are not lost with the session.
---
## How to use this document
**Option A — Recommended: turn findings into backlog tasks, then work them one at a time.**
```bash
# 1. Make sure the Backlog.md CLI is installed — see https://backlog.md
backlog --version
# 2. Run the Python script (cross-platform; works on Windows, macOS, Linux)
python scripts/create_review_backlog.py --dry-run # preview
python scripts/create_review_backlog.py # actually create ~23 tasks
# 3. Pick up work the standard way
backlog task list --plain -s "To Do"
backlog task <id> --plain
backlog task edit <id> -s "In Progress" -a @claude
```
Each generated task cites the file:line from this document and has concrete acceptance criteria, so Claude Code can pick a task up cold.
**Option B — Hand issues to Claude Code ad-hoc.** Paste the relevant P0/P1/P2 section below into a Claude Code prompt and say "work this". Acceptable for one-off fixes, not great for the multi-file architectural items.
---
## Executive verdict
Grade: **C+**. Shippable, functional, but brittle under any refactor.
Three themes dominate everything below:
1. **Re-entrancy by convention, not by construction.** `doBackgroundTasks()` yields. `feedWatchDog()` yields. Several globals (`ot_log_buffer`, `cmdqueue[]`, static `sourceTopic`) are "safe" only because humans remember not to yield between the write and the consumer. This is the single biggest latent-stability class in the codebase.
2. **OTGW-Core.ino is a 4.7k-line god module.** OpenTherm decode, MQTT throttle arrays, WebSocket fan-out, REST timestamps, PIC state machine, command queue — all live in one file with no seams. Every change has high blast radius.
3. **ADR-051 (settings/state split) is half-applied.** Stragglers like `mqttPublishAllowed`, `statusBurstActive`, `verifyWildcard[]` are plain statics. Completing ADR-051 is a precondition for any module extraction.
Dead code is the least of the problems — maybe 50 lines to delete.
---
## P0 — Crash / stability / safety
| # | Finding | File:Line |
|---|---|---|
| **P0-1** | `ot_log_buffer[512]` is shared between `AddLog*` macros, Telnet, and WebSocket broadcast. `sendEventToWebSocket_P()` calls `feedWatchDog()` mid-flight. Re-entry via `doBackgroundTasks()` can corrupt the buffer. Ownership is documented as a comment, not enforced. | `OTGW-Core.ino:97`, `:3928–3930` |
| **P0-2** | `cmdqueue[]` / `cmdQueueSize` is mutated by 4+ callers (MQTT, REST, SAT, SATcycles) and by `processSerialTwo()` which yields. No guard. Under load, a command can be reordered or dropped. | `OTGW-Core.ino:~401` and every `addOTWGcmdtoqueue()` call site |
| **P0-3** | `static char sourceTopic[MQTT_TOPIC_MAX_LEN]` is filled with `snprintf()` then passed to `sendMQTTData()`, which yields. Assumption that nothing else rewrites the buffer is enforced by convention only. | `MQTTstuff.ino:1224–1228` |
| **P0-4** | `FSexplorer.ino:132` checks `strstr_P(lineBuf, PSTR(...))` then separately calls `strstr(lineBuf, "...")` on line 133 and dereferences the result (`*pos = '\0'`) **with no null check**. The two searches can disagree; result is a hard crash on an edge-case `index.html`. | `FSexplorer.ino:132–146` |
| **P0-5** | `SATcontrol.ino` bit-bangs OT status with magic masks (`& 0x08`). OTGW-Core has enum-based parsers elsewhere. A one-bit mistake silently changes control behavior. | `SATcontrol.ino:257, 406, 593` |
---
## P1 — Correctness / design
| # | Finding | File:Line |
|---|---|---|
| **P1-1** | `OTGW-Core.ino` mixes OT decode, MQTT throttle arrays, WebSocket dispatch, REST update timestamps, PIC-settings state machine, command queue — 4739 lines, no seams. | `OTGW-Core.ino` (entire) |
| **P1-2** | ADR-051 incomplete: `mqttPublishAllowed`, `statusBurstActive`, `statusBurstPublishCount`, `verifyActive`, `verifyWildcard[128]` are plain file-statics, not `state.mqtt.*` members. | `MQTTstuff.ino:60–184` |
| **P1-3** | REST API reads `OTcurrentSystemState` directly, bypassing the MQTT throttle/gate logic. Two presenters, two truths. | `restAPI.ino` (multiple) |
| **P1-4** | SAT publishes `TSet` via `satPublishMQTT()` in the same tick OTGW also publishes `TSet`. No de-dup — the same topic emits two conflicting messages under burst. | `SATcontrol.ino:~1730`; `MQTTstuff.ino` |
| **P1-5** | `String dBmtoQuality()` returns `String` — hot-path heap churn (WiFi quality). | `helperStuff.ino:646–656` |
| **P1-6** | Three `String` allocations during PIC upgrade control path. | `OTGW-Core.ino:4668–4670` |
| **P1-7** | `httpServer.arg("v").c_str()` passed to `strcmp()` — pointer into a temporary. Works only because rvalue outlives the call statement; fragile. | `FSexplorer.ino:98, 187, 201` |
| **P1-8** | `EVALBOOLEAN(x)` macro evaluates `x` three times and lacks parens around `x` — silent side-effect bug. | `OTGW-firmware.h:94` |
| **P1-9** | Two independent JSON escapers: `escapeJsonStringTo()` (public) and `static jsonEscape()` (private). Identical logic, guaranteed to diverge. | `jsonStuff.ino:14` / `OTGW-Core.ino:4342` |
| **P1-10** | `strlcpy_P` inline redefined despite the header guard in `OTGW-firmware.h:22`. | `mqtt_configuratie.cpp:1665` |
| **P1-11** | `snprintf(buffer, ..., "%d", v)` with RAM format string where `snprintf_P(PSTR(...))` is expected. | `MQTTstuff.ino:1182, 1188, 1227` |
| **P1-12** | `strcmp(state.pic.sDeviceid, "unknown")` — literal in RAM instead of `strcmp_P(..., PSTR("unknown"))`. | `OTGW-Core.ino:4600, 4683` |
---
## P2 — Quality / maintenance
- Deprecated v0 endpoints still live past their stated v1.3.0 removal date: `/api/firmwarefilelist`, `/api/listfiles` — `FSexplorer.ino:252–253`; handlers at `:283, :384`.
- Unreachable gas-consumption block guarded by `if (minCons > 0 && maxCons > 0)` where both are hard-coded `0.0f` — `SATcontrol.ino:2116–2119`.
- `static bool webhookInitialized` is written once, never read — `webhook.ino:17`.
- `evaluate.py` (785 lines of quality tooling) is **not wired into CI**. `.github/workflows/` has only two workflows, neither runs it.
- `tests/test_dallas_address.cpp` is orphaned — no Makefile/CMake/PlatformIO hook.
- Stale artifact directories: `.full-review/`, `.copilot-tracking/research/`.
- Commented-out debug lines scattered across: `helperStuff.ino:149–153, 331, 653`; `OTGW-firmware.ino:125, 183`; `restAPI.ino:760–761, 1076–1079`; `versionStuff.ino:25, 31, 41, 51–53`; `FSexplorer.ino:254`; `OTGW-Core.ino:2858`.
- Magic number `3` in `memcpy(value, buf+3, …)` — `OTGW-Core.ino:2814`. Needs `OT_CMD_VALUE_OFFSET`.
- Header `OTGW-firmware.h` pulls `Arduino.h`, `AceTime.h`, `SimpleTelnet.h`, `Wire.h`, `safeTimers.h`, `OTGWSerial.h`, `OTGW-Core.h`, `OneWire.h`, `DallasTemperature.h` into every `.ino` file — heavy preprocess cost per unit.
---
## Top-12 refactors, ranked by ROI
| Rank | Refactor | Effort | Payoff |
|---|---|---|---|
| 1 | Harden `ot_log_buffer` re-entrancy (P0-1) | S | Removes silent-corruption class |
| 2 | Extract MQTT throttle state into `state.mqtt` (P1-2) | M | Completes ADR-051; precondition for module split |
| 3 | Typed OT status accessors (P0-5) | S | Kills a bug class across SAT |
| 4 | Extract OT protocol from OTGW-Core.ino (P1-1) | L | Enables testability |
| 5 | Delete deprecated `/api/firmwarefilelist`, `/api/listfiles` (P2) | XS | Pure win |
| 6 | Collapse duplicate JSON escaper (P1-9) | XS | Removes drift risk |
| 7 | Replace `String dBmtoQuality()` with lookup table (P1-5) | XS | Hot-path heap removed |
| 8 | Fix `EVALBOOLEAN` macro (P1-8) | XS | Silent-bug prevention |
| 9 | Delete unreachable gas-consumption block (P2) | XS | Dead code |
| 10 | Remove `webhookInitialized` + commented-out debug lines (P2) | XS | Hygiene |
| 11 | Wire `evaluate.py` / `tests/` into CI or delete (P2) | S | Stops rot |
| 12 | Fix FSexplorer `strstr` null-deref (P0-4) | XS | Potential crash fix |
---
## Recommended sequencing
Do these in order — each unlocks the next:
1. **Crash-class first** — P0-1 (log buffer), P0-4 (null deref). Both are small, both are cheap, both are shippable as one PR.
2. **Complete ADR-051** — pull MQTT throttle flags into `state.mqtt` (P1-2). Until this is done, any attempt to split OTGW-Core.ino will leak globals across the new boundary.
3. **Typed OT status accessors** (P0-5, P1-4) — mechanical, touches many files, but trivially reviewable. Do before SAT refactor.
4. **Spring cleaning PR** — items 5, 6, 7, 8, 9, 10, 12 from the ranked table. All small, all independent, ship together.
5. **CI integration** — decide `evaluate.py` and `tests/` fate. Without CI there is no safety net for the next two items.
6. **Extract OT protocol from OTGW-Core.ino** (P1-1). Only after all of the above — this is the highest-risk change and must be done on a solid base.
---
## Creating the backlog tasks
The script at `scripts/create_review_backlog.py` creates ~23 backlog tasks (one per finding) with concrete acceptance criteria. Each task includes:
- Title with priority tag (P0/P1/P2 prefix).
- Description citing file:line from this document.
- `--ac` flags for verifiable outcomes.
- `--priority high|medium|low` matching the table above.
- `-l code-review,refactor,<category>` labels (`re-entrancy`, `architecture`, `quality`, `dedup`, `progmem`, `cleanup`).
**Usage:**
```bash
# Preview
python scripts/create_review_backlog.py --dry-run
# Create all
python scripts/create_review_backlog.py
# Create only one category
python scripts/create_review_backlog.py --only re-entrancy
```
**On Windows:** the script uses `subprocess` with list-args (not shell strings) and auto-locates `backlog`, `backlog.cmd`, or `backlog.exe` via `shutil.which`. No PowerShell quoting gymnastics.
After creation, verify with:
```bash
backlog task list --plain -l code-review
```
---
## Closing a task (reminder from CLAUDE.md §7)
A task is Done only when:
1. All acceptance criteria checked via `backlog task edit <id> --check-ac N`.
2. All Definition of Done items checked via `backlog task edit <id> --check-dod N`.
3. `--final-summary` set (acts as the PR description).
4. Status set: `backlog task edit <id> -s Done`.
5. Build + `python evaluate.py` pass.
6. No regressions.
**Do not edit task `.md` files by hand.** The CLI is the only supported interface (CLAUDE.md, top of file).
---
## Session artifacts
- Review branch: `claude/review-c-codebase-303Qj`
- This file: `HANDOFF.md`
- Task creation script: `scripts/create_review_backlog.py`
- Draft PR: created against `rvdbreemen/otgw-firmware` after push.
================================================
FILE: .external-reviews/README.md
================================================
# External Reviews
This directory holds review outputs produced **outside** the current session's own review pipeline (`.full-review/`).
External reviews are kept strictly separate — they are **not** merged into the in-flight consolidated report. They are processed as independent input and can be cross-referenced from a dedicated analysis document, but the Phase 1-5 pipeline in `.full-review/` reflects only the current session's own findings.
## Current contents
- `HANDOFF-claude-review-c-codebase-303Qj.md` — full-codebase review (~24k LoC) from branch `claude/review-c-codebase-303Qj`, dated 2026-04-21. Scope differs from `.full-review/`: that review is whole-codebase, `.full-review/` is branch 1.4.1 diff vs dev.
## Processing policy
1. Do not fold external findings into `.full-review/0X-*.md` files.
2. If a cross-check / overlap analysis is desired, produce it as a **separate** document (e.g., `cross-check-<name>.md` in this directory).
3. Preserve the external reviewer's wording verbatim — do not paraphrase their findings.
4. When an external claim conflicts with current code, verify against the tree before taking it at face value.
================================================
FILE: .full-review/00-scope.md
================================================
# Review Scope
## Target
Full branch review of `1.4.1` vs `dev` (base branch). This branch is the "heap-pressure reduction + MQTT discovery verification + time-boundary dispatcher" release candidate for OTGW-firmware 1.4.1.
- Commits in range: 14 (0cc5dd10 → deaddd85)
- Source files changed: ~20 C/C++/INO files, plus frontend JS/CSS and build helpers
- Documentation: ADR-062 (retained discovery verification), ADR-064 (time-boundary single-caller contract)
- Backlog: TASK-338 through TASK-351
## Themes
1. **Heap pressure reduction** during Home Assistant MQTT discovery drip
- Slower drip interval, wider HEAP_LOW backoff, fragmentation-aware publish gates, Status-burst cooldown
- Lower heap guard thresholds tuned on tester log data
2. **Cumulative heap diagnostics** with hourly MQTT publishing (drop/tier counters)
3. **MQTT discovery verification & republish** (on-demand + daily automatic)
4. **Time-boundary dispatcher refactor** (unify hourChanged / dayChanged / yearChanged under single-caller contract)
5. **Nightly restart refactor** to use hourChanged hook
6. **Version bump** 1.4.0 → 1.4.1-beta, build artefact hygiene (remove .gz from git)
## Files (source / docs / build)
### Source code (C/C++/INO)
- `src/OTGW-firmware/MQTTstuff.ino` — major changes (discovery drip, verification, cooldown)
- `src/OTGW-firmware/OTGW-firmware.ino` — time dispatcher refactor, nightly restart hook
- `src/OTGW-firmware/OTGW-firmware.h` — pending flags, cooldown state, discovery verification state
- `src/OTGW-firmware/OTGW-Core.ino` + `.h` — Status-burst cooldown hooks, version bump
- `src/OTGW-firmware/helperStuff.ino` — heap guard thresholds, drop counters
- `src/OTGW-firmware/restAPI.ino` — on-demand discovery verification endpoints
- `src/OTGW-firmware/handleDebug.ino` — debug hooks for discovery verification
- `src/OTGW-firmware/settingStuff.ino` — settings for discovery verification cadence
- `src/OTGW-firmware/mqtt_configuratie.cpp` — discovery verification hooks
- `src/OTGW-firmware/networkStuff.{h,ino}` — time-dispatcher callers
- Smaller edits: `FSexplorer.ino`, `jsonStuff.ino`, `outputs_ext.ino`, `s0PulseCount.ino`, `sensors_ext.ino`, `webSocketStuff.ino`, `webhook.ino` (mostly version bumps)
### Frontend
- `src/OTGW-firmware/data/index.js` — heap diagnostics UI, discovery verification UI
- `src/OTGW-firmware/data/index.css` / `index_dark.css` — minor
- `src/OTGW-firmware/data/FSexplorer.{css,html}`, `FSexplorer_dark.css`, `graph.js` — version-only
### Config / build
- `evaluate.py` — extended with new checks
- `src/OTGW-firmware/data/mqttha.cfg` — version bump
- `src/OTGW-firmware/data/version.hash`, `version.h` — version tracking
- Removed generated `.gz` artefacts from git (404f7a48)
### Docs
- `docs/adr/ADR-062-retained-discovery-verification.md` (Proposed)
- `docs/adr/ADR-064-time-boundary-single-caller-contract.md` (Proposed)
### Backlog
- 14 task files covering the changes (TASK-338 through TASK-351)
## Flags
- Security Focus: no (embedded LAN-only firmware; see ADR "HTTP/WS only")
- Performance Critical: yes (ESP8266, ~40KB RAM, heap pressure is the main theme)
- Strict Mode: no
- Framework: Arduino / ESP8266 (auto-detected)
## Platform constraints reviewers must remember
- ESP8266 with ~40KB usable RAM and a **4KB cooperative CONT stack**
- `doBackgroundTasks()` IS re-entrant (MQTTstuff.ino:1055 reads files while auto-configuring)
- `feedWatchDog()` at OTGW-Core.ino:403 has `yield()` commented out — no re-entrancy from watchdog
- PROGMEM pointers must match helpers: never pass PROGMEM to `printf %s`, `MQTTclient.write()`, `writeMqttChunk()`
- No HTTPS / WSS — plain HTTP/WS only (ADR)
- No `String` class in hot paths (ADR-004)
- Static buffers `mqttAutoCfgScratch` / `ot_log_buffer` have specific ownership rules
## Review Phases
1. Code Quality & Architecture
2. Security & Performance
3. Testing & Documentation
4. Best Practices & Standards
5. Consolidated Report
================================================
FILE: .full-review/01-quality-architecture.md
================================================
# Phase 1: Code Quality & Architecture Review
**Target**: branch `1.4.1` vs `dev` (14 commits, ~20 source files)
**Themes**: heap-pressure reduction, cumulative heap diagnostics, MQTT discovery verification, time-boundary dispatcher refactor
**Full reports**: `phase1a-code-quality.md` (40 KB) · `phase1b-architecture.md` (26 KB)
## Aggregate severity counts
| Severity | Code Quality (1A) | Architecture (1B) | Total |
|---|---|---|---|
| Critical | 0 | 1 | **1** |
| High | 4 | 3 | **7** |
| Medium | 9 | 5 | **14** |
| Low | 7 | 4 | **11** |
| Dead code | 14 | — | **14** |
Overall: branch is in solid shape. No crash-class code issues. The single Critical is an **ADR integrity** problem (not a code-level coupling problem). The HIGH findings cluster around three roots: (a) the Status-burst quiesce isn't applied to VH publishers, (b) comments have drifted from the refactor, (c) the ADR governance process wasn't followed cleanly.
---
## 1A: Code Quality Highlights
### HIGH (4)
1. **VH ventilation publishers bypass the Status-burst quiesce** — `OTGW-Core.ino:1688-1732`. The whole point of TASK-342/347 is to suppress the drip during the 9-publish Status fanout; VH boilers get the pre-refactor behaviour because `beginStatusBurst`/`endStatusBurst` and `incrementStatusBurstPublishCount()` are missing in `publishMasterStatusVHState`, `publishSlaveStatusVHState`, and `publishStatusVHBitMQTT`. Functional gap, not cosmetic.
2. **False comment on `startDiscoveryVerification` preconditions** — `OTGW-firmware.ino:316-319`. Comment claims NTP + uptime>3600 are enforced; they aren't. Either add the guards or rewrite the comment. (First option preferred — matches stated contract.)
3. **REST `/verify` hard-codes `6000` instead of referencing `VERIFICATION_MIN_HEAP_START`** — `restAPI.ino:499`. Two sources of truth; UX also duplicates guards that already live inside `startDiscoveryVerific
gitextract_aoxwd8ux/
├── .claude/
│ ├── .vscode/
│ │ ├── arduino.json
│ │ ├── c_cpp_properties.json
│ │ ├── settings.json
│ │ └── tasks.json
│ ├── adr-kit-guide.md
│ ├── backlog-cli-reference.md
│ ├── commands/
│ │ ├── backlog_discord.md
│ │ └── check_otgw_issues.md
│ ├── docs/
│ │ └── discord-backlog-bridge.md
│ ├── settings.20260421_085354.bak
│ ├── settings.json
│ └── skills/
│ ├── adr/
│ │ └── SKILL.md
│ ├── flash/
│ │ └── SKILL.md
│ ├── release/
│ │ └── SKILL.md
│ └── update-docs/
│ └── SKILL.md
├── .codex
├── .copilot-tracking/
│ └── research/
│ ├── 20260306-mqtt-json-refactor-research.md
│ └── 20260306-ui-fixes-otmonitor-panel-spacing-research.md
├── .external-reviews/
│ ├── HANDOFF-claude-review-c-codebase-303Qj.md
│ └── README.md
├── .full-review/
│ ├── 00-scope.md
│ ├── 01-quality-architecture.md
│ ├── 02-security-performance.md
│ ├── 03-testing-documentation.md
│ ├── 05-final-report.md
│ ├── phase1a-code-quality.md
│ ├── phase1b-architecture.md
│ ├── phase2a-security.md
│ ├── phase2b-performance.md
│ ├── phase3a-testing.md
│ ├── phase3b-documentation.md
│ └── state.json
├── .full-review-archive-20260421-085044/
│ ├── 00-scope.md
│ ├── 01-quality-architecture.md
│ ├── 02-security-performance.md
│ └── state.json
├── .gitattributes
├── .githooks/
│ └── pre-commit
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md.example
│ ├── actions/
│ │ ├── build/
│ │ │ └── action.yml
│ │ └── setup/
│ │ └── action.yml
│ ├── agents/
│ │ ├── adr-generator.agent.md
│ │ ├── api-architect.agent.md
│ │ ├── context7.agent.md
│ │ ├── critical-thinking.agent.md
│ │ ├── debug.agent.md
│ │ ├── devils-advocate.agent.md
│ │ ├── expert-cpp-software-engineer.agent.md
│ │ ├── expert-react-frontend-engineer.agent.md
│ │ ├── gpt-5-beast-mode.agent.md
│ │ ├── implementation-plan.agent.md
│ │ ├── specification.agent.md
│ │ ├── task-planner.agent.md
│ │ └── task-researcher.agent.md
│ ├── copilot-instructions.md
│ ├── instructions/
│ │ ├── adr.code-review.instructions.md
│ │ └── adr.coding-agent.instructions.md
│ ├── prompts/
│ │ └── check-discord-issues.prompt.md
│ ├── skills/
│ │ ├── adr/
│ │ │ ├── ALWAYS_USE_SKILL.md
│ │ │ ├── IMPLEMENTATION_SUMMARY.md
│ │ │ ├── QUICK_START.md
│ │ │ ├── README.md
│ │ │ ├── SKILL.md
│ │ │ └── USAGE_GUIDE.md
│ │ ├── algorithmic-art/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── templates/
│ │ │ ├── generator_template.js
│ │ │ └── viewer.html
│ │ ├── brand-guidelines/
│ │ │ ├── LICENSE.txt
│ │ │ └── SKILL.md
│ │ ├── canvas-design/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── canvas-fonts/
│ │ │ ├── ArsenalSC-OFL.txt
│ │ │ ├── BigShoulders-OFL.txt
│ │ │ ├── Boldonse-OFL.txt
│ │ │ ├── BricolageGrotesque-OFL.txt
│ │ │ ├── CrimsonPro-OFL.txt
│ │ │ ├── DMMono-OFL.txt
│ │ │ ├── EricaOne-OFL.txt
│ │ │ ├── GeistMono-OFL.txt
│ │ │ ├── Gloock-OFL.txt
│ │ │ ├── IBMPlexMono-OFL.txt
│ │ │ ├── InstrumentSans-OFL.txt
│ │ │ ├── Italiana-OFL.txt
│ │ │ ├── JetBrainsMono-OFL.txt
│ │ │ ├── Jura-OFL.txt
│ │ │ ├── LibreBaskerville-OFL.txt
│ │ │ ├── Lora-OFL.txt
│ │ │ ├── NationalPark-OFL.txt
│ │ │ ├── NothingYouCouldDo-OFL.txt
│ │ │ ├── Outfit-OFL.txt
│ │ │ ├── PixelifySans-OFL.txt
│ │ │ ├── PoiretOne-OFL.txt
│ │ │ ├── RedHatMono-OFL.txt
│ │ │ ├── Silkscreen-OFL.txt
│ │ │ ├── SmoochSans-OFL.txt
│ │ │ ├── Tektur-OFL.txt
│ │ │ ├── WorkSans-OFL.txt
│ │ │ └── YoungSerif-OFL.txt
│ │ ├── doc-coauthoring/
│ │ │ └── SKILL.md
│ │ ├── docx/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ ├── __init__.py
│ │ │ ├── accept_changes.py
│ │ │ ├── comment.py
│ │ │ ├── office/
│ │ │ │ ├── helpers/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── merge_runs.py
│ │ │ │ │ └── simplify_redlines.py
│ │ │ │ ├── pack.py
│ │ │ │ ├── schemas/
│ │ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── pml.xsd
│ │ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ │ ├── sml.xsd
│ │ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── wml.xsd
│ │ │ │ │ │ └── xml.xsd
│ │ │ │ │ ├── ecma/
│ │ │ │ │ │ └── fouth-edition/
│ │ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ │ ├── mce/
│ │ │ │ │ │ └── mc.xsd
│ │ │ │ │ └── microsoft/
│ │ │ │ │ ├── wml-2010.xsd
│ │ │ │ │ ├── wml-2012.xsd
│ │ │ │ │ ├── wml-2018.xsd
│ │ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ │ └── wml-symex-2015.xsd
│ │ │ │ ├── soffice.py
│ │ │ │ ├── unpack.py
│ │ │ │ ├── validate.py
│ │ │ │ └── validators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── docx.py
│ │ │ │ ├── pptx.py
│ │ │ │ └── redlining.py
│ │ │ └── templates/
│ │ │ ├── comments.xml
│ │ │ ├── commentsExtended.xml
│ │ │ ├── commentsExtensible.xml
│ │ │ ├── commentsIds.xml
│ │ │ └── people.xml
│ │ ├── frontend-design/
│ │ │ ├── LICENSE.txt
│ │ │ └── SKILL.md
│ │ ├── internal-comms/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── examples/
│ │ │ ├── 3p-updates.md
│ │ │ ├── company-newsletter.md
│ │ │ ├── faq-answers.md
│ │ │ └── general-comms.md
│ │ ├── mcp-builder/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── reference/
│ │ │ │ ├── evaluation.md
│ │ │ │ ├── mcp_best_practices.md
│ │ │ │ ├── node_mcp_server.md
│ │ │ │ └── python_mcp_server.md
│ │ │ └── scripts/
│ │ │ ├── connections.py
│ │ │ ├── evaluation.py
│ │ │ ├── example_evaluation.xml
│ │ │ └── requirements.txt
│ │ ├── pdf/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── forms.md
│ │ │ ├── reference.md
│ │ │ └── scripts/
│ │ │ ├── check_bounding_boxes.py
│ │ │ ├── check_fillable_fields.py
│ │ │ ├── convert_pdf_to_images.py
│ │ │ ├── create_validation_image.py
│ │ │ ├── extract_form_field_info.py
│ │ │ ├── extract_form_structure.py
│ │ │ ├── fill_fillable_fields.py
│ │ │ └── fill_pdf_form_with_annotations.py
│ │ ├── pptx/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── editing.md
│ │ │ ├── pptxgenjs.md
│ │ │ └── scripts/
│ │ │ ├── __init__.py
│ │ │ ├── add_slide.py
│ │ │ ├── clean.py
│ │ │ ├── office/
│ │ │ │ ├── helpers/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── merge_runs.py
│ │ │ │ │ └── simplify_redlines.py
│ │ │ │ ├── pack.py
│ │ │ │ ├── schemas/
│ │ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── pml.xsd
│ │ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ │ ├── sml.xsd
│ │ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ │ ├── wml.xsd
│ │ │ │ │ │ └── xml.xsd
│ │ │ │ │ ├── ecma/
│ │ │ │ │ │ └── fouth-edition/
│ │ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ │ ├── mce/
│ │ │ │ │ │ └── mc.xsd
│ │ │ │ │ └── microsoft/
│ │ │ │ │ ├── wml-2010.xsd
│ │ │ │ │ ├── wml-2012.xsd
│ │ │ │ │ ├── wml-2018.xsd
│ │ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ │ └── wml-symex-2015.xsd
│ │ │ │ ├── soffice.py
│ │ │ │ ├── unpack.py
│ │ │ │ ├── validate.py
│ │ │ │ └── validators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── docx.py
│ │ │ │ ├── pptx.py
│ │ │ │ └── redlining.py
│ │ │ └── thumbnail.py
│ │ ├── refactor/
│ │ │ └── SKILL.md
│ │ ├── skill-creator/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── references/
│ │ │ │ ├── output-patterns.md
│ │ │ │ └── workflows.md
│ │ │ └── scripts/
│ │ │ ├── init_skill.py
│ │ │ ├── package_skill.py
│ │ │ └── quick_validate.py
│ │ ├── template-skill/
│ │ │ └── SKILL.md
│ │ ├── theme-factory/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── themes/
│ │ │ ├── arctic-frost.md
│ │ │ ├── botanical-garden.md
│ │ │ ├── desert-rose.md
│ │ │ ├── forest-canopy.md
│ │ │ ├── golden-hour.md
│ │ │ ├── midnight-galaxy.md
│ │ │ ├── modern-minimalist.md
│ │ │ ├── ocean-depths.md
│ │ │ ├── sunset-boulevard.md
│ │ │ └── tech-innovation.md
│ │ ├── web-artifacts-builder/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ ├── bundle-artifact.sh
│ │ │ └── init-artifact.sh
│ │ ├── webapp-testing/
│ │ │ ├── LICENSE.txt
│ │ │ ├── SKILL.md
│ │ │ ├── examples/
│ │ │ │ ├── console_logging.py
│ │ │ │ ├── element_discovery.py
│ │ │ │ └── static_html_automation.py
│ │ │ └── scripts/
│ │ │ └── with_server.py
│ │ └── xlsx/
│ │ ├── LICENSE.txt
│ │ ├── SKILL.md
│ │ └── scripts/
│ │ ├── office/
│ │ │ ├── helpers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── merge_runs.py
│ │ │ │ └── simplify_redlines.py
│ │ │ ├── pack.py
│ │ │ ├── schemas/
│ │ │ │ ├── ISO-IEC29500-4_2016/
│ │ │ │ │ ├── dml-chart.xsd
│ │ │ │ │ ├── dml-chartDrawing.xsd
│ │ │ │ │ ├── dml-diagram.xsd
│ │ │ │ │ ├── dml-lockedCanvas.xsd
│ │ │ │ │ ├── dml-main.xsd
│ │ │ │ │ ├── dml-picture.xsd
│ │ │ │ │ ├── dml-spreadsheetDrawing.xsd
│ │ │ │ │ ├── dml-wordprocessingDrawing.xsd
│ │ │ │ │ ├── pml.xsd
│ │ │ │ │ ├── shared-additionalCharacteristics.xsd
│ │ │ │ │ ├── shared-bibliography.xsd
│ │ │ │ │ ├── shared-commonSimpleTypes.xsd
│ │ │ │ │ ├── shared-customXmlDataProperties.xsd
│ │ │ │ │ ├── shared-customXmlSchemaProperties.xsd
│ │ │ │ │ ├── shared-documentPropertiesCustom.xsd
│ │ │ │ │ ├── shared-documentPropertiesExtended.xsd
│ │ │ │ │ ├── shared-documentPropertiesVariantTypes.xsd
│ │ │ │ │ ├── shared-math.xsd
│ │ │ │ │ ├── shared-relationshipReference.xsd
│ │ │ │ │ ├── sml.xsd
│ │ │ │ │ ├── vml-main.xsd
│ │ │ │ │ ├── vml-officeDrawing.xsd
│ │ │ │ │ ├── vml-presentationDrawing.xsd
│ │ │ │ │ ├── vml-spreadsheetDrawing.xsd
│ │ │ │ │ ├── vml-wordprocessingDrawing.xsd
│ │ │ │ │ ├── wml.xsd
│ │ │ │ │ └── xml.xsd
│ │ │ │ ├── ecma/
│ │ │ │ │ └── fouth-edition/
│ │ │ │ │ ├── opc-contentTypes.xsd
│ │ │ │ │ ├── opc-coreProperties.xsd
│ │ │ │ │ ├── opc-digSig.xsd
│ │ │ │ │ └── opc-relationships.xsd
│ │ │ │ ├── mce/
│ │ │ │ │ └── mc.xsd
│ │ │ │ └── microsoft/
│ │ │ │ ├── wml-2010.xsd
│ │ │ │ ├── wml-2012.xsd
│ │ │ │ ├── wml-2018.xsd
│ │ │ │ ├── wml-cex-2018.xsd
│ │ │ │ ├── wml-cid-2016.xsd
│ │ │ │ ├── wml-sdtdatahash-2020.xsd
│ │ │ │ └── wml-symex-2015.xsd
│ │ │ ├── soffice.py
│ │ │ ├── unpack.py
│ │ │ ├── validate.py
│ │ │ └── validators/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── docx.py
│ │ │ ├── pptx.py
│ │ │ └── redlining.py
│ │ └── recalc.py
│ └── workflows/
│ ├── claude-code-review.yml
│ ├── claude.yml
│ ├── evaluate.yml
│ ├── opentherm-v42-spec-audit.yml
│ ├── release-assets.yml
│ └── trigger-copilot-agent.yml
├── .gitignore
├── .gitmodules
├── AGENTS.md
├── AUTHORS
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── backlog/
│ ├── archive/
│ │ └── tasks/
│ │ ├── task-1 - Audit-and-fix-cMsg-shared-global-buffer-reentrancy-hazard.md
│ │ ├── task-10 - Harden-REST-API-input-validation-postSettings-field-whitelist-Dallas-labels.md
│ │ ├── task-11 - Add-WebSocket-idle-timeout-and-Content-Length-headers-for-file-serving.md
│ │ ├── task-12 - Fix-typos-and-minor-code-quality-issues-across-codebase.md
│ │ ├── task-13 - Upgrade-WiFiManager-from-RC-to-stable-release.md
│ │ ├── task-14 - Check-and-upgrade-WebSockets-DallasTemperature-and-OneWire-libraries.md
│ │ ├── task-15 - Refactor-OTA-updater-flow.md
│ │ ├── task-15.1 - Refactor-OTA-page-JavaScript.md
│ │ ├── task-15.2 - Refactor-OTA-backend-handler.md
│ │ ├── task-15.3 - Clarify-flash-mode-runtime-handling.md
│ │ ├── task-16 - Fix-MQTT-status-first-publish-and-reconnect-republish.md
│ │ ├── task-17 - Align-MQTT-publish-implementation-to-ADR-052.md
│ │ ├── task-18 - Reclaim-FSexplorer-static-HTML-streaming-buffers.md
│ │ ├── task-19 - Replace-global-sMessage-scratch-buffer-with-smaller-status-representation.md
│ │ ├── task-2 - Move-networkStuff.h-function-bodies-to-networkStuff.ino.md
│ │ ├── task-20 - Reduce-persistent-RAM-used-by-webhook-payload-expansion.md
│ │ ├── task-21 - Compact-OT-MQTT-publish-tracking-tables.md
│ │ ├── task-22 - Release-MQTT-autodiscovery-workspace-after-publish-sessions.md
│ │ ├── task-22.1 - Implement-in-place-MQTT-autodiscovery-line-parsing.md
│ │ ├── task-22.2 - Implement-streaming-MQTT-autodiscovery-template-rendering.md
│ │ ├── task-22.3 - Integrate-and-validate-reduced-workspace-MQTT-autodiscovery.md
│ │ ├── task-23 - Stream-PROGMEM-MQTT-payloads-and-remove-1200-byte-payload-scratch-buffer.md
│ │ ├── task-24 - Convert-all-MQTT-publishes-to-chunked-beginPublish-flow-and-shrink-client-buffer.md
│ │ ├── task-241 - Investigate-Tado-X-DHW-control-not-reflecting-correctly-via-OTGW.md
│ │ ├── task-242 - Fix-upgrade-to-v6.6-fails-reported-by-Tomba-on-Tweakers.md
│ │ ├── task-243 - Fix-NTP-time-sync-still-stuck-at-2106-02-07-after-v1.3.7-beta-fix.md
│ │ ├── task-243 - Investigate-Tado-X-DHW-control-not-reflecting-correctly-via-OTGW.md
│ │ ├── task-25 - Stream-mqttha.cfg-parsing-and-remove-persistent-MQTT-autodiscovery-workspace.md
│ │ ├── task-254 - Upgrade-ESP8266-Arduino-core-from-2.7.4-to-3.1.2.md
│ │ ├── task-255 - Update-all-pinned-Arduino-libraries-to-latest-compatible-versions.md
│ │ ├── task-256 - Fix-ESP8266-3.x-breaking-changes-in-firmware-code.md
│ │ ├── task-257 - SimpleTelnet-library-scaffold-and-build-integration.md
│ │ ├── task-258 - SimpleTelnet-core-connection-management-engine.md
│ │ ├── task-259 - SimpleTelnet-Stream-interface-—-broadcast-write-and-polling-read.md
│ │ ├── task-26 - Replace-dense-MQTT-publish-tracking-table-with-bounded-sparse-tracking.md
│ │ ├── task-260 - SimpleTelnet-CLI-input-mode-with-per-client-line-buffering.md
│ │ ├── task-261 - SimpleTelnet-printf-en-printf_P-PROGMEM-helpers.md
│ │ ├── task-262 - SimpleTelnet-firmware-migratie-—-OTGWstream-en-debugTelnet.md
│ │ ├── task-263 - SimpleTelnet-integratie-validatie-en-heap-meting.md
│ │ ├── task-264 - SimpleTelnet-worked-examples-—-StreamingMode-CLIMode-DualInstance.md
│ │ ├── task-265 - SimpleTelnet-API-documentatie-en-doxygen-header-comments.md
│ │ ├── task-266 - SimpleTelnet-publicatiebestanden-Arduino-Library-Manager-en-PlatformIO.md
│ │ ├── task-267 - SimpleTelnet-README.md-—-menselijk-Engelstalig-met-shoutouts-en-onderbouwing.md
│ │ ├── task-268 - Fix-ESP8266-v1.4.0-beta-reboot-loop-after-flash.md
│ │ ├── task-269 - Fix-PIC-firmware-v6.6-upgrade-via-web-UI-fails.md
│ │ ├── task-270 - Fix-MQTT-discovery-burst-on-every-reconnect.md
│ │ ├── task-271 - Build-generate-mqttha_progmem.h-from-mqttha.cfg.md
│ │ ├── task-272 - Refactor-MQTT-discovery-to-use-PROGMEM-index-instead-of-LittleFS.md
│ │ ├── task-273 - Config-switch-to-lwIP2-Low-Memory-variant-MSS536.md
│ │ ├── task-274 - Feature-scheduled-nightly-restart-for-heap-recovery.md
│ │ ├── task-276 - Optimize-eliminate-sLine1200-global-buffer-—-pass-PROGMEM-msg-pointers-directly.md
│ │ ├── task-277 - Optimize-eliminate-topicBuf200-stack-buffer-—-pass-PROGMEM-topic-pointers-directly.md
│ │ ├── task-278 - Fix-v1.4.0-beta-ESP8266-crash-reboot-loop-Exception-2-3.md
│ │ ├── task-279 - Fix-autodiscovery-PROGMEM-as-RAM-crashes-Exception-3.md
│ │ ├── task-280 - Fix-NULL-pointer-crash-in-getOTGWValue-updateSetting-at-boot-Exception-28.md
│ │ ├── task-281 - Refactor-mqttha_progmem-naar-leesbare-OTlookup_t-stijl.md
│ │ ├── task-282 - Refactor-MQTT-HA-discovery-compact-array-streaming-constructors.md
│ │ ├── task-3 - const-correctness-pass-on-MQTT-and-helper-functions.md
│ │ ├── task-338 - Slow-MQTT-discovery-drip-interval-from-1s-to-2s.md
│ │ ├── task-339 - Widen-MQTT-discovery-heap-pressure-backoff-trigger-to-HEAP_LOW.md
│ │ ├── task-340 - Use-getMaxFreeBlockSize-in-MQTT-WebSocket-publish-gates-for-fragmentation-awareness.md
│ │ ├── task-341 - JSON-ify-Status-frame-MQTT-fanout-single-publish-instead-of-9-sub-topics.md
│ │ ├── task-342 - Quiesce-MQTT-discovery-drip-during-Status-frame-burst.md
│ │ ├── task-343 - Delta-publishing-for-MQTT-Status-sub-topics-parked.md
│ │ ├── task-344 - Lower-heap-guard-thresholds-tuned-on-Crashevans-log-data.md
│ │ ├── task-345 - Refactor-nightly-restart-to-use-hourChanged-hook.md
│ │ ├── task-346 - Cumulative-heap-health-drop-statistics-with-hourly-MQTT-publish.md
│ │ ├── task-347 - Post-Status-burst-cooldown-window-for-MQTT-discovery-drip.md
│ │ ├── task-348 - Fix-discovery-drip-limbo-on-publish-failure.md
│ │ ├── task-349 - On-demand-MQTT-discovery-verification-and-republish.md
│ │ ├── task-350 - Unify-time-boundary-dispatcher-single-caller-contract.md
│ │ ├── task-351 - Daily-automatic-discovery-verification.md
│ │ ├── task-355 - choreadr-revert-ADR-062-064-to-Proposed-and-resolve-ghost-ADR-citations.md
│ │ ├── task-356 - fixmqtt-add-cooldown-precondition-on-api-v2-discovery-republish-to-prevent-verify-lockout.md
│ │ ├── task-357 - fixmqtt-guard-verify-window-callback-fall-through-to-command-dispatcher.md
│ │ ├── task-358 - fixmqtt-dedupe-heap-threshold-6000-between-REST-verify-and-startDiscoveryVerification.md
│ │ ├── task-359 - fixmqtt-enforce-NTP-and-uptime-preconditions-in-startDiscoveryVerification.md
│ │ ├── task-360 - docsmqtt-fix-stale-comments-on-heapdiag-call-site-drip-interval-and-ADR-077-reference.md
│ │ ├── task-361 - featmqtt-distinguish-verify-heap-abort-from-clean-pass-via-outcome-enum.md
│ │ ├── task-362 - chorecleanup-remove-dead-code-paths-and-write-only-state-fields-from-1.4.1-refactor.md
│ │ ├── task-363 - refactormqtt-extract-discovery-verification-state-machine-into-separate-TU.md
│ │ ├── task-364 - choreadr-implement-CI-gates-promised-by-ADR-062-for-discovery-counter-instrumentation.md
│ │ ├── task-365 - docsrelease-create-RELEASE_NOTES_1.4.1.md-update-BREAKING_CHANGES.md-README-What-is-new-section.md
│ │ ├── task-366 - docsapi-update-openapi.yaml-and-MQTT.md-for-new-discovery-verify-endpoints-and-heap-diag-topic.md
│ │ ├── task-367 - chorebacklog-append-erratum-on-TASK-342-346-349-351-Final-Summaries-and-remove-plan-file-references.md
│ │ ├── task-368 - choreci-wire-evaluate.py-into-GitHub-Actions-and-add-4-regex-gates-buffer-cooldown-ADR-VH-wrap.md
│ │ ├── task-369 - choretests-rewrite-tests-test_dallas_address.cpp-as-host-compilable-or-delete.md
│ │ ├── task-370 - fixheap-add-hysteresis-to-drip-interval-mode-transitions-to-stop-oscillation.md
│ │ ├── task-371 - fixotgw-quiesce-PIC-PR-readout-during-Status-burst-and-active-drip-tick.md
│ │ ├── task-372 - Fix-WiFi-does-not-reconnect-after-access-point-reboot.md
│ │ ├── task-386 - Fix-settings-page-double-tap-blanks-all-fields-1.4.2-beta.md
│ │ ├── task-4 - Break-OTGW-Core.ino-into-named-logical-regions-with-section-headers.md
│ │ ├── task-432 - Fix-1.5.0-beta-first-reboot-WiFi-association-without-DHCP-IP-andrebrait-reproducible.md
│ │ ├── task-5 - Add-bounds-validation-to-all-numeric-settings-in-updateSetting.md
│ │ ├── task-6 - Fix-MQTT-subscription-topic-truncation-and-byte-by-byte-streaming-write.md
│ │ ├── task-7 - Eliminate-String-class-from-FSexplorer-HTTP-handlers.md
│ │ ├── task-8 - Fix-undersized-buffers-overflowCountBuf-MQTT-payload-webhook-expansion.md
│ │ └── task-9 - Reduce-MQTT-callback-stack-pressure-and-protect-publishToSourceTopic-from-re-entrancy.md
│ ├── config.yml
│ └── tasks/
│ ├── task-240 - Fix-upgrade-to-v6.6-fails-reported-by-Tomba-on-Tweakers.md
│ ├── task-242 - Fix-OTGW-flapping-offline-online-with-serial-overrun-and-MQTT-throttle-drops.md
│ ├── task-275 - Validate-heap-stability-after-stap-1-fixes-—-decide-on-core-downgrade.md
│ ├── task-283 - Fix-v1.4.0-beta-boot-loop-triggered-by-MQTT-broker-connection.md
│ ├── task-352 - fixheapdiag-expand-sendMQTTheapdiag-JSON-buffer-to-prevent-truncation-at-max-counters.md
│ ├── task-353 - fixmqtt-lower-STATUS_BURST_COOLDOWN_MS-to-2000ms-to-stop-discovery-drip-stall.md
│ ├── task-354 - fixotgw-wrap-VH-status-publishers-in-beginStatusBurst-endStatusBurst-quiesce.md
│ ├── task-382 - Fix-MQTT-HA-discovery-drip-never-sends-device-name-or-sw_version-isFirstEntity-always-false.md
│ ├── task-383 - Docs-add-Arduino-Core-3.1.2-upgrade-warning-LittleFS-partition-change-causes-~10-min-boot-settings-loss.md
│ ├── task-384 - Fix-v1.3.5-bootloop-on-fresh-flash-to-Wemos-D1.md
│ ├── task-385 - Fix-text-fields-render-dark-in-light-mode-1.4.2-beta.md
│ ├── task-387 - Fix-theme-toggle-icon-overlaps-hostnameIP-text-in-mobile-header.md
│ ├── task-388 - Fix-MQTT-binary_sensor-discovery-via-flag-driven-otgw-pic-prefix.md
│ ├── task-389 - Create-ADR-065-otgw-pic-MQTT-subtree-is-stable-public-topic-API.md
│ ├── task-390 - Add-sendMQTTDataPic-helper-and-migrate-direct-publish-call-sites-to-use-it.md
│ ├── task-391 - Fix-1.4.2-webui-boot-lag-render-hotpath-lower-restore-cap-to-10k.md
│ ├── task-392 - Fix-findings-from-v1.4.1..dev-handoff-review.md
│ ├── task-395 - Port-TASK-394-Phase-12-reboot-diagnostics-fixes-from-2.0.0-to-dev.md
│ ├── task-396 - TASK-394-Phase-34-port-dev-hardening-deferred-reboot-OTA-heap-probes-watermark-flash-sanity-exccause.md
│ ├── task-397 - Diagnose-random-doBackgroundTasks-loop-stalls-—-BGTRACE-always-on-instrumentation.md
│ ├── task-398 - Create-LTS-1.4.x-on-2.7.4-branch-fork-dev-pin-to-Arduino-Core-2.7.4.md
│ ├── task-399 - Bump-SimpleTelnet-printf-stack-buffer-from-64-to-256-bytes-tunable-SIMPLETELNET_PRINTF_STACK_LEN.md
│ ├── task-400 - Per-bit-change-detection-for-OT-msgId-0-Status-MQTT-fan-out-60s-heartbeat.md
│ ├── task-401 - Per-bit-change-detection-60s-heartbeat-for-MQTT-fan-out-on-OT-msgId-5-ASF-6-RBP-and-100-Remote-Override.md
│ ├── task-402 - Rate-gate-MQTT-gated-fanout-publishes-at-1s-spacing-with-per-slot-pending-flags.md
│ ├── task-403 - Tune-MQTT-gated-fanout-spacing-from-1000ms-to-250ms-disable-BGTRACE-OTTRACE-instrumentation.md
│ ├── task-431 - Investigate-rapid-WebUI-page-refresh-freezes-the-OTGW-1.4.2-beta-requires-network-drop-to-recover.md
│ ├── task-478 - fixmqtt-stop-master-topic-flapping-for-non-echoed-OT-values-B-hybrid.md
│ ├── task-483 - fixwebui-apply-ADR-066-master-topic-filter-to-log-decode-and-REST-state.md
│ ├── task-484 - Fix-WiFi-setup-AP-mode-webUI-unreachable-after-Reset-Wifi-andrebrait-1.5.0-beta.md
│ ├── task-485 - Fix-AP-not-found-on-Netgear-Orbi-after-upgrading-to-1.4.1-aagorine.md
│ ├── task-486 - Fix-PIC-not-detected-on-Wemos-D1-Mini-Pro-GitHub-557-dwd1.md
│ ├── task-522 - HA-discovery-suppress-base-entity-when-bSeparateSources-is-enabled-no-overlap-design.md
│ ├── task-526 - Make-legacy-port-25238-otmonitor-TCP-opt-in-via-UI-toggle.md
│ ├── task-527 - feat-2.0.0-port-legacy-port-25238-opt-in-toggle-from-TASK-526-with-ESP32-OTDirect-considerations.md
│ ├── task-531 - Restore-backward-compatible-bare-topic-for-gateway-source-HA-entities-dev.md
│ ├── task-534 - Fix-DHW-setpoint-shows-HA-initial-value-43°C-and-DHW-temperature-unknown-in-HA-via-MQTT.md
│ ├── task-535 - Docs-fix-duplicate-HA-entities-after-firmware-upgrade-—-stale-retained-MQTT-discovery-topics.md
│ ├── task-536 - Add-dump-debug-info-command-to-debug-menu.md
│ ├── task-537 - Port-TASK-536-debug-dump-to-ESP32-2.0.0-branch.md
│ ├── task-538 - Drop-gateway-MQTT-sub-topic-canonical-entity-replaces-_gateway-HA-discovery.md
│ ├── task-538 - Fix-GWR-stuck-in-command-queue-causes-infinite-PIC-reset-loop.md
│ ├── task-539 - feat-2.0.0-port-TASK-538-—-drop-gateway-MQTT-sub-topic-canonical-entity-replaces-_gateway-HA-discovery.md
│ ├── task-540 - Add-HA-discovery-for-diagnostic-MQTT-topics-otgw-firmware-stats-reboot_count-etc..md
│ ├── task-541 - feat-2.0.0-port-TASK-540-—-add-HA-discovery-for-diagnostic-MQTT-topics.md
│ ├── task-542 - Fix-SimpleTelnet-OpenTherm-OTGWSerial-submodules-unregistered-in-.gitmodules-feature-dev-2.0.0.md
│ ├── task-543 - feat-2.0.0-HA-discovery-for-SAT-OTDirect-user-facing-topics.md
│ ├── task-545 - Compact-telnet-welcome-banner-with-diagnostic-snapshot-all-toggles-1.5.x.md
│ ├── task-546 - feat-2.0.0-port-TASK-534-DHW-climate-discovery-initial-fallback-removal.md
│ ├── task-547 - Fix-Services-unreachable-after-WiFi-reconnect.md
│ ├── task-548 - Feature-Static-IP-address-settings.md
│ ├── task-549 - Override-side-TSet-TrSet-routing-split-thermostat-vs-boiler-MQTT-publication-during-gateway-override.md
│ ├── task-551 - ADR-070-MQTT-source-topic-sibling-suffix-shape-supersedes-ADR-068-refines-ADR-069.md
│ ├── task-552 - Implement-ADR-070-switch-to-sibling-suffix-MQTT-source-topics-drop-base-suppression.md
│ ├── task-553 - fixmqtt-add-threshold-hysteresis-deadband-K-ticks-to-drip-mode-transitions-to-stop-~60-90s-thrash.md
│ ├── task-556 - featmqtt-flip-discovery-topic-shape-to-sibling-suffix-implements-ADR-071-supersedes-ADR-070-carve-out.md
│ ├── task-558 - Route-force-discovery-through-drip-publisher-add-maxBlock-to-throttle-warnings.md
│ ├── task-559 - Enforce-prerelease-bump-on-firmware-touching-commits-hook-helper-CLAUDE.md.md
│ ├── task-560 - Enforce-prerelease-bump-on-firmware-touching-commits-hook-helper-CLAUDE.md.md
│ ├── task-561 - fix-ADR-066-source-topic-gate-uses-wrong-enum-family-—-Write-Ack-flapping.md
│ ├── task-571 - fixmqtt-flip-MsgID-1-TSet-bSlaveEchoesValuefalse-—-heat-pump-non-echo-flap.md
│ ├── task-572 - fixmqtt-HA-discovery-friendly-name-uses-spaces-not-underscores.md
│ ├── task-573 - fixmqtt-normalise-HA-discovery-friendly-name-strings-—-split-camelCase-uppercase-acronyms-drop-typos.md
│ ├── task-575 - docs-update-documentation-for-changes-since-v1.4.1.md
│ ├── task-576 - feat-add-CHANGELOG.md-Keep-a-Changelog-and-integrate-into-release-workflow.md
│ ├── task-577 - Pure-JIT-MQTT-discovery-—-publish-OT-configs-only-when-MsgID-received.md
│ ├── task-578 - feat-2.0.0-port-TASK-577-—-Pure-JIT-MQTT-discovery-for-feature-branch.md
│ ├── task-588 - fixsat-wire-sat-curve_recommendation_attributes-to-HA-discovery-json_attributes_topic-or-remove-orphaned-publish.md
│ ├── task-589 - fixsat-remove-or-wire-orphaned-sat-climate_attributes-JSON-publish-512-byte-static-buffer.md
│ ├── task-590 - fixsat-remove-or-wire-orphaned-sat-pressure_health_attr-JSON-publish.md
│ ├── task-596 - docs-update-documentation-for-changes-since-v1.5.0-fix.md
│ └── task-86 - Fix-Max-CH-setpoint-shows-0°C-in-HA-Boiler-entity.md
├── bin/
│ └── bump-prerelease.sh
├── build.bat
├── build.py
├── build.sh
├── commits.txt
├── config.py
├── deep-research-report_arduino_core_3.1.2_reboot_issue_after_OTA.md
├── docs/
│ ├── BREAKING_CHANGES.md
│ ├── adr/
│ │ ├── ADR-001-esp8266-platform-selection.md
│ │ ├── ADR-002-modular-ino-architecture.md
│ │ ├── ADR-003-http-only-no-https.md
│ │ ├── ADR-004-static-buffer-allocation.md
│ │ ├── ADR-005-websocket-real-time-streaming.md
│ │ ├── ADR-006-mqtt-integration-pattern.md
│ │ ├── ADR-007-timer-based-task-scheduling.md
│ │ ├── ADR-008-littlefs-configuration-persistence.md
│ │ ├── ADR-009-progmem-string-literals.md
│ │ ├── ADR-010-multiple-concurrent-network-services.md
│ │ ├── ADR-011-external-hardware-watchdog.md
│ │ ├── ADR-012-pic-firmware-upgrade-via-web.md
│ │ ├── ADR-013-arduino-framework-over-esp-idf.md
│ │ ├── ADR-014-dual-build-system.md
│ │ ├── ADR-015-ntp-acetime-time-management.md
│ │ ├── ADR-016-opentherm-command-queue.md
│ │ ├── ADR-017-wifimanager-initial-configuration.md
│ │ ├── ADR-018-arduinojson-data-interchange.md
│ │ ├── ADR-019-rest-api-versioning-strategy.md
│ │ ├── ADR-020-dallas-ds18b20-sensor-integration.md
│ │ ├── ADR-021-s0-pulse-counter-interrupt-architecture.md
│ │ ├── ADR-022-gpio-output-bit-flag-control.md
│ │ ├── ADR-023-filesystem-explorer-http-api.md
│ │ ├── ADR-024-debug-telnet-command-console.md
│ │ ├── ADR-025-safari-websocket-connection-management.md
│ │ ├── ADR-026-conditional-javascript-cache-busting.md
│ │ ├── ADR-027-version-mismatch-warning-system.md
│ │ ├── ADR-028-file-streaming-over-loading.md
│ │ ├── ADR-029-simple-xhr-ota-flash.md
│ │ ├── ADR-030-heap-memory-monitoring-emergency-recovery.md
│ │ ├── ADR-031-two-microcontroller-coordination-architecture.md
│ │ ├── ADR-032-no-authentication-local-network-security.md
│ │ ├── ADR-033-dallas-sensor-custom-labels-graph-visualization.md
│ │ ├── ADR-034-non-blocking-modal-dialogs.md
│ │ ├── ADR-035-restful-api-compliance-strategy.md
│ │ ├── ADR-036-boot-sequence-ordering.md
│ │ ├── ADR-037-gateway-mode-detection.md
│ │ ├── ADR-038-opentherm-data-flow-pipeline.md
│ │ ├── ADR-039-otgraph-real-time-charting.md
│ │ ├── ADR-040-mqtt-source-specific-topics.md
│ │ ├── ADR-041-jit-ha-discovery.md
│ │ ├── ADR-042-streaming-json-no-arduinojson.md
│ │ ├── ADR-043-reset-pattern-wifi-recovery.md
│ │ ├── ADR-044-global-state-header-definition-pattern.md
│ │ ├── ADR-045-ps1-print-summary-parsing.md
│ │ ├── ADR-046-ps1-summary-translation-shared-publish-helpers.md
│ │ ├── ADR-047-nonblocking-wifi-reconnect.md
│ │ ├── ADR-048-nonblocking-webhook-state-machine.md
│ │ ├── ADR-049-string-prohibition-protocol-paths.md
│ │ ├── ADR-050-centralized-api-route-dispatch.md
│ │ ├── ADR-051-dual-encapsulating-structs.md
│ │ ├── ADR-052-mqtt-publish-eligibility-contract.md
│ │ ├── ADR-053-large-feature-buffer-static-allocation.md
│ │ ├── ADR-054-optional-http-basic-auth.md
│ │ ├── ADR-055-webhook-outbound-http-integration.md
│ │ ├── ADR-056-protected-admin-endpoint-security-and-secret-handling-contract.md
│ │ ├── ADR-057-webhook-delivery-retry-and-protected-test-endpoint-policy.md
│ │ ├── ADR-058-nonblocking-pic-command-response.md
│ │ ├── ADR-059-ser2net-queue-awareness.md
│ │ ├── ADR-060-pic-availability-guard-pattern.md
│ │ ├── ADR-061-wifi-reconnect-timeout-tuning.md
│ │ ├── ADR-062-retained-discovery-verification.md
│ │ ├── ADR-064-time-boundary-single-caller-contract.md
│ │ ├── ADR-065-otgw-pic-mqtt-subtree.md
│ │ ├── ADR-066-mqtt-publish-gating-by-source-and-slave-echo.md
│ │ ├── ADR-067-ha-discovery-state-reconciliation-on-ota-upgrade.md
│ │ ├── ADR-068-bseparatesources-mutually-exclusive-base-and-source-variants.md
│ │ ├── ADR-069-mqtt-source-topic-worldview-semantics.md
│ │ ├── ADR-070-mqtt-source-topic-sibling-suffix-shape.md
│ │ ├── ADR-071-mqtt-discovery-topic-sibling-suffix-shape.md
│ │ ├── ADR-072-ha-discovery-friendly-name-format.md
│ │ ├── ADR-073-jit-ha-discovery-smart-reconnect.md
│ │ ├── ADR_DATE_EVIDENCE_EXAMPLES.md
│ │ ├── ADR_DATE_VERIFICATION.md
│ │ ├── ADR_VERIFICATION_REPORT.md
│ │ ├── README.md
│ │ └── VERIFICATION_SUMMARY.md
│ ├── api/
│ │ ├── DALLAS_SENSOR_LABELS_API.md
│ │ ├── MQTT-message-id-echo-audit.md
│ │ ├── MQTT.md
│ │ ├── README.md
│ │ ├── WEBSOCKET_FLOW.md
│ │ ├── WEBSOCKET_QUICK_REFERENCE.md
│ │ ├── openapi-dallas-sensors.yaml
│ │ └── openapi.yaml
│ ├── archive/
│ │ ├── MQTT_old.md
│ │ ├── RELEASE_GENERATION_GUIDE.md
│ │ ├── daily-issue-report.md
│ │ ├── mqttha-generator/
│ │ │ ├── README.md
│ │ │ ├── generate_mqttha_data.py
│ │ │ ├── generate_mqttha_progmem.py
│ │ │ ├── generate_mqttha_readable.py
│ │ │ └── mqttha.cfg
│ │ ├── rc3-rc4-transition/
│ │ │ └── README.md
│ │ └── upgrade-from-0.x.md
│ ├── daily-issue-report.md
│ ├── features/
│ │ ├── TEMPERATURE_SENSOR_DIAGRAM.md
│ │ ├── TEMPERATURE_SENSOR_FINAL_SUMMARY.md
│ │ ├── TEMPERATURE_SENSOR_GRAPH_IMPLEMENTATION.md
│ │ ├── dallas-temperature-sensors.md
│ │ ├── data-persistence.md
│ │ ├── gateway-mode-detection.md
│ │ └── webhook.md
│ ├── fixes/
│ │ ├── CI_BUILD_FIX.md
│ │ ├── README.md
│ │ ├── SAFARI_FLASH_FIX.md
│ │ ├── mqtt-auth-analysis-v0.10.3-vs-v1.0.0.md
│ │ ├── mqtt-whitespace-auth-fix.md
│ │ └── opentherm-v42-mqtt-breaking-changes.md
│ ├── guides/
│ │ ├── BUILD.md
│ │ ├── FLASH_GUIDE.md
│ │ ├── MQTT_LWT.md
│ │ ├── MQTT_STALE_TOPICS_CLEANUP.md
│ │ ├── WEBSOCKET_LOGGING.md
│ │ ├── WIFI_RECOVERY_TRIPLE_RESET.md
│ │ └── browser-debug-console.md
│ ├── opentherm specification/
│ │ ├── Integration homeassistant.txt
│ │ ├── New OT data-ids.txt
│ │ ├── OT protocol version information.txt
│ │ ├── OT spec 2.3b.txt
│ │ ├── OT specification 2.3b.xlsx
│ │ ├── OT-specification2.3b-todo.txt
│ │ ├── OT-specification2.3b.txt
│ │ ├── OpenTherm-Protocol-Specification-v4.2-message-id-reference.md
│ │ ├── OpenTherm-Protocol-Specification-v4.2.md
│ │ └── OpenTherm-specifications.md
│ ├── plan/
│ │ ├── CPP_REFACTORING_PLAN.md
│ │ └── SETTINGS_STREAMING_REFACTOR_PLAN.md
│ ├── process/
│ │ ├── EVALUATION.md
│ │ ├── RELEASE_PROCESS.md
│ │ ├── branch-hygiene-queue.csv
│ │ ├── branch-hygiene-status.md
│ │ ├── ps1-lean-translator-refactor-plan.md
│ │ └── release-workflow.md
│ ├── releases/
│ │ ├── RELEASE_GITHUB_1.3.5.md
│ │ ├── RELEASE_GITHUB_1.4.1.md
│ │ ├── RELEASE_GITHUB_1.5.0-beta.md
│ │ ├── RELEASE_GITHUB_1.5.0.md
│ │ ├── RELEASE_NOTES_1.3.5.md
│ │ ├── RELEASE_NOTES_1.4.1.md
│ │ ├── RELEASE_NOTES_1.5.0-beta.md
│ │ ├── RELEASE_NOTES_1.5.0.md
│ │ └── archive/
│ │ ├── GITHUB_RELEASE_v1.3.0.md
│ │ ├── RELEASE_GITHUB_1.1.0.md
│ │ ├── RELEASE_GITHUB_1.2.0.md
│ │ ├── RELEASE_GITHUB_1.3.0.md
│ │ ├── RELEASE_GITHUB_1.3.1.md
│ │ ├── RELEASE_GITHUB_1.3.2.md
│ │ ├── RELEASE_GITHUB_1.3.3.md
│ │ ├── RELEASE_GITHUB_1.3.4.md
│ │ ├── RELEASE_NOTES_1.0.0.md
│ │ ├── RELEASE_NOTES_1.1.0.md
│ │ ├── RELEASE_NOTES_1.2.0.md
│ │ ├── RELEASE_NOTES_1.3.0.md
│ │ ├── RELEASE_NOTES_1.3.1.md
│ │ ├── RELEASE_NOTES_1.3.2.md
│ │ ├── RELEASE_NOTES_1.3.3.md
│ │ └── RELEASE_NOTES_1.3.4.md
│ └── reviews/
│ ├── 2026-01-17_dev-rc4-analysis/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── DEV_RC4_BRANCH_REVIEW.md
│ │ ├── EVALUATION_QUICKREF.md
│ │ ├── EVALUATION_SUMMARY.md
│ │ ├── FLASH_GUIDE.md
│ │ ├── HEAP_OPTIMIZATION_SUMMARY.md
│ │ ├── HIGH_PRIORITY_FIXES.md
│ │ ├── IMPLEMENTATION_SUMMARY.md
│ │ ├── LARGE_BUFFER_ANALYSIS.md
│ │ ├── LIBRARY_ANALYSIS.md
│ │ ├── MERGED_BINARY_GUIDE.md
│ │ ├── MQTT_STREAMING_AUTODISCOVERY.md
│ │ ├── OTGWSerial_PR_Description.md
│ │ ├── PIC_Flashing_Fix_Analysis.md
│ │ ├── README.md
│ │ ├── REVIEW_INDEX.md
│ │ ├── REVIEW_SUMMARY.md
│ │ ├── SENSOR_FIX_SUMMARY.md
│ │ ├── SENSOR_MQTT_ANALYSIS.md
│ │ └── Stream Logging.md
│ ├── 2026-01-18_post-merge-final/
│ │ ├── POST_MERGE_REVIEW.md
│ │ └── README.md
│ ├── 2026-01-19_pr364-verification/
│ │ ├── PR_364_VERIFICATION_REPORT.md
│ │ └── VERIFICATION_SUMMARY.md
│ ├── 2026-01-21_filesystem-flash-robustness/
│ │ ├── FLASH_ROBUSTNESS_ANALYSIS.md
│ │ ├── QUICK_REFERENCE.md
│ │ └── README.md
│ ├── 2026-01-23_pic-flash-update/
│ │ └── PIC_FLASH_WEBSOCKET_UPDATE.md
│ ├── 2026-01-26_browser-compatibility-review/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── BROWSER_COMPATIBILITY_AUDIT_2026.md
│ │ ├── COMPATIBILITY_SUMMARY_2026.md
│ │ ├── HIGH_PRIORITY_FIXES.md
│ │ ├── README.md
│ │ ├── REVIEW_INDEX.md
│ │ ├── SAFARI_COMPATIBILITY_ASSESSMENT.md
│ │ ├── WEBSOCKET_IMPROVEMENTS_SUMMARY.md
│ │ ├── WEBSOCKET_QUICKREF.md
│ │ ├── WEBSOCKET_ROBUSTNESS_ANALYSIS.md
│ │ └── WEBSOCKET_VISUAL_GUIDE.md
│ ├── 2026-01-27_pr384-code-review/
│ │ ├── PR384_CODE_REVIEW.md
│ │ └── README.md
│ ├── 2026-02-01_memory-management-bug-fix/
│ │ ├── BUG_FIX_ASSESSMENT.md
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ ├── QUICK_REFERENCE.md
│ │ └── README.md
│ ├── 2026-02-04_flash-approach-assessment/
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ ├── FLASH_APPROACH_ASSESSMENT.md
│ │ └── README.md
│ ├── 2026-02-06_config-strategy-analysis/
│ │ └── CONFIG_STRATEGY_EVALUATION.md
│ ├── 2026-02-11_codebase-improvements/
│ │ ├── ACTION_CHECKLIST.md
│ │ ├── BACKWARDS_COMPATIBILITY_PROOF.md
│ │ ├── CODEBASE_REVIEW.md
│ │ ├── EXECUTIVE_SUMMARY.md
│ │ └── README.md
│ ├── 2026-02-13_codebase-review/
│ │ ├── CODEBASE_REVIEW.md
│ │ └── README.md
│ ├── 2026-02-15_opentherm-v42-compliance/
│ │ ├── OPENTHERM_V42_COMPLIANCE_PLAN.md
│ │ ├── OUT_OF_SCOPE_ANALYSIS.md
│ │ └── README.md
│ ├── 2026-02-16_restful-api-evaluation/
│ │ ├── IMPROVEMENT_PLAN.md
│ │ └── REST_API_EVALUATION.md
│ ├── 2026-02-20_issue-143-source-separation/
│ │ └── ISSUE_143_OPTIONS_ANALYSIS.md
│ ├── 2026-03-16_gpio-ota-postmortem/
│ │ ├── POSTMORTEM.md
│ │ └── README.md
│ ├── 2026-03-19_critical-review-refactoring/
│ │ └── REVIEW.md
│ ├── 2026-03-20_v1.2.0-to-v1.3.0-beta-review/
│ │ ├── FIXES_APPLIED.md
│ │ └── REVIEW.md
│ ├── 2026-04-07_issue-525-sdk-dhcp-analysis/
│ │ └── ANALYSIS_REPORT.md
│ ├── 2026-04-07_opentherm-spec-deep-audit/
│ │ ├── AUDIT_REPORT.md
│ │ ├── AUDIT_REPORT_EN.md
│ │ ├── AUDIT_REPORT_NL.md
│ │ └── README.md
│ └── 2026-04-24_v1.4.1-to-dev-handoff/
│ └── FINDINGS.md
├── evaluate.py
├── example-api/
│ ├── API_CHANGES_v1.0.0.md
│ ├── api-call-responses.txt
│ ├── hotwater_examples.md
│ └── outside_temperature_override_examples.md
├── flash_esp.py
├── flash_otgw.bat
├── flash_otgw.sh
├── logfile.txt
├── package.json
├── plan/
│ ├── OTGW_1.5.0_Beta_11.txt
│ └── process-debug-ota-filesystem-regression-1.md
├── scripts/
│ ├── README.md
│ ├── autoinc-semver.py
│ ├── branch-hygiene-queue.ps1
│ └── webui_launcher.py
├── src/
│ ├── OTGW-firmware/
│ │ ├── Debug.h
│ │ ├── FSexplorer.ino
│ │ ├── MQTTstuff.h
│ │ ├── MQTTstuff.ino
│ │ ├── OTGW-Core.h
│ │ ├── OTGW-Core.ino
│ │ ├── OTGW-ModUpdateServer-impl.h
│ │ ├── OTGW-ModUpdateServer.h
│ │ ├── OTGW-firmware.h
│ │ ├── OTGW-firmware.ino
│ │ ├── SATcontrol.ino
│ │ ├── SATcycles.ino
│ │ ├── SATpid.ino
│ │ ├── SATpressure.ino
│ │ ├── SATweather.ino
│ │ ├── data/
│ │ │ ├── FSexplorer.css
│ │ │ ├── FSexplorer.html
│ │ │ ├── FSexplorer_dark.css
│ │ │ ├── ds-tokens.css
│ │ │ ├── graph.js
│ │ │ ├── index.css
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ ├── index_common.css
│ │ │ ├── index_dark.css
│ │ │ ├── pic16f1847/
│ │ │ │ ├── diagnose.hex
│ │ │ │ ├── diagnose.ver
│ │ │ │ ├── gateway.hex
│ │ │ │ ├── gateway.ver
│ │ │ │ ├── interface.hex
│ │ │ │ └── interface.ver
│ │ │ ├── pic16f88/
│ │ │ │ ├── diagnose.hex
│ │ │ │ ├── diagnose.ver
│ │ │ │ ├── gateway-4.3.hex
│ │ │ │ ├── gateway-4.3.ver
│ │ │ │ ├── gateway.hex
│ │ │ │ ├── gateway.ver
│ │ │ │ ├── interface.hex
│ │ │ │ └── interface.ver
│ │ │ ├── settings.ini
│ │ │ └── version.hash
│ │ ├── handleDebug.ino
│ │ ├── helperStuff.ino
│ │ ├── jsonStuff.ino
│ │ ├── mqtt_configuratie.cpp
│ │ ├── mqtt_discovery_verify.cpp
│ │ ├── mqtt_discovery_verify.h
│ │ ├── networkStuff.h
│ │ ├── networkStuff.h.tmp
│ │ ├── networkStuff.ino
│ │ ├── outputs_ext.ino
│ │ ├── restAPI.ino
│ │ ├── s0PulseCount.ino
│ │ ├── safeTimers.h
│ │ ├── sensors_ext.ino
│ │ ├── settingStuff.ino
│ │ ├── updateServerHtml.h
│ │ ├── version.h
│ │ ├── versionStuff.ino
│ │ ├── webSocketStuff.ino
│ │ └── webhook.ino
│ ├── OTGW-firmware$f
│ └── libraries/
│ └── OTGWSerial/
│ ├── OTGWSerial.cpp
│ └── OTGWSerial.h
├── test_flash_automation.py
├── tests/
│ ├── README.md
│ └── test_dallas_address.cpp
└── tools/
└── opentherm_v42_spec_audit.py
SYMBOL INDEX (845 symbols across 77 files)
FILE: .github/skills/algorithmic-art/templates/generator_template.js
function initializeSeed (line 43) | function initializeSeed(seed) {
function setup (line 53) | function setup() {
function draw (line 70) | function draw() {
class Entity (line 92) | class Entity {
method constructor (line 93) | constructor() {
method update (line 98) | update() {
method display (line 106) | display() {
function hexToRgb (line 132) | function hexToRgb(hex) {
function colorFromPalette (line 141) | function colorFromPalette(index) {
function mapRange (line 146) | function mapRange(value, inMin, inMax, outMin, outMax) {
function easeInOutCubic (line 150) | function easeInOutCubic(t) {
function wrapAround (line 155) | function wrapAround(value, max) {
function updateParameter (line 165) | function updateParameter(paramName, value) {
function regenerate (line 171) | function regenerate() {
function fadeBackground (line 183) | function fadeBackground(opacity) {
function getNoiseValue (line 190) | function getNoiseValue(x, y, scale = 0.01) {
function vectorFromAngle (line 195) | function vectorFromAngle(angle, magnitude = 1) {
function exportImage (line 203) | function exportImage() {
FILE: .github/skills/docx/scripts/accept_changes.py
function accept_changes (line 36) | def accept_changes(
function _setup_libreoffice_macro (line 91) | def _setup_libreoffice_macro() -> bool:
FILE: .github/skills/docx/scripts/comment.py
function _generate_hex_id (line 68) | def _generate_hex_id() -> str:
function _encode_smart_quotes (line 80) | def _encode_smart_quotes(text: str) -> str:
function _append_xml (line 86) | def _append_xml(xml_path: Path, root_tag: str, content: str) -> None:
function _find_para_id (line 98) | def _find_para_id(comments_path: Path, comment_id: int) -> str | None:
function _get_next_rid (line 108) | def _get_next_rid(rels_path: Path) -> int:
function _has_relationship (line 121) | def _has_relationship(rels_path: Path, target: str) -> bool:
function _has_content_type (line 129) | def _has_content_type(ct_path: Path, part_name: str) -> bool:
function _ensure_comment_relationships (line 137) | def _ensure_comment_relationships(unpacked_dir: Path) -> None:
function _ensure_comment_content_types (line 179) | def _ensure_comment_content_types(unpacked_dir: Path) -> None:
function add_comment (line 218) | def add_comment(
FILE: .github/skills/docx/scripts/office/helpers/merge_runs.py
function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
function _get_child (line 59) | def _get_child(parent, tag: str):
function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
function _remove_elements (line 93) | def _remove_elements(root, tag: str):
function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
function _first_child_run (line 128) | def _first_child_run(container):
function _next_element_sibling (line 135) | def _next_element_sibling(node):
function _next_sibling_run (line 144) | def _next_sibling_run(node):
function _is_run (line 154) | def _is_run(node) -> bool:
function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
function _merge_run_content (line 170) | def _merge_run_content(target, source):
function _consolidate_text (line 178) | def _consolidate_text(run):
FILE: .github/skills/docx/scripts/office/helpers/simplify_redlines.py
function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
function _get_author (line 80) | def _get_author(elem) -> str:
function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...
FILE: .github/skills/docx/scripts/office/pack.py
function pack (line 24) | def pack(
function _run_validation (line 69) | def _run_validation(
function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:
FILE: .github/skills/docx/scripts/office/soffice.py
function get_soffice_env (line 24) | def get_soffice_env() -> dict:
function run_soffice (line 35) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
function _needs_shim (line 44) | def _needs_shim() -> bool:
function _ensure_shim (line 53) | def _ensure_shim() -> Path:
FILE: .github/skills/docx/scripts/office/unpack.py
function unpack (line 34) | def unpack(
function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:
FILE: .github/skills/docx/scripts/office/validate.py
function main (line 25) | def main():
FILE: .github/skills/docx/scripts/office/validators/base.py
class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
method validate (line 109) | def validate(self):
method repair (line 112) | def repair(self) -> int:
method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
method validate_xml (line 143) | def validate_xml(self):
method validate_namespaces (line 170) | def validate_namespaces(self):
method validate_unique_ids (line 199) | def validate_unique_ids(self):
method validate_file_references (line 289) | def validate_file_references(self):
method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
method validate_content_types (line 492) | def validate_content_types(self):
method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
method validate_against_xsd (line 636) | def validate_against_xsd(self):
method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):
FILE: .github/skills/docx/scripts/office/validators/docx.py
class DOCXSchemaValidator (line 16) | class DOCXSchemaValidator(BaseSchemaValidator):
method validate (line 24) | def validate(self):
method validate_whitespace_preservation (line 66) | def validate_whitespace_preservation(self):
method validate_deletions (line 112) | def validate_deletions(self):
method count_paragraphs_in_unpacked (line 163) | def count_paragraphs_in_unpacked(self):
method count_paragraphs_in_original (line 179) | def count_paragraphs_in_original(self):
method validate_insertions (line 202) | def validate_insertions(self):
method compare_paragraph_counts (line 243) | def compare_paragraph_counts(self):
method _parse_id_value (line 251) | def _parse_id_value(self, val: str, base: int = 16) -> int:
method validate_id_constraints (line 254) | def validate_id_constraints(self):
method validate_comment_markers (line 298) | def validate_comment_markers(self):
method repair (line 386) | def repair(self) -> int:
method repair_durableId (line 391) | def repair_durableId(self) -> int:
FILE: .github/skills/docx/scripts/office/validators/pptx.py
class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
method validate (line 25) | def validate(self):
method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):
FILE: .github/skills/docx/scripts/office/validators/redlining.py
class RedliningValidator (line 11) | class RedliningValidator:
method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
method repair (line 22) | def repair(self) -> int:
method validate (line 25) | def validate(self):
method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
method _extract_text_content (line 229) | def _extract_text_content(self, root):
FILE: .github/skills/mcp-builder/scripts/connections.py
class MCPConnection (line 13) | class MCPConnection(ABC):
method __init__ (line 16) | def __init__(self):
method _create_context (line 21) | def _create_context(self):
method __aenter__ (line 24) | async def __aenter__(self):
method __aexit__ (line 48) | async def __aexit__(self, exc_type, exc_val, exc_tb):
method list_tools (line 55) | async def list_tools(self) -> list[dict[str, Any]]:
method call_tool (line 67) | async def call_tool(self, tool_name: str, arguments: dict[str, Any]) -...
class MCPConnectionStdio (line 73) | class MCPConnectionStdio(MCPConnection):
method __init__ (line 76) | def __init__(self, command: str, args: list[str] = None, env: dict[str...
method _create_context (line 82) | def _create_context(self):
class MCPConnectionSSE (line 88) | class MCPConnectionSSE(MCPConnection):
method __init__ (line 91) | def __init__(self, url: str, headers: dict[str, str] = None):
method _create_context (line 96) | def _create_context(self):
class MCPConnectionHTTP (line 100) | class MCPConnectionHTTP(MCPConnection):
method __init__ (line 103) | def __init__(self, url: str, headers: dict[str, str] = None):
method _create_context (line 108) | def _create_context(self):
function create_connection (line 112) | def create_connection(
FILE: .github/skills/mcp-builder/scripts/evaluation.py
function parse_evaluation_file (line 56) | def parse_evaluation_file(file_path: Path) -> list[dict[str, Any]]:
function extract_xml_content (line 79) | def extract_xml_content(text: str, tag: str) -> str | None:
function agent_loop (line 86) | async def agent_loop(
function evaluate_single_task (line 154) | async def evaluate_single_task(
function run_evaluation (line 220) | async def run_evaluation(
function parse_headers (line 275) | def parse_headers(header_list: list[str]) -> dict[str, str]:
function parse_env_vars (line 290) | def parse_env_vars(env_list: list[str]) -> dict[str, str]:
function main (line 305) | async def main():
FILE: .github/skills/pdf/scripts/check_bounding_boxes.py
class RectAndField (line 9) | class RectAndField:
function get_bounding_box_messages (line 15) | def get_bounding_box_messages(fields_json_stream) -> list[str]:
FILE: .github/skills/pdf/scripts/convert_pdf_to_images.py
function convert (line 9) | def convert(pdf_path, output_dir, max_dim=1000):
FILE: .github/skills/pdf/scripts/create_validation_image.py
function create_validation_image (line 9) | def create_validation_image(page_number, fields_json_path, input_path, o...
FILE: .github/skills/pdf/scripts/extract_form_field_info.py
function get_full_annotation_field_id (line 9) | def get_full_annotation_field_id(annotation):
function make_field_dict (line 19) | def make_field_dict(field, field_id):
function get_field_info (line 47) | def get_field_info(reader: PdfReader):
function write_field_info (line 110) | def write_field_info(pdf_path: str, json_output_path: str):
FILE: .github/skills/pdf/scripts/extract_form_structure.py
function extract_form_structure (line 20) | def extract_form_structure(pdf_path):
function main (line 91) | def main():
FILE: .github/skills/pdf/scripts/fill_fillable_fields.py
function fill_pdf_fields (line 11) | def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_p...
function validation_error_for_field_value (line 55) | def validation_error_for_field_value(field_info, field_value):
function monkeypatch_pydpf_method (line 74) | def monkeypatch_pydpf_method():
FILE: .github/skills/pdf/scripts/fill_pdf_form_with_annotations.py
function transform_from_image_coords (line 10) | def transform_from_image_coords(bbox, image_width, image_height, pdf_wid...
function transform_from_pdf_coords (line 23) | def transform_from_pdf_coords(bbox, pdf_height):
function fill_pdf_form (line 33) | def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):
FILE: .github/skills/pptx/scripts/add_slide.py
function get_next_slide_number (line 27) | def get_next_slide_number(slides_dir: Path) -> int:
function create_slide_from_layout (line 33) | def create_slide_from_layout(unpacked_dir: Path, layout_file: str) -> None:
function duplicate_slide (line 90) | def duplicate_slide(unpacked_dir: Path, source: str) -> None:
function _add_to_content_types (line 130) | def _add_to_content_types(unpacked_dir: Path, dest: str) -> None:
function _add_to_presentation_rels (line 141) | def _add_to_presentation_rels(unpacked_dir: Path, dest: str) -> str:
function _get_next_slide_id (line 158) | def _get_next_slide_id(unpacked_dir: Path) -> int:
function parse_source (line 165) | def parse_source(source: str) -> tuple[str, str | None]:
FILE: .github/skills/pptx/scripts/clean.py
function get_slides_in_sldidlst (line 27) | def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
function remove_orphaned_slides (line 49) | def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
function remove_trash_directory (line 91) | def remove_trash_directory(unpacked_dir: Path) -> list[str]:
function get_slide_referenced_files (line 106) | def get_slide_referenced_files(unpacked_dir: Path) -> set:
function remove_orphaned_rels_files (line 128) | def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
function get_referenced_files (line 153) | def get_referenced_files(unpacked_dir: Path) -> set:
function remove_orphaned_files (line 171) | def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[s...
function update_content_types (line 221) | def update_content_types(unpacked_dir: Path, removed_files: list[str]) -...
function clean_unused_files (line 241) | def clean_unused_files(unpacked_dir: Path) -> list[str]:
FILE: .github/skills/pptx/scripts/office/helpers/merge_runs.py
function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
function _get_child (line 59) | def _get_child(parent, tag: str):
function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
function _remove_elements (line 93) | def _remove_elements(root, tag: str):
function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
function _first_child_run (line 128) | def _first_child_run(container):
function _next_element_sibling (line 135) | def _next_element_sibling(node):
function _next_sibling_run (line 144) | def _next_sibling_run(node):
function _is_run (line 154) | def _is_run(node) -> bool:
function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
function _merge_run_content (line 170) | def _merge_run_content(target, source):
function _consolidate_text (line 178) | def _consolidate_text(run):
FILE: .github/skills/pptx/scripts/office/helpers/simplify_redlines.py
function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
function _get_author (line 80) | def _get_author(elem) -> str:
function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...
FILE: .github/skills/pptx/scripts/office/pack.py
function pack (line 24) | def pack(
function _run_validation (line 69) | def _run_validation(
function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:
FILE: .github/skills/pptx/scripts/office/soffice.py
function get_soffice_env (line 24) | def get_soffice_env() -> dict:
function run_soffice (line 35) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
function _needs_shim (line 44) | def _needs_shim() -> bool:
function _ensure_shim (line 53) | def _ensure_shim() -> Path:
FILE: .github/skills/pptx/scripts/office/unpack.py
function unpack (line 34) | def unpack(
function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:
FILE: .github/skills/pptx/scripts/office/validate.py
function main (line 25) | def main():
FILE: .github/skills/pptx/scripts/office/validators/base.py
class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
method validate (line 109) | def validate(self):
method repair (line 112) | def repair(self) -> int:
method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
method validate_xml (line 143) | def validate_xml(self):
method validate_namespaces (line 170) | def validate_namespaces(self):
method validate_unique_ids (line 199) | def validate_unique_ids(self):
method validate_file_references (line 289) | def validate_file_references(self):
method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
method validate_content_types (line 492) | def validate_content_types(self):
method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
method validate_against_xsd (line 636) | def validate_against_xsd(self):
method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):
FILE: .github/skills/pptx/scripts/office/validators/docx.py
class DOCXSchemaValidator (line 16) | class DOCXSchemaValidator(BaseSchemaValidator):
method validate (line 24) | def validate(self):
method validate_whitespace_preservation (line 66) | def validate_whitespace_preservation(self):
method validate_deletions (line 112) | def validate_deletions(self):
method count_paragraphs_in_unpacked (line 163) | def count_paragraphs_in_unpacked(self):
method count_paragraphs_in_original (line 179) | def count_paragraphs_in_original(self):
method validate_insertions (line 202) | def validate_insertions(self):
method compare_paragraph_counts (line 243) | def compare_paragraph_counts(self):
method _parse_id_value (line 251) | def _parse_id_value(self, val: str, base: int = 16) -> int:
method validate_id_constraints (line 254) | def validate_id_constraints(self):
method validate_comment_markers (line 298) | def validate_comment_markers(self):
method repair (line 386) | def repair(self) -> int:
method repair_durableId (line 391) | def repair_durableId(self) -> int:
FILE: .github/skills/pptx/scripts/office/validators/pptx.py
class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
method validate (line 25) | def validate(self):
method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):
FILE: .github/skills/pptx/scripts/office/validators/redlining.py
class RedliningValidator (line 11) | class RedliningValidator:
method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
method repair (line 22) | def repair(self) -> int:
method validate (line 25) | def validate(self):
method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
method _extract_text_content (line 229) | def _extract_text_content(self, root):
FILE: .github/skills/pptx/scripts/thumbnail.py
function main (line 40) | def main():
function get_slide_info (line 95) | def get_slide_info(pptx_path: Path) -> list[dict]:
function build_slide_list (line 121) | def build_slide_list(
function create_hidden_placeholder (line 149) | def create_hidden_placeholder(size: tuple[int, int]) -> Image.Image:
function convert_to_images (line 158) | def convert_to_images(pptx_path: Path, temp_dir: Path) -> list[Path]:
function create_grids (line 196) | def create_grids(
function create_grid (line 225) | def create_grid(
FILE: .github/skills/skill-creator/scripts/init_skill.py
function title_case_skill_name (line 189) | def title_case_skill_name(skill_name):
function init_skill (line 194) | def init_skill(skill_name, path):
function main (line 273) | def main():
FILE: .github/skills/skill-creator/scripts/package_skill.py
function package_skill (line 19) | def package_skill(skill_path, output_dir=None):
function main (line 85) | def main():
FILE: .github/skills/skill-creator/scripts/quick_validate.py
function validate_skill (line 12) | def validate_skill(skill_path):
FILE: .github/skills/webapp-testing/examples/console_logging.py
function handle_console_message (line 14) | def handle_console_message(msg):
FILE: .github/skills/webapp-testing/scripts/with_server.py
function is_server_ready (line 23) | def is_server_ready(port, timeout=30):
function main (line 35) | def main():
FILE: .github/skills/xlsx/scripts/office/helpers/merge_runs.py
function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
function _get_child (line 59) | def _get_child(parent, tag: str):
function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
function _remove_elements (line 93) | def _remove_elements(root, tag: str):
function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
function _first_child_run (line 128) | def _first_child_run(container):
function _next_element_sibling (line 135) | def _next_element_sibling(node):
function _next_sibling_run (line 144) | def _next_sibling_run(node):
function _is_run (line 154) | def _is_run(node) -> bool:
function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
function _merge_run_content (line 170) | def _merge_run_content(target, source):
function _consolidate_text (line 178) | def _consolidate_text(run):
FILE: .github/skills/xlsx/scripts/office/helpers/simplify_redlines.py
function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
function _get_author (line 80) | def _get_author(elem) -> str:
function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...
FILE: .github/skills/xlsx/scripts/office/pack.py
function pack (line 24) | def pack(
function _run_validation (line 69) | def _run_validation(
function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:
FILE: .github/skills/xlsx/scripts/office/soffice.py
function get_soffice_env (line 24) | def get_soffice_env() -> dict:
function run_soffice (line 35) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
function _needs_shim (line 44) | def _needs_shim() -> bool:
function _ensure_shim (line 53) | def _ensure_shim() -> Path:
FILE: .github/skills/xlsx/scripts/office/unpack.py
function unpack (line 34) | def unpack(
function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:
FILE: .github/skills/xlsx/scripts/office/validate.py
function main (line 25) | def main():
FILE: .github/skills/xlsx/scripts/office/validators/base.py
class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
method validate (line 109) | def validate(self):
method repair (line 112) | def repair(self) -> int:
method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
method validate_xml (line 143) | def validate_xml(self):
method validate_namespaces (line 170) | def validate_namespaces(self):
method validate_unique_ids (line 199) | def validate_unique_ids(self):
method validate_file_references (line 289) | def validate_file_references(self):
method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
method validate_content_types (line 492) | def validate_content_types(self):
method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
method validate_against_xsd (line 636) | def validate_against_xsd(self):
method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):
FILE: .github/skills/xlsx/scripts/office/validators/docx.py
class DOCXSchemaValidator (line 16) | class DOCXSchemaValidator(BaseSchemaValidator):
method validate (line 24) | def validate(self):
method validate_whitespace_preservation (line 66) | def validate_whitespace_preservation(self):
method validate_deletions (line 112) | def validate_deletions(self):
method count_paragraphs_in_unpacked (line 163) | def count_paragraphs_in_unpacked(self):
method count_paragraphs_in_original (line 179) | def count_paragraphs_in_original(self):
method validate_insertions (line 202) | def validate_insertions(self):
method compare_paragraph_counts (line 243) | def compare_paragraph_counts(self):
method _parse_id_value (line 251) | def _parse_id_value(self, val: str, base: int = 16) -> int:
method validate_id_constraints (line 254) | def validate_id_constraints(self):
method validate_comment_markers (line 298) | def validate_comment_markers(self):
method repair (line 386) | def repair(self) -> int:
method repair_durableId (line 391) | def repair_durableId(self) -> int:
FILE: .github/skills/xlsx/scripts/office/validators/pptx.py
class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
method validate (line 25) | def validate(self):
method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):
FILE: .github/skills/xlsx/scripts/office/validators/redlining.py
class RedliningValidator (line 11) | class RedliningValidator:
method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
method repair (line 22) | def repair(self) -> int:
method validate (line 25) | def validate(self):
method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
method _extract_text_content (line 229) | def _extract_text_content(self, root):
FILE: .github/skills/xlsx/scripts/recalc.py
function has_gtimeout (line 32) | def has_gtimeout():
function setup_libreoffice_macro (line 42) | def setup_libreoffice_macro():
function recalc (line 70) | def recalc(filename, timeout=30):
function main (line 164) | def main():
FILE: build.py
class Colors (line 48) | class Colors:
method disable (line 62) | def disable():
function print_step (line 75) | def print_step(message):
function print_success (line 82) | def print_success(message):
function print_error (line 87) | def print_error(message):
function print_warning (line 92) | def print_warning(message):
function print_info (line 97) | def print_info(message):
function run_command (line 102) | def run_command(cmd, cwd=None, env=None, check=True, capture_output=Fals...
function get_system_info (line 148) | def get_system_info():
function check_python_version (line 156) | def check_python_version():
function setup_arduino_config (line 166) | def setup_arduino_config(project_dir):
function install_dependencies (line 194) | def install_dependencies(project_dir, config_file):
function install_arduino_cli (line 232) | def install_arduino_cli(system):
function update_version (line 357) | def update_version(project_dir):
function get_semver (line 390) | def get_semver(project_dir):
function create_build_directory (line 409) | def create_build_directory(project_dir):
function build_firmware (line 430) | def build_firmware(project_dir, config_file):
function build_filesystem (line 458) | def build_filesystem(project_dir, config_file):
function consolidate_build_artifacts (line 504) | def consolidate_build_artifacts(project_dir):
function rename_build_artifacts (line 575) | def rename_build_artifacts(project_dir, semver):
function list_build_artifacts (line 618) | def list_build_artifacts(project_dir):
function create_merged_binary (line 641) | def create_merged_binary(project_dir, semver, compress=False):
function check_esptool (line 756) | def check_esptool():
function cleanup_temp_directory (line 799) | def cleanup_temp_directory(project_dir):
function clean_build (line 816) | def clean_build(project_dir):
function main (line 836) | def main():
FILE: docs/archive/mqttha-generator/generate_mqttha_data.py
function _mdi_to_enum (line 200) | def _mdi_to_enum(mdi_name: str) -> str:
function load_icon_overrides (line 207) | def load_icon_overrides(path: str) -> dict:
function c_escape (line 257) | def c_escape(s: str) -> str:
function to_c_ident (line 269) | def to_c_ident(s: str) -> str:
function compute_flags (line 277) | def compute_flags(topic: str, msg: str) -> int:
function parse_config (line 290) | def parse_config(path: str):
function get_entity_type (line 318) | def get_entity_type(topic: str) -> str:
function extract_label (line 325) | def extract_label(msg_json: dict, topic: str) -> str:
function extract_friendly_name (line 337) | def extract_friendly_name(msg_json: dict) -> str:
function determine_sensor_icon (line 346) | def determine_sensor_icon(device_class: str, label: str, unit: str) -> str:
function determine_binsensor_icon (line 452) | def determine_binsensor_icon(label: str) -> str:
function determine_entity_cat (line 478) | def determine_entity_cat(ot_id: int, label: str) -> str:
function determine_enabled_by_default (line 489) | def determine_enabled_by_default(ot_id: int, label: str) -> bool:
class SensorEntry (line 502) | class SensorEntry:
method __init__ (line 503) | def __init__(self, ot_id, flags, label, friendly_name, device_class, u...
class BinSensorEntry (line 517) | class BinSensorEntry:
method __init__ (line 518) | def __init__(self, ot_id, flags, label, friendly_name, icon, entity_ca...
class SpecialEntry (line 528) | class SpecialEntry:
method __init__ (line 530) | def __init__(self, ot_id, entity_type, topic, msg, flags):
function process_entries (line 538) | def process_entries(raw_entries):
function collect_progmem_strings (line 599) | def collect_progmem_strings(sensors, bin_sensors):
function build_index (line 638) | def build_index(sorted_entries, count):
function generate_cpp (line 649) | def generate_cpp(sensors, bin_sensors, specials, labels, names, output_p...
function main (line 882) | def main():
FILE: docs/archive/mqttha-generator/generate_mqttha_progmem.py
function c_escape (line 58) | def c_escape(s: str) -> str:
function parse_config (line 71) | def parse_config(path: str):
function build_index (line 98) | def build_index(entries):
function compute_flags (line 108) | def compute_flags(topic: str, msg: str) -> int:
function build_pools (line 118) | def build_pools(sorted_entries):
function pool_to_c_string (line 137) | def pool_to_c_string(pool: bytes) -> str:
function generate_header (line 161) | def generate_header(count, topic_pool_size, msg_pool_size, output_path, ...
function generate_cpp (line 217) | def generate_cpp(sorted_entries, topic_pool, msg_pool, t_offsets, m_offs...
function main (line 293) | def main():
FILE: docs/archive/mqttha-generator/generate_mqttha_readable.py
function c_escape (line 50) | def c_escape(s: str) -> str:
function compute_flags (line 62) | def compute_flags(topic: str, msg: str) -> int:
function derive_short_name (line 73) | def derive_short_name(topic: str) -> str:
function format_json_multiline (line 110) | def format_json_multiline(json_str: str, indent: str = " ") -> list[s...
function parse_config (line 165) | def parse_config(path: str):
function build_index (line 193) | def build_index(sorted_entries):
function assign_names (line 204) | def assign_names(sorted_entries):
function describe_entry (line 237) | def describe_entry(topic: str, msg: str) -> str:
function generate_header (line 261) | def generate_header(count, output_path, timestamp):
function generate_cpp (line 320) | def generate_cpp(sorted_entries, names, output_path, timestamp):
function main (line 423) | def main():
FILE: evaluate.py
class Colors (line 40) | class Colors:
method disable (line 52) | def disable():
class EvaluationResult (line 58) | class EvaluationResult:
method __init__ (line 60) | def __init__(self, category: str, name: str, status: str, message: str...
method __repr__ (line 68) | def __repr__(self):
class WorkspaceEvaluator (line 74) | class WorkspaceEvaluator:
method __init__ (line 77) | def __init__(self, project_dir: Path, verbose: bool = False):
method add_result (line 83) | def add_result(self, result: EvaluationResult):
method run_command (line 90) | def run_command(self, cmd: List[str], capture: bool = True) -> Tuple[i...
method check_code_structure (line 108) | def check_code_structure(self):
method check_time_boundary_single_caller (line 159) | def check_time_boundary_single_caller(self):
method _extract_function_body (line 224) | def _extract_function_body(source: str, signature_start: int) -> Tuple...
method check_discovery_counter_instrumented (line 286) | def check_discovery_counter_instrumented(self):
method check_publishedtopic_counter_reset (line 360) | def check_publishedtopic_counter_reset(self):
method check_ha_sensor_index_consistency (line 431) | def check_ha_sensor_index_consistency(self):
method check_json_buffer_arithmetic (line 561) | def check_json_buffer_arithmetic(self):
method check_status_burst_cooldown_bound (line 767) | def check_status_burst_cooldown_bound(self):
method check_status_publishers_wrap_burst (line 831) | def check_status_publishers_wrap_burst(self):
method check_ps_summary_master_topic_gate (line 902) | def check_ps_summary_master_topic_gate(self):
method check_adr_references_resolve (line 1007) | def check_adr_references_resolve(self):
method check_coding_standards (line 1077) | def check_coding_standards(self):
method check_memory_usage (line 1129) | def check_memory_usage(self):
method check_build_system (line 1158) | def check_build_system(self):
method check_dependencies (line 1204) | def check_dependencies(self):
method check_documentation (line 1239) | def check_documentation(self):
method check_security (line 1312) | def check_security(self):
method check_git_repository (line 1367) | def check_git_repository(self):
method check_filesystem_data (line 1420) | def check_filesystem_data(self):
method check_version_info (line 1454) | def check_version_info(self):
method evaluate_all (line 1487) | def evaluate_all(self, quick: bool = False):
method print_summary (line 1519) | def print_summary(self):
method generate_report (line 1550) | def generate_report(self, output_file: Path):
function main (line 1581) | def main():
FILE: flash_esp.py
class Colors (line 40) | class Colors:
method disable (line 53) | def disable():
function print_header (line 66) | def print_header(text):
function print_success (line 73) | def print_success(text):
function print_error (line 78) | def print_error(text):
function print_warning (line 83) | def print_warning(text):
function print_info (line 88) | def print_info(text):
function get_latest_release_info (line 93) | def get_latest_release_info():
function download_release_assets (line 122) | def download_release_assets(release_info, download_dir):
function build_firmware (line 177) | def build_firmware():
function check_python_version (line 251) | def check_python_version():
function check_esptool (line 258) | def check_esptool():
function detect_serial_ports (line 317) | def detect_serial_ports():
function select_port (line 359) | def select_port(ports, default_port=None):
function find_firmware_files (line 402) | def find_firmware_files():
function check_build_artifacts (line 434) | def check_build_artifacts():
function interactive_mode_selection (line 467) | def interactive_mode_selection():
function select_file (line 536) | def select_file(files, file_type):
function flash_esp8266 (line 574) | def flash_esp8266(port, firmware_file=None, filesystem_file=None, baud=D...
function main (line 687) | def main():
FILE: scripts/autoinc-semver.py
function normalize_token (line 18) | def normalize_token(value):
function parse_int (line 29) | def parse_int(value, key):
function parse_version_file (line 37) | def parse_version_file(path):
function resolve_githash (line 81) | def resolve_githash(override, length):
function prerelease_suffix (line 97) | def prerelease_suffix(prerelease):
function update_version_header (line 106) | def update_version_header(path, version_info, githash, date_str, time_st...
function extract_version_from_file (line 179) | def extract_version_from_file(filepath):
function update_version_in_file (line 204) | def update_version_in_file(filepath, version_info):
function should_skip_path (line 242) | def should_skip_path(path, base_dir):
function update_files (line 265) | def update_files(directory, version_info, ext_list, check_only=False):
function update_version_hash (line 334) | def update_version_hash(path, githash):
function git_commit_changes (line 340) | def git_commit_changes(directory, version):
function main (line 351) | def main(directory, filename, git_enabled, increment, githash_override, ...
FILE: scripts/webui_launcher.py
function handle_client (line 12) | def handle_client(client_socket, target_host, target_port):
function forward (line 35) | def forward(source, destination):
function start_proxy (line 52) | def start_proxy(target_ip, target_port=80):
FILE: src/OTGW-firmware/Debug.h
function _debugPrintf_P (line 60) | void _debugPrintf_P(PGM_P fmt, ...) {
function _debugBOL (line 69) | void _debugBOL(const char *fn, int line)
FILE: src/OTGW-firmware/MQTTstuff.h
function pgm_strncmp_PP (line 25) | inline int pgm_strncmp_PP(PGM_P s1, const char *s2, size_t n)
function pgm_read_char (line 36) | inline char pgm_read_char(PGM_P p)
type class (line 51) | enum class
type class (line 64) | enum class
function HaStateClass (line 85) | enum class HaStateClass : uint8_t {
FILE: src/OTGW-firmware/OTGW-Core.h
type OTdataStruct (line 26) | typedef struct {
type OTValueType (line 178) | enum OTValueType {
type OpenThermMessageType (line 192) | enum OpenThermMessageType {
type OpenThermMessageID (line 205) | enum OpenThermMessageID {
type OTtype_t (line 323) | enum OTtype_t { ot_f88, ot_s16, ot_s8s8, ot_u16, ot_u8u8, ot_flag8, ot_f...
type OTmsgcmd_t (line 324) | enum OTmsgcmd_t { OT_READ, OT_WRITE, OT_RW, OT_UNDEF }
type OTlookup_t (line 326) | struct OTlookup_t
function OTPublishGate (line 499) | struct OTPublishGate {
type OT_cmd_t (line 507) | struct OT_cmd_t { // see all possible commands for PIC here: https://otg...
type OT_cmd_t (line 515) | struct OT_cmd_t
type OTGW_response_type (line 527) | enum OTGW_response_type {
type OpenthermData_t (line 538) | struct OpenthermData_t {
FILE: src/OTGW-firmware/OTGW-ModUpdateServer-impl.h
function namespace (line 53) | namespace esp8266httpupdateserver {
FILE: src/OTGW-firmware/OTGW-ModUpdateServer.h
function namespace (line 26) | namespace esp8266httpupdateserver {
function namespace (line 95) | namespace BearSSL {
FILE: src/OTGW-firmware/OTGW-firmware.h
function strlcpy_P (line 22) | inline size_t strlcpy_P(char *dst, PGM_P src, size_t size) {
type HeapHealthLevel (line 98) | enum HeapHealthLevel {
type class (line 176) | enum class
type class (line 182) | enum class
type PICSection (line 241) | struct PICSection { // state.pic — PIC microcontroller identi...
type OTGWProtocol (line 248) | struct OTGWProtocol { // state.otgw — OpenTherm protocol & bus ...
type MQTTRuntimeSection (line 257) | struct MQTTRuntimeSection { // state.mqtt — MQTT broker connection state
type FlashSection (line 262) | struct FlashSection { // state.flash — Firmware upgrade operations
type DebugSection (line 270) | struct DebugSection { // state.debug — Runtime diagnostic outpu...
type UptimeSection (line 286) | struct UptimeSection { // state.uptime — System longevity counters
type class (line 296) | enum class
type DiscoverySection (line 304) | struct DiscoverySection { // state.discovery — MQTT a...
type HeapDiagSection (line 325) | struct HeapDiagSection { // state.heapdiag — cumulative ...
type PicSettingsSection (line 336) | struct PicSettingsSection { // state.picSettings — settings polled fr...
type SATHeatingSystem (line 365) | enum SATHeatingSystem : uint8_t {
type SATControlMode (line 371) | enum SATControlMode : uint8_t { SAT_MODE_OFF = 0, SAT_MODE_CONTINUOUS, S...
type SATCalibPhase (line 372) | enum SATCalibPhase : uint8_t {
type SATPreset (line 376) | enum SATPreset : uint8_t {
type SATFallbackReason (line 381) | enum SATFallbackReason : uint8_t {
type SATCycleClass (line 384) | enum SATCycleClass : uint8_t {
type SATCycleKind (line 388) | enum SATCycleKind : uint8_t {
type SATCyclePhase (line 391) | enum SATCyclePhase : uint8_t {
type SATCurveRecommendation (line 394) | enum SATCurveRecommendation : uint8_t {
type SATManufacturer (line 397) | enum SATManufacturer : uint8_t {
type SATFlameStatus (line 411) | enum SATFlameStatus : uint8_t {
type SATBoilerStatus (line 415) | enum SATBoilerStatus : uint8_t {
type SATWindowRecord (line 426) | struct SATWindowRecord {
type SATZoneState (line 435) | struct SATZoneState {
type SATRuntimeSection (line 446) | struct SATRuntimeSection { // state.sat — SAT thermostat control...
type OTGWState (line 578) | struct OTGWState {
function isPICEnabled (line 600) | inline bool isPICEnabled() { return state.pic.bAvailable; }
function isGatewayFirmware (line 601) | inline bool isGatewayFirmware() { return strcmp_P(state.pic.sType, PSTR(...
function hasOTCommandInterface (line 604) | inline bool hasOTCommandInterface() { return isPICEnabled(); }
type MQTTSettingsSection (line 611) | struct MQTTSettingsSection {
type NTPSection (line 629) | struct NTPSection {
type SensorsSection (line 636) | struct SensorsSection { // Dallas DS18B20 external sensors
type S0Section (line 643) | struct S0Section {
type OutputsSection (line 651) | struct OutputsSection { // GPIO relay outputs
type WebhookSection (line 657) | struct WebhookSection {
type UISection (line 666) | struct UISection {
type OTGWBootSection (line 676) | struct OTGWBootSection { // PIC boot-time command injection
type SATSection (line 683) | struct SATSection {
type DeviceSection (line 767) | struct DeviceSection {
type OTGWSettings (line 772) | struct OTGWSettings {
function isFlashing (line 812) | inline bool isFlashing() {
FILE: src/OTGW-firmware/data/graph.js
constant UPDATE_INTERVAL_MS (line 13) | const UPDATE_INTERVAL_MS = 2000;
function isDallasAddress (line 17) | function isDallasAddress(entry) {
FILE: src/OTGW-firmware/data/index.js
constant APIGW (line 12) | const APIGW = window.location.protocol + '//' + window.location.host + '...
constant MOBILE_BREAKPOINT_PX (line 13) | const MOBILE_BREAKPOINT_PX = 768;
constant PS_MODE_NOTICE_TEXT (line 14) | const PS_MODE_NOTICE_TEXT = 'PS=1 mode active: showing decoded field sum...
constant WEBKIT_SCROLLBAR_STYLE_ID (line 15) | const WEBKIT_SCROLLBAR_STYLE_ID = 'otgw-webkit-scrollbar-style';
function safeJSONParse (line 27) | function safeJSONParse(text) {
function safeGetElementById (line 54) | function safeGetElementById(id, warnIfMissing = false) {
function ensureWebkitScrollbarStyles (line 62) | function ensureWebkitScrollbarStyles() {
function isDallasAddress (line 84) | function isDallasAddress(entry) {
function fetchDallasLabels (line 100) | function fetchDallasLabels() {
constant PIC_SETTINGS_REFRESH_INTERVAL_MS (line 147) | const PIC_SETTINGS_REFRESH_INTERVAL_MS = 3000;
constant PIC_SETTINGS_CACHE_MAX_AGE_MS (line 148) | const PIC_SETTINGS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
function isPageVisible (line 150) | function isPageVisible() {
function isMainPageActive (line 154) | function isMainPageActive() {
function getPICSettingsStorageKey (line 159) | function getPICSettingsStorageKey() {
function getPICSettingsCache (line 163) | function getPICSettingsCache() {
function savePICSettingToCache (line 186) | function savePICSettingToCache(key, value, timestampMs) {
function getPICSettingFromCache (line 204) | function getPICSettingFromCache(key) {
function isPICSettingDiscovered (line 222) | function isPICSettingDiscovered(value) {
function startPICsettingsRefreshTimer (line 226) | function startPICsettingsRefreshTimer() {
function stopPICsettingsRefreshTimer (line 257) | function stopPICsettingsRefreshTimer() {
function startOTmonitorPolling (line 264) | function startOTmonitorPolling() {
function stopOTmonitorPolling (line 273) | function stopOTmonitorPolling() {
function startTimeUpdates (line 280) | function startTimeUpdates() {
function stopTimeUpdates (line 286) | function stopTimeUpdates() {
function setActivePageSection (line 293) | function setActivePageSection(activeId) {
constant GATEWAY_MODE_REFRESH_INTERVAL (line 336) | const GATEWAY_MODE_REFRESH_INTERVAL = 60;
function updateGatewayModeIndicator (line 338) | function updateGatewayModeIndicator(value) {
function parseGatewayModeValue (line 358) | function parseGatewayModeValue(modeValue) {
function formatGatewayModeDisplayValue (line 378) | function formatGatewayModeDisplayValue(modeValue) {
function formatDeviceInfoLabel (line 387) | function formatDeviceInfoLabel(key) {
function formatDeviceInfoValue (line 395) | function formatDeviceInfoValue(key, value) {
function applyParsedGatewayMode (line 410) | function applyParsedGatewayMode(parsedMode) {
function refreshGatewayMode (line 423) | function refreshGatewayMode(force) {
function enterFlashMode (line 465) | function enterFlashMode() {
function exitFlashMode (line 479) | function exitFlashMode() {
constant RENDER_LIMIT (line 837) | const RENDER_LIMIT = 2000;
constant TARGET_MEMORY_MB (line 840) | const TARGET_MEMORY_MB = 100;
constant STORAGE_SAFETY_MARGIN (line 841) | const STORAGE_SAFETY_MARGIN = 0.8;
constant WEBSOCKET_PORT (line 903) | const WEBSOCKET_PORT = 81;
constant WS_WATCHDOG_TIMEOUT (line 907) | const WS_WATCHDOG_TIMEOUT = 45000;
constant DEBUG_WS (line 913) | let DEBUG_WS = false;
constant PERSISTENCE_INTERVAL_MS (line 926) | const PERSISTENCE_INTERVAL_MS = 30000;
constant DEBOUNCE_SAVE_MS (line 927) | const DEBOUNCE_SAVE_MS = 2000;
constant PERSISTENCE_KEY_LOGS (line 928) | const PERSISTENCE_KEY_LOGS = 'otgw_log_buffer';
constant PERSISTENCE_KEY_PREFS (line 929) | const PERSISTENCE_KEY_PREFS = 'otgw_log_prefs';
function detectStorageQuota (line 936) | function detectStorageQuota() {
function estimateMemoryUsage (line 959) | function estimateMemoryUsage() {
function getActualMemoryUsage (line 983) | function getActualMemoryUsage() {
function calculateOptimalMaxLines (line 1007) | function calculateOptimalMaxLines() {
function updateDynamicLimits (line 1038) | function updateDynamicLimits() {
function updateMemoryDisplay (line 1055) | function updateMemoryDisplay() {
function saveDataToLocalStorage (line 1085) | function saveDataToLocalStorage() {
function restoreDataFromLocalStorage (line 1156) | function restoreDataFromLocalStorage() {
function clearStoredData (line 1217) | function clearStoredData() {
function debouncedSave (line 1228) | function debouncedSave() {
function startPersistenceTimer (line 1245) | function startPersistenceTimer() {
function stopPersistenceTimer (line 1263) | function stopPersistenceTimer() {
function stopScheduledOTLogWebSocketInit (line 1271) | function stopScheduledOTLogWebSocketInit() {
function scheduleOTLogWebSocketInit (line 1278) | function scheduleOTLogWebSocketInit(force, delayMs) {
function shutdownPageNetworking (line 1292) | function shutdownPageNetworking(reason) {
function persistOTLogBufferForUnload (line 1301) | function persistOTLogBufferForUnload() {
function resetWSWatchdog (line 1368) | function resetWSWatchdog() {
function getOTLogDisplayState (line 1392) | function getOTLogDisplayState() {
function updateOTLogModeNotice (line 1417) | function updateOTLogModeNotice(displayState) {
function setOTLogCommandsOnly (line 1434) | function setOTLogCommandsOnly(enabled) {
function updateOTLogResponsiveState (line 1462) | function updateOTLogResponsiveState() {
function handleOTLogResize (line 1496) | function handleOTLogResize() {
function initOTLogWebSocket (line 1506) | function initOTLogWebSocket(force) {
function disconnectOTLogWebSocket (line 1708) | function disconnectOTLogWebSocket() {
function updateWSStatus (line 1753) | function updateWSStatus(connected) {
function parseSimulationValue (line 1771) | function parseSimulationValue(rawValue) {
function applyPICAvailability (line 1783) | function applyPICAvailability(available) {
function applyOTGWSimulationState (line 1810) | function applyOTGWSimulationState(rawValue) {
function updateSimulationBadge (line 1817) | function updateSimulationBadge() {
function formatLogLine (line 1840) | function formatLogLine(logLine) {
function parseLogLine (line 1899) | function parseLogLine(line) {
function addLogLine (line 1996) | function addLogLine(logLine) {
function scheduleDisplayUpdate (line 2050) | function scheduleDisplayUpdate() {
function updateFilteredBuffer (line 2064) | function updateFilteredBuffer() {
function getMainPageContainer (line 2079) | function getMainPageContainer() {
function getOTLogContentElement (line 2123) | function getOTLogContentElement() {
function renderLogDisplay (line 2164) | function renderLogDisplay() {
function updateLogDisplay (line 2215) | function updateLogDisplay() {
function updateLogCounters (line 2229) | function updateLogCounters() {
function clearLogBuffer (line 2251) | function clearLogBuffer() {
function setupOTLogControls (line 2260) | function setupOTLogControls() {
function normalizeOTGWcommand (line 2438) | function normalizeOTGWcommand(cmd) {
function sendOTGWcommand (line 2456) | function sendOTGWcommand(cmd) {
function startFileStreaming (line 2528) | async function startFileStreaming() {
function getTodayDateString (line 2575) | function getTodayDateString() {
function rotateLogFile (line 2583) | async function rotateLogFile() {
function checkFileRotation (line 2617) | async function checkFileRotation() {
function stopFileStreaming (line 2640) | function stopFileStreaming() {
function enqueueLogLine (line 2661) | function enqueueLogLine(text) {
function processLogQueue (line 2667) | async function processLogQueue() {
function writeToStream (line 2714) | async function writeToStream(entry) {
function downloadLog (line 2727) | function downloadLog(isAuto = false) {
function forceDownloadBlob (line 2761) | function forceDownloadBlob(blob, filename) {
function toggleAutoDownloadLog (line 2774) | function toggleAutoDownloadLog(enabled) {
function loadUISettings (line 2808) | function loadUISettings() {
function saveUISetting (line 2814) | function saveUISetting(field, value) {
function renderSharedPageNavShell (line 2820) | function renderSharedPageNavShell() {
function updateThemeToggle (line 2843) | function updateThemeToggle() {
function initMainPage (line 2854) | function initMainPage() {
function checkFSMismatch (line 3034) | function checkFSMismatch() {
function showMainPage (line 3061) | function showMainPage() {
function firmwarePage (line 3084) | function firmwarePage() {
function deviceinfoPage (line 3092) | function deviceinfoPage() {
function settingsPage (line 3102) | function settingsPage() {
function webhookPage (line 3113) | function webhookPage() {
function toggleHidden (line 3126) | function toggleHidden(className, hideOnly) {
function setVisible (line 3138) | function setVisible(className, visible) {
function renderBottomMessage (line 3150) | function renderBottomMessage() {
function updateHeapDisplay (line 3181) | function updateHeapDisplay() {
function refreshDevTime (line 3188) | function refreshDevTime() {
function applyPSmodeState (line 3238) | function applyPSmodeState() {
function refreshFirmware (line 3261) | function refreshFirmware() {
function mapPICCode (line 3528) | function mapPICCode(code, table, fallbackPrefix) {
function formatPICSetpointOverride (line 3538) | function formatPICSetpointOverride(value) {
function formatPICDhwOverride (line 3555) | function formatPICDhwOverride(value) {
function formatPICGpioFunctions (line 3564) | function formatPICGpioFunctions(value) {
function formatPICGpioStates (line 3588) | function formatPICGpioStates(value) {
function formatPICLedFunctions (line 3601) | function formatPICLedFunctions(value) {
function formatPICTweaks (line 3628) | function formatPICTweaks(value) {
function formatPICTempSensor (line 3643) | function formatPICTempSensor(value) {
function formatPICSmartPower (line 3650) | function formatPICSmartPower(value) {
function formatPICThermostatDetect (line 3659) | function formatPICThermostatDetect(value) {
function formatPICResetCause (line 3669) | function formatPICResetCause(value) {
function formatPICVoltageRef (line 3683) | function formatPICVoltageRef(value) {
function formatPICSettingValue (line 3698) | function formatPICSettingValue(key, value) {
function setPICValueWithBreaks (line 3727) | function setPICValueWithBreaks(el, text, unit) {
function refreshPICsettings (line 3737) | function refreshPICsettings() {
function refreshDevInfo (line 3891) | function refreshDevInfo() {
function refreshOTmonitor (line 3932) | function refreshOTmonitor() {
function refreshDeviceInfo (line 4138) | function refreshDeviceInfo() {
function renderCrashLogInfo (line 4197) | function renderCrashLogInfo(crashlog) {
function refreshCrashLogInfo (line 4244) | function refreshCrashLogInfo() {
function isHttpPasswordPlaceholder (line 4283) | function isHttpPasswordPlaceholder(value) {
function getHttpPasswordPlaceholderLength (line 4287) | function getHttpPasswordPlaceholderLength(value) {
function isPasswordPlaceholderField (line 4308) | function isPasswordPlaceholderField(field) {
function getOriginalPasswordPrefill (line 4312) | function getOriginalPasswordPrefill(field) {
function refreshSettings (line 4326) | function refreshSettings() {
function testWebhookUI (line 4493) | function testWebhookUI(stateOn) {
function refreshWebhookPage (line 4515) | function refreshWebhookPage() {
function saveWebhookSettings (line 4628) | function saveWebhookSettings() {
function saveSettings (line 4658) | function saveSettings() {
function sendPostSetting (line 4706) | function sendPostSetting(field, value) {
function translateToHuman (line 4742) | function translateToHuman(longName) {
function translateTooltip (line 4770) | function translateTooltip(longName) {
function setBackGround (line 4797) | function setBackGround(field, newColor) {
function getBackGround (line 4808) | function getBackGround(field) {
function round (line 4816) | function round(value, precision) {
function printAllVals (line 4823) | function printAllVals(obj) {
function strToBool (line 4834) | function strToBool(s) {
function applyTheme (line 5042) | function applyTheme() {
function toggleInteraction (line 5075) | function toggleInteraction(enabled) {
function startFlash (line 5083) | function startFlash(filename) {
function parseFirmwareInfo (line 5088) | function parseFirmwareInfo(filename) {
function startFlashPolling (line 5114) | function startFlashPolling() {
function stopFlashPolling (line 5122) | function stopFlashPolling() {
function pollFlashStatus (line 5130) | function pollFlashStatus() {
function handleFlashCompletion (line 5175) | function handleFlashCompletion(filename, error) {
function handleFlashError (line 5216) | function handleFlashError(filename, error) {
function performFlash (line 5235) | function performFlash(filename) {
function handleFlashMessage (line 5317) | function handleFlashMessage(data) {
function otmGetTypeNibbleChar (line 5445) | function otmGetTypeNibbleChar(raw) {
function otmGetTypeFromRaw (line 5468) | function otmGetTypeFromRaw(raw) {
function otmTypeFromDirString (line 5476) | function otmTypeFromDirString(dir) {
function otmDirectionLabel (line 5491) | function otmDirectionLabel(typeCode, fallbackDir) {
function openLogTab (line 5508) | function openLogTab(evt, tabName) {
function processStatsLine (line 5529) | function processStatsLine(line) {
function scheduleStatsUpdate (line 5617) | function scheduleStatsUpdate() {
function updateStatisticsDisplay (line 5627) | function updateStatisticsDisplay() {
function sortStats (line 5680) | function sortStats(col) {
function saveUISetting (line 5691) | function saveUISetting(field, value) {
function setupPersistentUIListeners (line 5696) | function setupPersistentUIListeners() {
function loadPersistentUI (line 5722) | function loadPersistentUI() {
function openInlineSensorLabelEditor (line 5824) | function openInlineSensorLabelEditor(address, targetNode, evt) {
function closeInlineSensorLabelEditor (line 5890) | function closeInlineSensorLabelEditor(cancelOnly) {
function saveInlineSensorLabel (line 5910) | function saveInlineSensorLabel() {
function resetWiFiSettingsUI (line 5989) | function resetWiFiSettingsUI() {
FILE: src/OTGW-firmware/mqtt_configuratie.cpp
function PGM_P (line 1691) | PGM_P haDeviceClassStr(HaDeviceClass dc) {
function PGM_P (line 1705) | PGM_P haUnitStr(HaUnit u) {
function PGM_P (line 1726) | PGM_P haStateClassStr(HaStateClass sc) {
function PGM_P (line 1735) | PGM_P haIconStr(HaIcon ic) {
function PGM_P (line 1785) | PGM_P haEntityCatStr(HaEntityCat ec) {
function strlcpy_P (line 1823) | inline size_t strlcpy_P(char *dst, PGM_P src, size_t size) {
function writeJsonKV (line 1843) | static bool writeJsonKV(MqttJsonWriter &w, PGM_P key, const char *value) {
function writeJsonKV_P (line 1848) | static bool writeJsonKV_P(MqttJsonWriter &w, PGM_P key, PGM_P value) {
function writeJsonKVBool (line 1853) | static bool writeJsonKVBool(MqttJsonWriter &w, PGM_P key, bool value) {
function writeJsonOpen (line 1858) | static bool writeJsonOpen(MqttJsonWriter &w) { return w.writeChar('{'); }
function writeJsonClose (line 1859) | static bool writeJsonClose(MqttJsonWriter &w) { return w.writeChar('}'); }
function writeJsonComma (line 1860) | static bool writeJsonComma(MqttJsonWriter &w) { return w.writeChar(','); }
function writeFriendlyName (line 1876) | static bool writeFriendlyName(MqttJsonWriter &w, const char *s) {
function writeDeviceBlock (line 1920) | static bool writeDeviceBlock(MqttJsonWriter &w, const HaDiscoveryContext...
function writeOriginBlock (line 1947) | static bool writeOriginBlock(MqttJsonWriter &w, const HaDiscoveryContext...
function sanitizeHaObjectId (line 1962) | static void sanitizeHaObjectId(char *s) {
function composeSensorPayload (line 1975) | static bool composeSensorPayload(MqttJsonWriter &w,
function composeBinSensorPayload (line 2083) | static bool composeBinSensorPayload(MqttJsonWriter &w,
function buildSensorDiscoveryTopic (line 2163) | static bool buildSensorDiscoveryTopic(char *dest, size_t destSize,
function buildBinSensorDiscoveryTopic (line 2181) | static bool buildBinSensorDiscoveryTopic(char *dest, size_t destSize,
function streamSensorDiscovery (line 2201) | bool streamSensorDiscovery(PubSubClient &client,
function streamBinarySensorDiscovery (line 2240) | bool streamBinarySensorDiscovery(PubSubClient &client,
function streamDallasSensorDiscovery (line 2276) | bool streamDallasSensorDiscovery(PubSubClient &client,
function expandAndStreamSensorSources (line 2424) | bool expandAndStreamSensorSources(PubSubClient &client,
function streamClimateDiscovery (line 2476) | bool streamClimateDiscovery(PubSubClient &client,
function streamNumberDiscovery (line 2661) | bool streamNumberDiscovery(PubSubClient &client,
FILE: src/OTGW-firmware/mqtt_discovery_verify.cpp
function startDiscoveryVerification (line 98) | bool startDiscoveryVerification() {
function endDiscoveryVerification (line 172) | static void endDiscoveryVerification() {
function tickDiscoveryVerification (line 221) | void tickDiscoveryVerification() {
function isDiscoveryVerificationActive (line 264) | bool isDiscoveryVerificationActive() { return verifyActive; }
function handleDiscoveryVerifyMessage (line 278) | bool handleDiscoveryVerifyMessage(const char *topic, unsigned int /*leng...
FILE: src/OTGW-firmware/networkStuff.h
type timespec (line 63) | struct timespec
type NtpStatus_t (line 67) | enum NtpStatus_t {
FILE: src/OTGW-firmware/safeTimers.h
function __Due__ (line 160) | uint32_t __Due__(uint32_t &timer_due, uint32_t timer_interval, byte time...
function __TimeLeft__ (line 211) | uint32_t __TimeLeft__(uint32_t timer_due)
function __Once__ (line 221) | uint32_t __Once__(uint32_t timer_due)
function getParam (line 229) | uint32_t getParam(uint32_t i, ...)
FILE: src/OTGW-firmware/updateServerHtml.h
function catch (line 20) | catch (e) { console.error(e); }
function function (line 125) | function restoreDallasLabelsFromStorage(onStatus) {
function catch (line 159) | catch (e) {
function function (line 181) | function redirectToHome(delayMs) {
function function (line 187) | function pollUntilHealthy(options) {
function function (line 229) | function showProgressPage() {
function function (line 247) | function downloadBackup(url, prefix) {
function function (line 274) | function doBackups() {
function function (line 295) | function waitForDeviceReboot(onReady) {
function function (line 327) | function formatBytes(bytes) {
function function (line 334) | function initUploadForm(formId, targetName) {
function catch (line 471) | catch (e) {}
function function (line 488) | function tryRestoreLabels() {
function setTimeout (line 522) | setTimeout(function() { window.location.href = '/'; }
FILE: src/libraries/OTGWSerial/OTGWSerial.cpp
type PicInfo (line 84) | struct PicInfo
function p16f88recover (line 102) | unsigned short p16f88recover(unsigned short addr, unsigned short *code) {
function p16f1847recover (line 111) | unsigned short p16f1847recover(unsigned short addr, unsigned short *code) {
function OTGWError (line 128) | OTGWError OTGWUpgrade::start(const char *hexfile) {
function OTGWError (line 167) | OTGWError OTGWUpgrade::readHexRecord() {
function OTGWError (line 215) | OTGWError OTGWUpgrade::readHexFile(const char *hexfile) {
function OTGWError (line 769) | OTGWError OTGWUpgrade::finishUpgrade(OTGWError result) {
function OTGWFirmware (line 903) | OTGWFirmware OTGWSerial::firmwareType() {
function String (line 907) | String OTGWSerial::firmwareToString(OTGWFirmware fw) {
function String (line 920) | String OTGWSerial::firmwareToString() {
function OTGWProcessor (line 924) | OTGWProcessor OTGWSerial::processor() {
function String (line 935) | String OTGWSerial::processorToString(OTGWProcessor pic) {
function String (line 946) | String OTGWSerial::processorToString() {
function OTGWError (line 1009) | OTGWError OTGWSerial::startUpgrade(const char *hexfile) {
function OTGWError (line 1023) | OTGWError OTGWSerial::finishUpgrade(OTGWError result, short errors, shor...
FILE: src/libraries/OTGWSerial/OTGWSerial.h
type OTGWProcessor (line 31) | typedef enum {
type OTGWFirmware (line 39) | typedef enum {
type OTGWError (line 47) | typedef enum {
type PicInfo (line 62) | struct PicInfo {
type OTGWTransferData (line 75) | typedef struct {
function class (line 87) | class OTGWUpgrade {
function class (line 139) | class OTGWSerial: public HardwareSerial {
FILE: test_flash_automation.py
function run_command (line 11) | def run_command(cmd, description, timeout=300):
function main (line 77) | def main():
FILE: tests/test_dallas_address.cpp
function check (line 56) | static void check(const char* name, const char* expected, const char* got)
function main (line 64) | int main()
FILE: tools/opentherm_v42_spec_audit.py
function _read_text (line 84) | def _read_text(path: Path) -> str:
function _norm_cell (line 88) | def _norm_cell(cell: str) -> str:
function _norm_unit (line 95) | def _norm_unit(unit: str) -> str:
function _spec_type_key (line 107) | def _spec_type_key(spec_row: Dict[str, Any]) -> str:
function parse_spec_ids (line 140) | def parse_spec_ids(spec_text: str) -> Dict[int, Dict[str, Any]]:
function parse_enum_map (line 168) | def parse_enum_map(header_text: str) -> Dict[str, Dict[str, Any]]:
function parse_otmap (line 201) | def parse_otmap(header_text: str) -> Dict[int, Dict[str, Any]]:
function parse_decode_cases (line 222) | def parse_decode_cases(source_text: str, enum_map: Dict[str, Dict[str, A...
function parse_mqttha_entries (line 251) | def parse_mqttha_entries(mqttha_text: str) -> List[Dict[str, Any]]:
function detect_legacy_reserved_guard (line 277) | def detect_legacy_reserved_guard(source_text: str) -> Dict[str, bool]:
function build_audit (line 287) | def build_audit(
function write_csv (line 569) | def write_csv(path: Path, rows: List[Dict[str, Any]]) -> None:
function write_json (line 580) | def write_json(path: Path, data: Dict[str, Any]) -> None:
function summarize_failures (line 585) | def summarize_failures(findings: Dict[str, List[Dict[str, Any]]]) -> Tup...
function _print_counts (line 591) | def _print_counts(counts: Dict[str, int], title: str) -> None:
function parse_args (line 597) | def parse_args() -> argparse.Namespace:
function main (line 611) | def main() -> int:
Copy disabled (too large)
Download .json
Condensed preview — 844 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (15,065K chars).
[
{
"path": ".claude/.vscode/arduino.json",
"chars": 335,
"preview": "{\n \"configuration\": \"xtal=80,vt=flash,exception=legacy,ssl=all,eesz=4M2M,led=2,ip=lm2f,dbg=Disabled,lvl=None____,wipe"
},
{
"path": ".claude/.vscode/c_cpp_properties.json",
"chars": 18853,
"preview": "{\n \"version\": 4,\n \"configurations\": [\n {\n \"name\": \"Arduino\",\n \"compilerPath\": \"C:\\\\Us"
},
{
"path": ".claude/.vscode/settings.json",
"chars": 1554,
"preview": "{\n \"editor.suggest.snippetsPreventQuickSuggestions\": false,\n \"aiXcoder.showTrayIcon\": true,\n \"DevChat.Language\""
},
{
"path": ".claude/.vscode/tasks.json",
"chars": 560,
"preview": "{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"type\": \"cmake\",\n\t\t\t\"label\": \"CMake: build\",\n\t\t\t\"command\": \"build\",\n\t\t\t\"target"
},
{
"path": ".claude/adr-kit-guide.md",
"chars": 10936,
"preview": "<!-- adr-kit-guide v0.13.0 -->\n<!-- Canonical project-side ADR guide. Copied from the plugin's templates/adr-kit-guide.m"
},
{
"path": ".claude/backlog-cli-reference.md",
"chars": 25984,
"preview": "# Instructions for the usage of Backlog.md CLI Tool\n\n## Backlog.md: Comprehensive Project Management Tool via CLI\n\n### A"
},
{
"path": ".claude/commands/backlog_discord.md",
"chars": 8654,
"preview": "# /backlog_discord — Respond to backlog commands from Discord\n\nMonitor a Discord channel for backlog-related requests, e"
},
{
"path": ".claude/commands/check_otgw_issues.md",
"chars": 15006,
"preview": "# /check_otgw_issues — Monitor Discord, GitHub and Tweakers for user-reported issues\n\nScan the OTGW-firmware Discord ser"
},
{
"path": ".claude/docs/discord-backlog-bridge.md",
"chars": 7548,
"preview": "# Discord ↔ Backlog.md Bridge — Setup & Operations Guide\n\nHandoff document for any Claude Code instance that needs to op"
},
{
"path": ".claude/settings.20260421_085354.bak",
"chars": 5066,
"preview": "{\n \"permissions\": {\n \"allow\": [\n \"mcp__discord__*\",\n \"Bash(*)\"\n ]\n },\n \"hooks\": {\n \"SessionStart\":"
},
{
"path": ".claude/settings.json",
"chars": 5159,
"preview": "{\n \"permissions\": {\n \"allow\": [\n \"mcp__discord__*\",\n \"Bash(*)\"\n ]\n },\n \"hooks\": {\n \"SessionStart\":"
},
{
"path": ".claude/skills/adr/SKILL.md",
"chars": 30663,
"preview": "---\nname: adr\ndescription: 'Architecture Decision Record (ADR) management skill. Creates, maintains, and enforces archit"
},
{
"path": ".claude/skills/flash/SKILL.md",
"chars": 902,
"preview": "---\nname: flash\ndescription: Build firmware + filesystem and flash to ESP via USB. Auto-detects serial port. No user inp"
},
{
"path": ".claude/skills/release/SKILL.md",
"chars": 11236,
"preview": "---\nname: release\ndescription: Prepare and execute a full OTGW-firmware release following the documented release process"
},
{
"path": ".claude/skills/update-docs/SKILL.md",
"chars": 15060,
"preview": "---\nname: update-docs\ndescription: Update OTGW-firmware documentation in one sequential, backlog-tracked workflow (dev /"
},
{
"path": ".codex",
"chars": 0,
"preview": ""
},
{
"path": ".copilot-tracking/research/20260306-mqtt-json-refactor-research.md",
"chars": 10892,
"preview": "<!-- markdownlint-disable-file -->\n\n# Task Research Notes: MQTT command matching and JSON escape declaration cleanup\n\n##"
},
{
"path": ".copilot-tracking/research/20260306-ui-fixes-otmonitor-panel-spacing-research.md",
"chars": 10469,
"preview": "<!-- markdownlint-disable-file -->\n\n# Task Research Notes: OT monitor panel fill and command spacing\n\n## Research Execut"
},
{
"path": ".external-reviews/HANDOFF-claude-review-c-codebase-303Qj.md",
"chars": 10987,
"preview": "# Code Review Handoff — OTGW-firmware\n\n**Reviewer:** Claude (Opus 4.7, 1M context), `claude/review-c-codebase-303Qj`\n**D"
},
{
"path": ".external-reviews/README.md",
"chars": 1158,
"preview": "# External Reviews\n\nThis directory holds review outputs produced **outside** the current session's own review pipeline ("
},
{
"path": ".full-review/00-scope.md",
"chars": 3956,
"preview": "# Review Scope\n\n## Target\n\nFull branch review of `1.4.1` vs `dev` (base branch). This branch is the \"heap-pressure reduc"
},
{
"path": ".full-review/01-quality-architecture.md",
"chars": 11709,
"preview": "# Phase 1: Code Quality & Architecture Review\n\n**Target**: branch `1.4.1` vs `dev` (14 commits, ~20 source files)\n**Them"
},
{
"path": ".full-review/02-security-performance.md",
"chars": 12275,
"preview": "# Phase 2: Security & Performance Review\n\n**Target**: branch `1.4.1` vs `dev` (14 commits, ~20 source files)\n**Threat mo"
},
{
"path": ".full-review/03-testing-documentation.md",
"chars": 7809,
"preview": "# Phase 3: Testing & Documentation Review\n\n**Target**: branch `1.4.1` vs `dev`\n**Full reports**: `phase3a-testing.md` (2"
},
{
"path": ".full-review/05-final-report.md",
"chars": 12471,
"preview": "# Comprehensive Code Review — Final Report\n\n**Target**: Branch `1.4.1` vs `dev` (14 commits, ~20 source files, +2010 / −"
},
{
"path": ".full-review/phase1a-code-quality.md",
"chars": 39809,
"preview": "# Phase 1A: Code Quality Findings\n\nReview target: branch `1.4.1` vs `dev`, 14 commits, ~20 source files. Focus: heap-pre"
},
{
"path": ".full-review/phase1b-architecture.md",
"chars": 26348,
"preview": "# Phase 1B: Architecture Findings\n\n## Summary\n\n- Critical: 1 | High: 3 | Medium: 5 | Low: 4\n\nThe branch is, on the whole"
},
{
"path": ".full-review/phase2a-security.md",
"chars": 17530,
"preview": "# Phase 2A: Security Findings\n\n## Summary\n- Critical: 0 | High: 0 | Medium: 2 | Low: 3 | Informational: 2\n\n## Threat mod"
},
{
"path": ".full-review/phase2b-performance.md",
"chars": 27546,
"preview": "# Phase 2B: Performance & Scalability Findings\n\n**Target**: branch `1.4.1` vs `dev`, performance theme\n**Platform**: ESP"
},
{
"path": ".full-review/phase3a-testing.md",
"chars": 22534,
"preview": "# Phase 3A: Test Coverage & Quality\n\n## Summary\n\n- **Critical gaps**: 4 must-cover invariants with zero verification (bu"
},
{
"path": ".full-review/phase3b-documentation.md",
"chars": 22325,
"preview": "# Phase 3B: Documentation Findings\n\nBranch `1.4.1` vs `dev`, 14 commits. Phase 1B already covered the ADR governance fin"
},
{
"path": ".full-review/state.json",
"chars": 2608,
"preview": "{\n \"target\": \"Branch 1.4.1 vs dev (14 commits, ~20 source files) — heap-pressure reduction, MQTT discovery verification"
},
{
"path": ".full-review-archive-20260421-085044/00-scope.md",
"chars": 2278,
"preview": "# Review Scope\n\n## Target\n\nAll changes since the v1.3.2 release to current HEAD on the `dev` branch. This covers the v1."
},
{
"path": ".full-review-archive-20260421-085044/01-quality-architecture.md",
"chars": 5454,
"preview": "# Phase 1: Code Quality & Architecture Review\n\n## Code Quality Findings\n\n### Medium Severity\n\n**CQ-1: `sendMQTTStreaming"
},
{
"path": ".full-review-archive-20260421-085044/02-security-performance.md",
"chars": 5368,
"preview": "# Phase 2: Security & Performance Review\n\n## Security Findings\n\n### Medium Severity\n\n**SEC-1: MQTT pending slot overwrit"
},
{
"path": ".full-review-archive-20260421-085044/state.json",
"chars": 439,
"preview": "{\n \"target\": \"Changes since v1.3.2 release (28 source files, 309 insertions, 125 deletions)\",\n \"status\": \"in_progress\""
},
{
"path": ".gitattributes",
"chars": 270,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n*.bat text eol=crlf\n*.sh text eol=lf\n\n# CI-generated "
},
{
"path": ".githooks/pre-commit",
"chars": 3674,
"preview": "#!/usr/bin/env bash\n# adr-kit pre-commit hook (template; copied to project's .githooks/pre-commit by /adr-kit:install-ho"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md.example",
"chars": 3518,
"preview": "## Description\n\n<!-- Provide a clear and concise description of your changes -->\n\n\n\n## Type of Change\n\nPlease select the"
},
{
"path": ".github/actions/build/action.yml",
"chars": 1961,
"preview": "name: 'Arduino CLI Build'\ndescription: 'Build firmware using arduino-cli and makefile'\n\noutputs:\n semver:\n descripti"
},
{
"path": ".github/actions/setup/action.yml",
"chars": 776,
"preview": "name: 'CI Build Setup'\ndescription: 'Sets up build dependencies for Arduino CLI'\n\nruns:\n using: composite\n steps:\n "
},
{
"path": ".github/agents/adr-generator.agent.md",
"chars": 6662,
"preview": "---\nname: ADR Generator\ndescription: Expert agent for creating comprehensive Architectural Decision Records (ADRs) with "
},
{
"path": ".github/agents/api-architect.agent.md",
"chars": 2547,
"preview": "---\ndescription: 'Your role is that of an API architect. Help mentor the engineer by providing guidance, support, and wo"
},
{
"path": ".github/agents/context7.agent.md",
"chars": 26425,
"preview": "---\nname: Context7-Expert\ndescription: 'Expert in latest library versions, best practices, and correct syntax using up-t"
},
{
"path": ".github/agents/critical-thinking.agent.md",
"chars": 2153,
"preview": "---\ndescription: 'Challenge assumptions and encourage critical thinking to ensure the best possible solution and outcome"
},
{
"path": ".github/agents/debug.agent.md",
"chars": 3562,
"preview": "---\ndescription: 'Debug your application to find and fix a bug'\nname: 'Debug Mode Instructions'\ntools: ['edit/editFiles'"
},
{
"path": ".github/agents/devils-advocate.agent.md",
"chars": 2124,
"preview": "---\ndescription: \"I play the devil's advocate to challenge and stress-test your ideas by finding flaws, risks, and edge "
},
{
"path": ".github/agents/expert-cpp-software-engineer.agent.md",
"chars": 3109,
"preview": "---\ndescription: 'Provide expert C++ software engineering guidance using modern C++ and industry best practices.'\nname: "
},
{
"path": ".github/agents/expert-react-frontend-engineer.agent.md",
"chars": 24970,
"preview": "---\ndescription: \"Expert React 19.2 frontend engineer specializing in modern hooks, Server Components, Actions, TypeScri"
},
{
"path": ".github/agents/gpt-5-beast-mode.agent.md",
"chars": 6830,
"preview": "---\ndescription: 'Beast Mode 2.0: A powerful autonomous agent tuned specifically for GPT-5 that can solve complex proble"
},
{
"path": ".github/agents/implementation-plan.agent.md",
"chars": 7073,
"preview": "---\ndescription: \"Generate an implementation plan for new features or refactoring existing code.\"\nname: \"Implementation "
},
{
"path": ".github/agents/specification.agent.md",
"chars": 5990,
"preview": "---\ndescription: 'Generate or update specification documents for new or existing functionality.'\nname: 'Specification'\nt"
},
{
"path": ".github/agents/task-planner.agent.md",
"chars": 15474,
"preview": "---\ndescription: \"Task planner for creating actionable implementation plans - Brought to you by microsoft/edge-ai\"\nname:"
},
{
"path": ".github/agents/task-researcher.agent.md",
"chars": 12665,
"preview": "---\ndescription: \"Task research specialist for comprehensive project analysis - Brought to you by microsoft/edge-ai\"\nnam"
},
{
"path": ".github/copilot-instructions.md",
"chars": 40319,
"preview": "# GitHub Copilot Instructions for OTGW-firmware\n\n## Agent Workflow\n\n- This repository uses OpenWolf for context manageme"
},
{
"path": ".github/instructions/adr.code-review.instructions.md",
"chars": 4543,
"preview": "---\napplyTo: \"**\"\nexcludeAgent: \"coding-agent\"\n---\n\n# ADR Checks for Code Review\n\n## Review Checklist for Architectural "
},
{
"path": ".github/instructions/adr.coding-agent.instructions.md",
"chars": 1943,
"preview": "---\napplyTo: \"**\"\nexcludeAgent: \"code-review\"\n---\n\n# ADR Requirements for Coding Agent\n\n## Before Implementing Changes\n\n"
},
{
"path": ".github/prompts/check-discord-issues.prompt.md",
"chars": 4568,
"preview": "---\nname: check-discord-issues\ndescription: Scan Discord for new OTGW-firmware issues, triage them, and prepare an appro"
},
{
"path": ".github/skills/adr/ALWAYS_USE_SKILL.md",
"chars": 9999,
"preview": "# How to Ensure GitHub Copilot Always Uses the ADR Skill\n\nThis document provides step-by-step instructions for ensuring "
},
{
"path": ".github/skills/adr/IMPLEMENTATION_SUMMARY.md",
"chars": 12062,
"preview": "# ADR-Skill Implementation Summary\n\n## 📋 Overview\n\nThis document summarizes the complete ADR-skill implementation for th"
},
{
"path": ".github/skills/adr/QUICK_START.md",
"chars": 9587,
"preview": "# ADR-Skill Quick Start Guide\n\n## What Just Happened?\n\nA comprehensive **ADR (Architecture Decision Record) skill** has "
},
{
"path": ".github/skills/adr/README.md",
"chars": 4160,
"preview": "# ADR Skill for GitHub Copilot\n\nThis directory contains the Architecture Decision Record (ADR) management skill for GitH"
},
{
"path": ".github/skills/adr/SKILL.md",
"chars": 30663,
"preview": "---\nname: adr\ndescription: 'Architecture Decision Record (ADR) management skill. Creates, maintains, and enforces archit"
},
{
"path": ".github/skills/adr/USAGE_GUIDE.md",
"chars": 15108,
"preview": "# ADR Skill - Usage and Configuration Guide\n\n## Overview\n\nThis guide explains how to ensure GitHub Copilot always uses t"
},
{
"path": ".github/skills/algorithmic-art/LICENSE.txt",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/algorithmic-art/SKILL.md",
"chars": 19735,
"preview": "---\nname: algorithmic-art\ndescription: Creating algorithmic art using p5.js with seeded randomness and interactive param"
},
{
"path": ".github/skills/algorithmic-art/templates/generator_template.js",
"chars": 7376,
"preview": "/**\n * ═══════════════════════════════════════════════════════════════════════════\n * P5.JS GENERATIVE "
},
{
"path": ".github/skills/algorithmic-art/templates/viewer.html",
"chars": 19402,
"preview": "<!DOCTYPE html>\n<!--\n THIS IS A TEMPLATE THAT SHOULD BE USED EVERY TIME AND MODIFIED.\n WHAT TO KEEP:\n ✓ Overall"
},
{
"path": ".github/skills/brand-guidelines/LICENSE.txt",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/brand-guidelines/SKILL.md",
"chars": 2235,
"preview": "---\nname: brand-guidelines\ndescription: Applies Anthropic's official brand colors and typography to any sort of artifact"
},
{
"path": ".github/skills/canvas-design/LICENSE.txt",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/canvas-design/SKILL.md",
"chars": 11937,
"preview": "---\nname: canvas-design\ndescription: Create beautiful visual art in .png and .pdf documents using design philosophy. You"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt",
"chars": 4373,
"preview": "Copyright 2012 The Arsenal Project Authors (andrij.design@gmail.com)\n\nThis Font Software is licensed under the SIL Open "
},
{
"path": ".github/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt",
"chars": 4397,
"preview": "Copyright 2019 The Big Shoulders Project Authors (https://github.com/xotypeco/big_shoulders)\n\nThis Font Software is lice"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt",
"chars": 4390,
"preview": "Copyright 2024 The Boldonse Project Authors (https://github.com/googlefonts/boldonse)\n\nThis Font Software is licensed un"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt",
"chars": 4403,
"preview": "Copyright 2022 The Bricolage Grotesque Project Authors (https://github.com/ateliertriay/bricolage)\n\nThis Font Software i"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt",
"chars": 4394,
"preview": "Copyright 2018 The Crimson Pro Project Authors (https://github.com/Fonthausen/CrimsonPro)\n\nThis Font Software is license"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/DMMono-OFL.txt",
"chars": 4392,
"preview": "Copyright 2020 The DM Mono Project Authors (https://www.github.com/googlefonts/dm-mono)\n\nThis Font Software is licensed "
},
{
"path": ".github/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt",
"chars": 4410,
"preview": "Copyright (c) 2011 by LatinoType Limitada (luciano@latinotype.com), \nwith Reserved Font Names \"Erica One\"\n\nThis Font Sof"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt",
"chars": 4388,
"preview": "Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git)\n\nThis Font Software is licensed unde"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Gloock-OFL.txt",
"chars": 4381,
"preview": "Copyright 2022 The Gloock Project Authors (https://github.com/duartp/gloock)\n\nThis Font Software is licensed under the S"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt",
"chars": 4362,
"preview": "Copyright © 2017 IBM Corp. with Reserved Font Name \"Plex\"\n\nThis Font Software is licensed under the SIL Open Font Licens"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt",
"chars": 4403,
"preview": "Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans)\n\nThis Font Software i"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Italiana-OFL.txt",
"chars": 4394,
"preview": "Copyright (c) 2011, Santiago Orozco (hi@typemade.mx), with Reserved Font Name \"Italiana\".\n\nThis Font Software is license"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt",
"chars": 4399,
"preview": "Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)\n\nThis Font Software is li"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Jura-OFL.txt",
"chars": 4380,
"preview": "Copyright 2019 The Jura Project Authors (https://github.com/ossobuffo/jura)\n\nThis Font Software is licensed under the SI"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt",
"chars": 4449,
"preview": "Copyright 2012 The Libre Baskerville Project Authors (https://github.com/impallari/Libre-Baskerville) with Reserved Font"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Lora-OFL.txt",
"chars": 4423,
"preview": "Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name \"Lora\".\n\n"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt",
"chars": 4399,
"preview": "Copyright 2025 The National Park Project Authors (https://github.com/benhoepner/National-Park)\n\nThis Font Software is li"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt",
"chars": 4363,
"preview": "Copyright (c) 2010, Kimberly Geswein (kimberlygeswein.com)\n\nThis Font Software is licensed under the SIL Open Font Licen"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Outfit-OFL.txt",
"chars": 4389,
"preview": "Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts)\n\nThis Font Software is licensed und"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt",
"chars": 4395,
"preview": "Copyright 2021 The Pixelify Sans Project Authors (https://github.com/eifetx/Pixelify-Sans)\n\nThis Font Software is licens"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt",
"chars": 4366,
"preview": "Copyright (c) 2011, Denis Masharov (denis.masharov@gmail.com)\n\nThis Font Software is licensed under the SIL Open Font Li"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt",
"chars": 4394,
"preview": "Copyright 2024 The Red Hat Project Authors (https://github.com/RedHatOfficial/RedHatFont)\n\nThis Font Software is license"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt",
"chars": 4394,
"preview": "Copyright 2001 The Silkscreen Project Authors (https://github.com/googlefonts/silkscreen)\n\nThis Font Software is license"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt",
"chars": 4396,
"preview": "Copyright 2016 The Smooch Sans Project Authors (https://github.com/googlefonts/smooch-sans)\n\nThis Font Software is licen"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/Tektur-OFL.txt",
"chars": 4385,
"preview": "Copyright 2023 The Tektur Project Authors (https://www.github.com/hyvyys/Tektur)\n\nThis Font Software is licensed under t"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt",
"chars": 4397,
"preview": "Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans)\n\nThis Font Software is lice"
},
{
"path": ".github/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt",
"chars": 4398,
"preview": "Copyright 2023 The Young Serif Project Authors (https://github.com/noirblancrouge/YoungSerif)\n\nThis Font Software is lic"
},
{
"path": ".github/skills/doc-coauthoring/SKILL.md",
"chars": 15815,
"preview": "---\nname: doc-coauthoring\ndescription: Guide users through a structured workflow for co-authoring documentation. Use whe"
},
{
"path": ".github/skills/docx/LICENSE.txt",
"chars": 1466,
"preview": "© 2025 Anthropic, PBC. All rights reserved.\n\nLICENSE: Use of these materials (including all code, prompts, assets, files"
},
{
"path": ".github/skills/docx/SKILL.md",
"chars": 17113,
"preview": "---\nname: docx\ndescription: \"Use this skill whenever the user wants to create, read, edit, or manipulate Word documents "
},
{
"path": ".github/skills/docx/scripts/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": ".github/skills/docx/scripts/accept_changes.py",
"chars": 4051,
"preview": "\"\"\"Accept all tracked changes in a DOCX file using LibreOffice.\n\nRequires LibreOffice (soffice) to be installed.\n\"\"\"\n\nim"
},
{
"path": ".github/skills/docx/scripts/comment.py",
"chars": 10694,
"preview": "\"\"\"Add comments to DOCX documents.\n\nUsage:\n python comment.py unpacked/ 0 \"Comment text\"\n python comment.py unpack"
},
{
"path": ".github/skills/docx/scripts/office/helpers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": ".github/skills/docx/scripts/office/helpers/merge_runs.py",
"chars": 5567,
"preview": "\"\"\"Merge adjacent runs with identical formatting in DOCX.\n\nMerges adjacent <w:r> elements that have identical <w:rPr> pr"
},
{
"path": ".github/skills/docx/scripts/office/helpers/simplify_redlines.py",
"chars": 5754,
"preview": "\"\"\"Simplify tracked changes by merging adjacent w:ins or w:del elements.\n\nMerges adjacent <w:ins> elements from the same"
},
{
"path": ".github/skills/docx/scripts/office/pack.py",
"chars": 4991,
"preview": "\"\"\"Pack a directory into a DOCX, PPTX, or XLSX file.\n\nValidates with auto-repair, condenses XML formatting, and creates "
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd",
"chars": 74984,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:a=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd",
"chars": 6956,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:a=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd",
"chars": 51302,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd",
"chars": 624,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd",
"chars": 152039,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:r=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd",
"chars": 1231,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd",
"chars": 8862,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:a=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd",
"chars": 14795,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:a=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd",
"chars": 83612,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd",
"chars": 1269,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd",
"chars": 7328,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd",
"chars": 6382,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd",
"chars": 1248,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd",
"chars": 880,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
"chars": 2608,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
"chars": 3507,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd",
"chars": 7507,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd",
"chars": 23313,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd",
"chars": 1367,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd",
"chars": 242277,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://schemas."
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd",
"chars": 26148,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:schemas-micro"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd",
"chars": 25279,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"urn:schemas-mic"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd",
"chars": 535,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"urn:schemas-mic"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd",
"chars": 5712,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"urn:schemas-mic"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd",
"chars": 4010,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"urn:schemas-mic"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd",
"chars": 171367,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns:m=\"http://schema"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd",
"chars": 4646,
"preview": "<?xml version='1.0'?>\n<xs:schema targetNamespace=\"http://www.w3.org/XML/1998/namespace\" xmlns:xs=\"http://www.w3.org/2001"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd",
"chars": 1961,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<xs:schema xmlns=\"http://schemas.openxmlformats.org/package/2006"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd",
"chars": 2513,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xs:schema targetNamespace=\"http://schemas.openxmlformats.org/package/2006/metad"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd",
"chars": 2856,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsd:schema xmlns=\"http://schemas.openxmlformats.org/package/2006/digital-signatu"
},
{
"path": ".github/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd",
"chars": 1342,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<xsd:schema xmlns=\"http://schemas.openxmlformats.org/package/200"
},
{
"path": ".github/skills/docx/scripts/office/schemas/mce/mc.xsd",
"chars": 3125,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsd:schema xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd",
"chars": 26549,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd",
"chars": 3745,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd",
"chars": 901,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd",
"chars": 1778,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/20"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd",
"chars": 1002,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd",
"chars": 600,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd",
"chars": 745,
"preview": " <xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:w12=\"http://schemas.openxmlformats.org/wordprocessingml/"
},
{
"path": ".github/skills/docx/scripts/office/soffice.py",
"chars": 5293,
"preview": "\"\"\"\nHelper for running LibreOffice (soffice) in environments where AF_UNIX\nsockets may be blocked (e.g., sandboxed VMs)."
},
{
"path": ".github/skills/docx/scripts/office/unpack.py",
"chars": 4052,
"preview": "\"\"\"Unpack Office files (DOCX, PPTX, XLSX) for editing.\n\nExtracts the ZIP archive, pretty-prints XML files, and optionall"
},
{
"path": ".github/skills/docx/scripts/office/validate.py",
"chars": 3668,
"preview": "\"\"\"\nCommand line tool to validate Office document XML files against XSD schemas and tracked changes.\n\nUsage:\n python "
},
{
"path": ".github/skills/docx/scripts/office/validators/__init__.py",
"chars": 336,
"preview": "\"\"\"\nValidation modules for Word document processing.\n\"\"\"\n\nfrom .base import BaseSchemaValidator\nfrom .docx import DOCXSc"
},
{
"path": ".github/skills/docx/scripts/office/validators/base.py",
"chars": 32651,
"preview": "\"\"\"\nBase validator with common validation logic for document files.\n\"\"\"\n\nimport re\nfrom pathlib import Path\n\nimport defu"
},
{
"path": ".github/skills/docx/scripts/office/validators/docx.py",
"chars": 16372,
"preview": "\"\"\"\nValidator for Word document XML files against XSD schemas.\n\"\"\"\n\nimport random\nimport re\nimport tempfile\nimport zipfi"
},
{
"path": ".github/skills/docx/scripts/office/validators/pptx.py",
"chars": 9824,
"preview": "\"\"\"\nValidator for PowerPoint presentation XML files against XSD schemas.\n\"\"\"\n\nimport re\n\nfrom .base import BaseSchemaVal"
},
{
"path": ".github/skills/docx/scripts/office/validators/redlining.py",
"chars": 8918,
"preview": "\"\"\"\nValidator for tracked changes in Word documents.\n\"\"\"\n\nimport subprocess\nimport tempfile\nimport zipfile\nfrom pathlib "
},
{
"path": ".github/skills/docx/scripts/templates/comments.xml",
"chars": 2603,
"preview": "<?xml version=\"1.0\" ?>\n<w:comments xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas\" xmlns:"
},
{
"path": ".github/skills/docx/scripts/templates/commentsExtended.xml",
"chars": 2611,
"preview": "<?xml version=\"1.0\" ?>\n<w15:commentsEx xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas\" xm"
},
{
"path": ".github/skills/docx/scripts/templates/commentsExtensible.xml",
"chars": 2707,
"preview": "<?xml version=\"1.0\" ?>\n<w16cex:commentsExtensible xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessin"
},
{
"path": ".github/skills/docx/scripts/templates/commentsIds.xml",
"chars": 2619,
"preview": "<?xml version=\"1.0\" ?>\n<w16cid:commentsIds xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
},
{
"path": ".github/skills/docx/scripts/templates/people.xml",
"chars": 115,
"preview": "<?xml version=\"1.0\" ?>\n<w15:people xmlns:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\">\n</w15:people>\n"
},
{
"path": ".github/skills/frontend-design/LICENSE.txt",
"chars": 10174,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/frontend-design/SKILL.md",
"chars": 4440,
"preview": "---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality"
},
{
"path": ".github/skills/internal-comms/LICENSE.txt",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/internal-comms/SKILL.md",
"chars": 1511,
"preview": "---\nname: internal-comms\ndescription: A set of resources to help me write all kinds of internal communications, using th"
},
{
"path": ".github/skills/internal-comms/examples/3p-updates.md",
"chars": 3274,
"preview": "## Instructions\nYou are being asked to write a 3P update. 3P updates stand for \"Progress, Plans, Problems.\" The main aud"
},
{
"path": ".github/skills/internal-comms/examples/company-newsletter.md",
"chars": 3295,
"preview": "## Instructions\nYou are being asked to write a company-wide newsletter update. You are meant to summarize the past week/"
},
{
"path": ".github/skills/internal-comms/examples/faq-answers.md",
"chars": 2366,
"preview": "## Instructions\nYou are an assistant for answering questions that are being asked across the company. Every week, there "
},
{
"path": ".github/skills/internal-comms/examples/general-comms.md",
"chars": 602,
"preview": " ## Instructions\n You are being asked to write internal company communication that doesn't fit into the standard forma"
},
{
"path": ".github/skills/mcp-builder/LICENSE.txt",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": ".github/skills/mcp-builder/SKILL.md",
"chars": 9059,
"preview": "---\nname: mcp-builder\ndescription: Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs"
},
{
"path": ".github/skills/mcp-builder/reference/evaluation.md",
"chars": 21659,
"preview": "# MCP Server Evaluation Guide\n\n## Overview\n\nThis document provides guidance on creating comprehensive evaluations for MC"
},
{
"path": ".github/skills/mcp-builder/reference/mcp_best_practices.md",
"chars": 7330,
"preview": "# MCP Server Best Practices\n\n## Quick Reference\n\n### Server Naming\n- **Python**: `{service}_mcp` (e.g., `slack_mcp`)\n- *"
},
{
"path": ".github/skills/mcp-builder/reference/node_mcp_server.md",
"chars": 28472,
"preview": "# Node/TypeScript MCP Server Implementation Guide\n\n## Overview\n\nThis document provides Node/TypeScript-specific best pra"
},
{
"path": ".github/skills/mcp-builder/reference/python_mcp_server.md",
"chars": 25099,
"preview": "# Python MCP Server Implementation Guide\n\n## Overview\n\nThis document provides Python-specific best practices and example"
},
{
"path": ".github/skills/mcp-builder/scripts/connections.py",
"chars": 4875,
"preview": "\"\"\"Lightweight connection handling for MCP servers.\"\"\"\n\nfrom abc import ABC, abstractmethod\nfrom contextlib import Async"
},
{
"path": ".github/skills/mcp-builder/scripts/evaluation.py",
"chars": 12559,
"preview": "\"\"\"MCP Server Evaluation Harness\n\nThis script evaluates MCP servers by running test questions against them using Claude."
},
{
"path": ".github/skills/mcp-builder/scripts/example_evaluation.xml",
"chars": 1192,
"preview": "<evaluation>\n <qa_pair>\n <question>Calculate the compound interest on $10,000 invested at 5% annual interest rate"
},
{
"path": ".github/skills/mcp-builder/scripts/requirements.txt",
"chars": 29,
"preview": "anthropic>=0.39.0\nmcp>=1.1.0\n"
},
{
"path": ".github/skills/pdf/LICENSE.txt",
"chars": 1466,
"preview": "© 2025 Anthropic, PBC. All rights reserved.\n\nLICENSE: Use of these materials (including all code, prompts, assets, files"
},
{
"path": ".github/skills/pdf/SKILL.md",
"chars": 8035,
"preview": "---\nname: pdf\ndescription: Use this skill whenever the user wants to do anything with PDF files. This includes reading o"
},
{
"path": ".github/skills/pdf/forms.md",
"chars": 11854,
"preview": "**CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**\n\nIf you need to fill out a PDF "
},
{
"path": ".github/skills/pdf/reference.md",
"chars": 16692,
"preview": "# PDF Processing Advanced Reference\n\nThis document contains advanced PDF processing features, detailed examples, and add"
},
{
"path": ".github/skills/pdf/scripts/check_bounding_boxes.py",
"chars": 2774,
"preview": "from dataclasses import dataclass\nimport json\nimport sys\n\n\n\n\n@dataclass\nclass RectAndField:\n rect: list[float]\n re"
},
{
"path": ".github/skills/pdf/scripts/check_fillable_fields.py",
"chars": 268,
"preview": "import sys\nfrom pypdf import PdfReader\n\n\n\n\nreader = PdfReader(sys.argv[1])\nif (reader.get_fields()):\n print(\"This PDF"
},
{
"path": ".github/skills/pdf/scripts/convert_pdf_to_images.py",
"chars": 1008,
"preview": "import os\nimport sys\n\nfrom pdf2image import convert_from_path\n\n\n\n\ndef convert(pdf_path, output_dir, max_dim=1000):\n i"
},
{
"path": ".github/skills/pdf/scripts/create_validation_image.py",
"chars": 1258,
"preview": "import json\nimport sys\n\nfrom PIL import Image, ImageDraw\n\n\n\n\ndef create_validation_image(page_number, fields_json_path, "
},
{
"path": ".github/skills/pdf/scripts/extract_form_field_info.py",
"chars": 4300,
"preview": "import json\nimport sys\n\nfrom pypdf import PdfReader\n\n\n\n\ndef get_full_annotation_field_id(annotation):\n components = ["
},
{
"path": ".github/skills/pdf/scripts/extract_form_structure.py",
"chars": 3945,
"preview": "\"\"\"\nExtract form structure from a non-fillable PDF.\n\nThis script analyzes the PDF to find:\n- Text labels with their exac"
},
{
"path": ".github/skills/pdf/scripts/fill_fillable_fields.py",
"chars": 3819,
"preview": "import json\nimport sys\n\nfrom pypdf import PdfReader, PdfWriter\n\nfrom extract_form_field_info import get_field_info\n\n\n\n\nd"
},
{
"path": ".github/skills/pdf/scripts/fill_pdf_form_with_annotations.py",
"chars": 3235,
"preview": "import json\nimport sys\n\nfrom pypdf import PdfReader, PdfWriter\nfrom pypdf.annotations import FreeText\n\n\n\n\ndef transform_"
},
{
"path": ".github/skills/pptx/LICENSE.txt",
"chars": 1466,
"preview": "© 2025 Anthropic, PBC. All rights reserved.\n\nLICENSE: Use of these materials (including all code, prompts, assets, files"
},
{
"path": ".github/skills/pptx/SKILL.md",
"chars": 9128,
"preview": "---\nname: pptx\ndescription: \"Use this skill any time a .pptx file is involved in any way — as input, output, or both. Th"
},
{
"path": ".github/skills/pptx/editing.md",
"chars": 6841,
"preview": "# Editing Presentations\n\n## Template-Based Workflow\n\nWhen using an existing presentation as a template:\n\n1. **Analyze ex"
},
{
"path": ".github/skills/pptx/pptxgenjs.md",
"chars": 12774,
"preview": "# PptxGenJS Tutorial\n\n## Setup & Basic Structure\n\n```javascript\nconst pptxgen = require(\"pptxgenjs\");\n\nlet pres = new pp"
},
{
"path": ".github/skills/pptx/scripts/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": ".github/skills/pptx/scripts/add_slide.py",
"chars": 6872,
"preview": "\"\"\"Add a new slide to an unpacked PPTX directory.\n\nUsage: python add_slide.py <unpacked_dir> <source>\n\nThe source can be"
},
{
"path": ".github/skills/pptx/scripts/clean.py",
"chars": 9583,
"preview": "\"\"\"Remove unreferenced files from an unpacked PPTX directory.\n\nUsage: python clean.py <unpacked_dir>\n\nExample:\n pytho"
},
{
"path": ".github/skills/pptx/scripts/office/helpers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": ".github/skills/pptx/scripts/office/helpers/merge_runs.py",
"chars": 5567,
"preview": "\"\"\"Merge adjacent runs with identical formatting in DOCX.\n\nMerges adjacent <w:r> elements that have identical <w:rPr> pr"
}
]
// ... and 644 more files (download for full content)
About this extraction
This page contains the full source code of the rvdbreemen/OTGW-firmware GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 844 files (13.8 MB), approximately 3.7M tokens, and a symbol index with 845 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.