From 0ef39c65505b1ecea1d66cca5c47e24b73b8233b Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Fri, 29 Nov 2024 21:10:29 +0100 Subject: [PATCH 01/15] devtools: Add shellcheck to pre-commit. --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe1a13e4c102..7c8e13d8b117 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,3 +11,9 @@ repos: - id: ruff-format args: [ --diff ] exclude: "contrib/pyln-grpc-proto/pyln/grpc/(primitives|node)_pb2(|_grpc).py" + +- repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.11.0.1 + hooks: + - id: shellcheck + args: [ -fgcc ] From bb8aae88918ae9b971a10b1a6c05b6be473f80dd Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Sat, 30 Nov 2024 16:28:18 +0100 Subject: [PATCH 02/15] devtools: Add amount access check to pre-commit. Reimplements `make check-amount-access` for Python regex. --- .pre-commit-config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c8e13d8b117..b9a48260181b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,3 +17,14 @@ repos: hooks: - id: shellcheck args: [ -fgcc ] + +- repo: local + hooks: + # Reimplementation of `make check-amount-access` for pygrep. + - id: check-amount-access + name: Check amount_msat and amount_sat members are not accessed directly + description: "Don't access amount_msat and amount_sat members directly without a good reason since it risks overflow." + language: pygrep + entry: (->|\.)(milli)?satoshis(?!.*\/\*\ Raw:)|(? Date: Sun, 1 Dec 2024 13:52:59 +0100 Subject: [PATCH 03/15] devtools: Add discouraged function check to pre-commit. Reimplements `make check-discouraged-functions` for Python regex. --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9a48260181b..0f8dc272e47b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,3 +28,11 @@ repos: entry: (->|\.)(milli)?satoshis(?!.*\/\*\ Raw:)|(? Date: Mon, 16 Dec 2024 19:13:38 +0100 Subject: [PATCH 04/15] devtools: Add codespell to pre-commit. Includes default config file and an initial word list to ignore. --- .codespellignore | 3 +++ .codespellrc | 4 ++++ .pre-commit-config.yaml | 8 ++++++++ 3 files changed, 15 insertions(+) create mode 100644 .codespellignore create mode 100644 .codespellrc diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 000000000000..972d0b2596ab --- /dev/null +++ b/.codespellignore @@ -0,0 +1,3 @@ +connectd +crate +mut diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 000000000000..092dcccfc418 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,4 @@ +[codespell] + +count = true +ignore-words = .codespellignore diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f8dc272e47b..7a99d261035a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,14 @@ repos: - id: shellcheck args: [ -fgcc ] +- repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + description: Checks for common misspellings. + exclude: ccan|contrib|tests/fuzz/corpora + stages: [ manual ] + - repo: local hooks: # Reimplementation of `make check-amount-access` for pygrep. From 9c2efdd2ce5307cd03613379b7f0e748fa7cc301 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Tue, 17 Dec 2024 11:25:28 +0100 Subject: [PATCH 05/15] devtools: Add doc JSON schema checks and formatting to pre-commit. --- .pre-commit-config.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a99d261035a..31d99aee9b4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,29 @@ repos: - id: shellcheck args: [ -fgcc ] +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.30.0 + hooks: + - id: check-jsonschema + name: check doc JSON schemas + args: ["--schemafile", "doc/rpc-schema-draft.json"] + files: ^doc/schemas/.*\.json$ + types: [ json ] + + - id: check-metaschema + name: check doc JSON metaschemas + args: ["--verbose"] + files: ^doc/schemas/.*\.json$ + types: [ json ] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: pretty-format-json + args: [ "--indent", "2", "--no-sort-keys" ] + files: ^doc/schemas/.*\.json$ + types: [ json ] + - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: From 498b2923a91530237f911e6408837dc8d140b840 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Tue, 9 Dec 2025 13:21:04 +0100 Subject: [PATCH 06/15] devtools: Add EOF fixer and trailing whitespace fix to pre-commit. --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31d99aee9b4c..16b52dd31822 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,11 @@ repos: args: [ "--indent", "2", "--no-sort-keys" ] files: ^doc/schemas/.*\.json$ types: [ json ] + - id: trailing-whitespace + args: [ "--markdown-linebreak-ext=md" ] + exclude: ccan|contrib|tests/fuzz/corpora + - id: end-of-file-fixer + exclude: ccan|contrib|tests/fuzz/corpora - repo: https://github.com/codespell-project/codespell rev: v2.3.0 From baef60af36a4caa0cc85deb8f6993e24e85ce5b3 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Wed, 10 Dec 2025 13:01:59 +0100 Subject: [PATCH 07/15] devtools: Add `fix-style-errors` script by @sangbida. A helper tool to fix code style errors using `ruff` and `clang-format` and correct spelling. --- devtools/fix-style-errors | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 devtools/fix-style-errors diff --git a/devtools/fix-style-errors b/devtools/fix-style-errors new file mode 100755 index 000000000000..628e6812020d --- /dev/null +++ b/devtools/fix-style-errors @@ -0,0 +1,18 @@ +#!/bin/bash + +# Takes a list of files and applies style fixes for Python using `ruff` and C using +# `clang-format`. Also corrects spelling using `codespell`. This tool is an auxiliary to the +# `pre-commit` hooks found in: +# `.pre-commit-config.yaml` +# +# WARNING: Changes are destructive. Ensure a clean working environment before running. +# +# By: @sangbida + +for file in "$@"; do + case "$file" in + *.py) ruff check --fix "$file"; ruff format "$file" ;; + *.c|*.h) clang-format -i "$file" 2>/dev/null ;; + esac + codespell -w "$file" 2>/dev/null +done From a048dcb408df79cc91b35d295c198fefeb9f507c Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Wed, 10 Dec 2025 13:33:00 +0100 Subject: [PATCH 08/15] devtools: Add pre-commit as a `dev` Python package. --- pyproject.toml | 1 + uv.lock | 230 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9838713c7c9f..b86ffef59cdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ "tqdm", "pytest-benchmark", "pdoc3>=0.11.6", + "pre-commit>=4.3.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index cb3018fe44bc..9373dedf7a57 100644 --- a/uv.lock +++ b/uv.lock @@ -279,6 +279,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -458,6 +482,8 @@ dev = [ { name = "flake8" }, { name = "flask-socketio" }, { name = "pdoc3" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-benchmark" }, @@ -493,6 +519,7 @@ dev = [ { name = "flake8", specifier = ">=7.0" }, { name = "flask-socketio", specifier = ">=5" }, { name = "pdoc3", specifier = ">=0.11.6" }, + { name = "pre-commit", specifier = ">=4.3.0" }, { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-benchmark" }, { name = "pytest-custom-exit-code", specifier = "==0.3.0" }, @@ -714,6 +741,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "ephemeral-port-reserve" version = "1.1.4" @@ -744,6 +780,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + [[package]] name = "flake8" version = "7.3.0" @@ -943,6 +1003,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -1217,6 +1286,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/32/709df4390d155bd72e75f2a9bce6004c2958a21f31817f3a2c7750299f70/mypy_protobuf-3.7.0-py3-none-any.whl", hash = "sha256:85256e9d4da935722ce8fbaa8d19397e1a2989aa8075c96577987de9fe7cea4d", size = 17488, upload-time = "2025-11-17T22:11:11.62Z" }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1240,6 +1318,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/98/629f269c2bd91bdcac147aad5cf51ceb645c0196e23a41ee3c051125190f/pdoc3-0.11.6-py3-none-any.whl", hash = "sha256:8b72723767bd48d899812d2aec8375fc1c3476e179455db0b4575e6dccb44b93", size = 255188, upload-time = "2025-03-20T22:53:51.671Z" }, ] +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1249,6 +1351,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "identify", marker = "python_full_version < '3.10'" }, + { name = "nodeenv", marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "virtualenv", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "identify", marker = "python_full_version >= '3.10'" }, + { name = "nodeenv", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "virtualenv", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, +] + [[package]] name = "protobuf" version = "6.32.1" @@ -1808,6 +1948,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl", hash = "sha256:e93363102f4da6d8e7a8872bf4908b866c40f070e716aa27132891e643e2687c", size = 79451, upload-time = "2025-11-22T18:50:19.416Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + [[package]] name = "referencing" version = "0.36.2" @@ -2257,6 +2470,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/1a/9ffe814d317c5224166b23e7c47f606d6e473712a2fad0f704ea9b99f246/urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f", size = 131083, upload-time = "2025-12-05T15:08:45.983Z" }, ] +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, +] + [[package]] name = "websocket-client" version = "1.9.0" From d070f9d30ef85c25f5cc7e3421ddacf73f3a7228 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Wed, 10 Dec 2025 13:34:26 +0100 Subject: [PATCH 09/15] doc: Add pre-commit section to Contributor Workflow documentation. --- .../contributor-workflow.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/contribute-to-core-lightning/contributor-workflow.md b/doc/contribute-to-core-lightning/contributor-workflow.md index 45e50212cb09..d429d83f70ea 100644 --- a/doc/contribute-to-core-lightning/contributor-workflow.md +++ b/doc/contribute-to-core-lightning/contributor-workflow.md @@ -61,6 +61,19 @@ uv build contrib/pyln-client/ uv build contrib/pyln-proto/ ``` +## Local checks with pre-commit + +You can avoid common mistakes, speed up your development workflow and avoid wasteful CI runs by opting in to the local code checks managed by [pre-commit](https://pre-commit.com). The `pre-commit` Python package is part of the `dev` group and should be installed along with the other packages using `pip` or `uv` when [installing from source](https://docs.corelightning.org/docs/installation#installing-from-source). Activate it on your local development environment from the root of your Core Lightning working directory with: +```shell +pre-commit install +``` + +You can disable and remove it with: +```shell +pre-commit uninstall +pre-commit clean +``` + ## Making BOLT Modifications All of code for marshalling/unmarshalling BOLT protocol messages is generated directly from the spec. These are pegged to the BOLTVERSION, as specified in `Makefile`. From ae72a8127682044cda6951f6091f29ee010add57 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Fri, 12 Dec 2025 11:27:12 +0100 Subject: [PATCH 10/15] devtools: Add clang-format to pre-commit. --- .pre-commit-config.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16b52dd31822..9c69e945bf3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,19 @@ repos: - id: shellcheck args: [ -fgcc ] +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v19.1.4 + hooks: + - id: clang-format + description: Runs formatting checks on the c code and and throws errors if suggestions + are detected, without modifying the code. Style is defined in `.clang-format`. When + encountering formatting-related errors, run `clang-format -i ` to make + (destructively) the suggestions and evalute the resulting diff for more context. + args: [ --dry-run, -Werror ] + entry: clang-format + types: [ c ] + stages: [ manual ] + - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.30.0 hooks: From 0e37f0c2780b5fb7b8b042995b50219e9e05482b Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Mon, 15 Dec 2025 12:44:14 +0100 Subject: [PATCH 11/15] build: Replace PLUGIN_RENEPAY_HDRS json.c with json.h in Makefile. --- plugins/renepay/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index d28ca33018af..4d931eca80c2 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -34,7 +34,7 @@ PLUGIN_RENEPAY_HDRS := \ plugins/renepay/uncertainty.h \ plugins/renepay/mods.h \ plugins/renepay/errorcodes.h \ - plugins/renepay/json.c + plugins/renepay/json.h PLUGIN_RENEPAY_OBJS := $(PLUGIN_RENEPAY_SRC:.c=.o) From 460c4481dc6cda335c511b15795d8047842de5a4 Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Sat, 13 Dec 2025 15:00:39 +0100 Subject: [PATCH 12/15] devtools: Add custom `include-order-fixer` to pre-commit. Also fixes some exising file spacing issues. Preserves whitespace and comments. Assisted by Cursor Auto. --- .pre-commit-config.yaml | 17 ++ Makefile | 5 + common/htlc_state.c | 1 - devtools/include-order-fixer.py | 394 ++++++++++++++++++++++++++++++++ 4 files changed, 416 insertions(+), 1 deletion(-) create mode 100755 devtools/include-order-fixer.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c69e945bf3f..497750a0cb49 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -85,3 +85,20 @@ repos: entry: '[^a-z_/](?:fgets|fputs|gets|scanf|sprintf)\(' types: [ c ] exclude: ccan|contrib + + - id: include-order-fixer + name: Fix Include Order + language: python + description: Analyzes Core Lightning C source and header files, assesses the order of + their include directives according to the published Coding Style Guidelines + [here](https://docs.corelightning.org/docs/coding-style-guidelines), automatically + applying sorting them. Aims to conform with `make check-includes`. + entry: python devtools/include-order-fixer.py + stages: [ manual ] + pass_filenames: false + + - id: check-includes + name: Check Includes + language: system + entry: make check-includes + pass_filenames: false diff --git a/Makefile b/Makefile index a5ab6dc8ecfc..e2baaa58f3b8 100644 --- a/Makefile +++ b/Makefile @@ -546,6 +546,11 @@ SRC_TO_CHECK := $(filter-out $(ALL_TEST_PROGRAMS:=.c), $(ALL_NONGEN_SOURCES)) check-src-includes: $(SRC_TO_CHECK:%=check-src-include-order/%) check-hdr-includes: $(ALL_NONGEN_HEADERS:%=check-hdr-include-order/%) +print-src-to-check: + @echo $(SRC_TO_CHECK) +print-hdr-to-check: + @echo $(ALL_NONGEN_HEADERS) + # If you want to check a specific variant of quotes use: # make check-source-bolt BOLTVERSION=xxx ifeq ($(BOLTVERSION),$(DEFAULT_BOLTVERSION)) diff --git a/common/htlc_state.c b/common/htlc_state.c index 75b841d3837e..52d0edc004d8 100644 --- a/common/htlc_state.c +++ b/common/htlc_state.c @@ -138,4 +138,3 @@ int htlc_state_flags(enum htlc_state state) assert(per_state_bits[state]); return per_state_bits[state]; } - diff --git a/devtools/include-order-fixer.py b/devtools/include-order-fixer.py new file mode 100755 index 000000000000..bee5541fb482 --- /dev/null +++ b/devtools/include-order-fixer.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +""" +Fix include directive ordering in C source and header files. + +This script analyzes Core Lightning C source and header files, ensuring +include directives are sorted according to the Coding Style Guidelines. + +Includes ending in "_gen.h" or with any leading whitespace are preserved +in their original positions. Comments and blank lines are also preserved. +Includes found more than once are de-duplicated. +""" + +import locale +import os +import re +import subprocess +import sys +import tempfile + +# Set C locale for sorting to match Makefile behavior +locale.setlocale(locale.LC_ALL, "C") + + +def parse_makefile_output(output): + """Parse Makefile output, handling the 'Building version' line.""" + lines = output.splitlines() + # Skip "Building version" line if present + if lines and lines[0].startswith("Building version"): + if len(lines) > 1: + file_list = lines[1] + else: + file_list = "" + else: + file_list = lines[0] if lines else "" + + # Split by spaces and filter out empty strings + files = [f for f in file_list.split() if f] + return files + + +def get_files_to_check(): + """Get lists of C source and header files from Makefile targets.""" + # Get C source files + result = subprocess.run( + ["make", "print-src-to-check"], + capture_output=True, + text=True, + cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + ) + if result.returncode != 0: + print( + f"Error running 'make print-src-to-check': {result.stderr}", file=sys.stderr + ) + sys.exit(1) + + src_files = parse_makefile_output(result.stdout) + + # Get header files + result = subprocess.run( + ["make", "print-hdr-to-check"], + capture_output=True, + text=True, + cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + ) + if result.returncode != 0: + print( + f"Error running 'make print-hdr-to-check': {result.stderr}", file=sys.stderr + ) + sys.exit(1) + + hdr_files = parse_makefile_output(result.stdout) + + # Return files with their types + files_with_types = [] + for f in src_files: + files_with_types.append((f, "c")) + for f in hdr_files: + files_with_types.append((f, "h")) + + return files_with_types + + +def extract_includes(content): + """ + Extract include directives from file content, preserving comments and whitespace. + + Returns: + tuple: (main_items, trailing_items, include_start_line, blank_line_index, include_end_line) + main_items: List of (type, line) tuples in main block where type is 'include', 'comment', or 'blank' + trailing_items: List of (type, line) tuples after blank line (to be preserved) + include_start_line: Line number where includes start (0-indexed) + blank_line_index: Line number of blank line separator (None if no blank line) + include_end_line: Line number after last include (0-indexed) + """ + lines = content.splitlines(keepends=True) + main_items = [] + trailing_items = [] + include_start = None + include_end = None + blank_line_index = None + in_trailing_block = False + + # Pattern to match include directives (with optional leading whitespace) + include_pattern = re.compile(r'^\s*#include\s+[<"].*[>"]\s*$') + # Pattern to match comments (single-line or start of multi-line) + comment_pattern = re.compile(r'^\s*/\*|^\s*//') + # Pattern to match continuation lines of multi-line comments + comment_continuation_pattern = re.compile(r'^\s*\*|.*\*/') + + in_multiline_comment = False + + for i, line in enumerate(lines): + # Check if this line is an include + if include_pattern.match(line): + in_multiline_comment = False + if include_start is None: + include_start = i + # Preserve the line as-is (including leading whitespace) + if in_trailing_block: + trailing_items.append(('include', line)) + else: + main_items.append(('include', line)) + include_end = i + 1 + elif include_start is not None: + # We've seen includes, but this line is not an include + if line.strip(): + # Check if it's a comment start or continuation + if comment_pattern.match(line): + # Start of a comment + in_multiline_comment = True + # Check if it's a single-line comment (ends with */) + if '*/' in line: + in_multiline_comment = False + # Preserve comments + if in_trailing_block: + trailing_items.append(('comment', line)) + else: + main_items.append(('comment', line)) + include_end = i + 1 + elif in_multiline_comment and comment_continuation_pattern.match(line): + # Continuation of multi-line comment + if in_trailing_block: + trailing_items.append(('comment', line)) + else: + main_items.append(('comment', line)) + include_end = i + 1 + # Check if this line ends the comment + if '*/' in line: + in_multiline_comment = False + else: + # Non-blank, non-include, non-comment line - stop here + in_multiline_comment = False + break + else: + # Blank line + # Only treat as separator if we haven't seen one yet + # and we'll continue to look for trailing includes + if blank_line_index is None: + blank_line_index = i + in_trailing_block = True + # Add this separator blank line to trailing_items + trailing_items.append(('blank', line)) + include_end = i + 1 + elif in_trailing_block: + # We're in trailing block, preserve blank lines here + trailing_items.append(('blank', line)) + include_end = i + 1 + else: + # Blank line in main block (before separator) - preserve it + main_items.append(('blank', line)) + include_end = i + 1 + # If we haven't started collecting includes yet, continue + + if include_start is None: + # No includes found + return [], [], None, None, None + + # If we marked a blank line as separator but found no trailing includes, + # those blank lines should not be treated as trailing - they're just + # normal blank lines after the includes that should remain in after_lines + if blank_line_index is not None: + # Check if we actually have trailing includes (not just blank lines/comments) + has_trailing_includes = any(item_type == 'include' for item_type, _ in trailing_items) + if not has_trailing_includes: + # No trailing includes found, so blank lines aren't a separator + # Reset to treat them as normal file content + blank_line_index = None + trailing_items = [] + # Recalculate include_end to point to the last include/comment in main_items + # Count how many lines we've processed in main_items + include_end = include_start + len(main_items) + + return ( + main_items, + trailing_items, + include_start, + blank_line_index, + include_end, + ) + + +def sort_includes(items, file_type): + """ + Sort includes according to Core Lightning rules. + + For .c files: all includes in alphabetical order + For .h files: config.h first (if present), then others alphabetically + + Includes ending in "_gen.h" or with any leading whitespace are preserved + in their original positions. Comments and blank lines are also preserved. + """ + if not items: + return items + + # Track includes that should be preserved at their positions + preserved_positions = {} # position -> (type, line) + regular_includes = [] # list of (position, include_line) tuples to sort + + for pos, (item_type, line) in enumerate(items): + if item_type != 'include': + # Preserve comments and blank lines at their positions + preserved_positions[pos] = (item_type, line) + else: + # Check if this include should be preserved + # (has any leading whitespace, or ends in "_gen.h") + stripped = line.lstrip() + has_leading_whitespace = line != stripped + is_gen_h = '_gen.h"' in line or "_gen.h>" in line + + if has_leading_whitespace or is_gen_h: + # Preserve at original position + preserved_positions[pos] = (item_type, line) + else: + # Regular include to be sorted + regular_includes.append((pos, line)) + + # Separate config.h from other regular includes for header files + config_h_pos = None + config_h_include = None + other_regular = [] + + for pos, inc in regular_includes: + if file_type == "h" and '"config.h"' in inc: + config_h_pos = pos + config_h_include = inc + else: + other_regular.append((pos, inc)) + + # Sort other regular includes using C locale (by the include content, not position) + other_regular_sorted = sorted(other_regular, key=lambda x: locale.strxfrm(x[1])) + + # Build sorted list of regular includes + sorted_regular = [] + if config_h_include: + sorted_regular.append((config_h_pos, config_h_include)) + sorted_regular.extend(other_regular_sorted) + + # Build result: preserved items at original positions, sorted regular includes elsewhere + result = [] + regular_idx = 0 + + for pos in range(len(items)): + if pos in preserved_positions: + # Use preserved item at its original position + result.append(preserved_positions[pos]) + else: + # Use next sorted regular include + if regular_idx < len(sorted_regular): + _, sorted_inc = sorted_regular[regular_idx] + result.append(('include', sorted_inc)) + regular_idx += 1 + + return result + + +def dedupe_include_items(items, seen): + """Remove duplicate include lines, keeping the first occurrence. + + Duplicate detection uses a canonical form of include lines (`lstrip()`), + so leading whitespace differences do not prevent matching. + Non-include items (comments/blanks) are always preserved. + """ + deduped = [] + for item_type, line in items: + if item_type != "include": + deduped.append((item_type, line)) + continue + + key = line.lstrip() + if key in seen: + continue + seen.add(key) + deduped.append((item_type, line)) + return deduped + + +def fix_file_includes(filepath, file_type): + """ + Fix include ordering in a file. + + Returns: + bool: True if file was modified, False otherwise + """ + try: + with open(filepath, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + except IOError as e: + print(f"Error reading {filepath}: {e}", file=sys.stderr) + return False + + # Extract includes + main_items, trailing_items, include_start, blank_line_index, include_end = extract_includes( + content + ) + + if include_start is None: + # No includes to sort + return False + + # Sort only the main includes block (preserving comments, blanks, and whitespace-prefixed includes) + sorted_main_items = sort_includes(main_items, file_type) + + # De-duplicate includes across main and trailing blocks, preserving the first occurrence + seen_includes = set() + sorted_main_items = dedupe_include_items(sorted_main_items, seen_includes) + trailing_items = dedupe_include_items(trailing_items, seen_includes) + + # Reconstruct file content + lines = content.splitlines(keepends=True) + before_lines = lines[:include_start] if include_start > 0 else [] + after_lines = lines[include_end:] if include_end < len(lines) else [] + + # Build the include section: main sorted items + trailing items + # Note: blank lines are already included in main_items/trailing_items, and + # blank_line_index is just a marker, so we don't need to add it separately + include_section = "".join(line for _, line in sorted_main_items) + if trailing_items: + # Add trailing items (blank line separator is already in trailing_items if present) + include_section += "".join(line for _, line in trailing_items) + + # Combine: before + include section + after + new_content = "".join(before_lines) + include_section + "".join(after_lines) + + # Check if content actually changed + if new_content == content: + return False + + # Write back atomically using temp file + try: + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + dir=os.path.dirname(filepath), + delete=False, + suffix=".tmp", + ) as tmp: + tmp.write(new_content) + tmp_path = tmp.name + + # Atomic rename + os.replace(tmp_path, filepath) + return True + except IOError as e: + print(f"Error writing {filepath}: {e}", file=sys.stderr) + if os.path.exists(tmp_path): + os.unlink(tmp_path) + return False + + +def main(): + """Main entry point.""" + files_with_types = get_files_to_check() + + modified_files = [] + + for filepath, file_type in files_with_types: + if not os.path.exists(filepath): + # File might not exist (generated files, etc.) + continue + + if fix_file_includes(filepath, file_type): + modified_files.append(filepath) + + # Exit with appropriate code + if modified_files: + # Files were modified - exit 1 so pre-commit shows the diff + sys.exit(1) + else: + # No changes needed + sys.exit(0) + + +if __name__ == "__main__": + main() From 59b455627245bb427d7ab93d173b70dbe3fc71bc Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Mon, 12 Jan 2026 18:49:28 +0100 Subject: [PATCH 13/15] devtools: Add commitlint to pre-commit. --- .pre-commit-config.yaml | 6 +++++ commitlint.config.js | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 commitlint.config.js diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 497750a0cb49..2db847f34550 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,6 +67,12 @@ repos: exclude: ccan|contrib|tests/fuzz/corpora stages: [ manual ] +- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.23.0 + hooks: + - id: commitlint + stages: [commit-msg] + - repo: local hooks: # Reimplementation of `make check-amount-access` for pygrep. diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 000000000000..f156abd38cc5 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,56 @@ +module.exports = { + + plugins: [ + { + rules: { + 'core-lightning': ({ type }) => { + // Allow standard Core Lightning types + const standardTypes = [ + // Daemons + 'channeld', 'closingd', 'connectd', 'gossipd', 'hsmd', 'lightningd', 'onchaind', + 'openingd', + // Related + 'bitcoin', 'cli', 'cln-grpc', 'cln-rpc', 'db', 'wallet', 'wire', + // Others + 'ci', 'common', 'contrib', 'devtools', 'docs', 'docker', 'github', 'global', + 'meta', 'nit', 'nix', 'release', 'script', 'tests', + ]; + + // Extensions + const extensions = ['plugin-', 'pyln-', 'tool-'] + if (type) { + for (const prefix of extensions) { + if (type.startsWith(prefix)) { + return [true]; + } + } + } + + // Otherwise, must be a standard type + if (standardTypes.includes(type)) { + return [true]; + } + + return [ + false, + `Type must be one of [${standardTypes.join(', ')}] or match patterns [${extensions.join(', ')}]` + ]; + }, + }, + }, + ], + + rules: { + // Disable the default type-enum rule since we're using custom validation + 'type-enum': [0], + + // Enable our custom rule + 'core-lightning': [2, 'always'], + + // Keep other standard rules + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'subject-empty': [2, 'never'], + 'subject-case': [2, 'never', ['upper-case']], + }, +}; From f9ad52eab4fa8240a1067ee5eb186ef0351c79dd Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Mon, 12 Jan 2026 20:01:08 +0100 Subject: [PATCH 14/15] devtools: Replace Ruff with Flake8 in pre-commit. --- .pre-commit-config.yaml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2db847f34550..e1f4cdcf504c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,9 @@ repos: -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.8.0 +- repo: https://github.com/pycqa/flake8 + rev: 7.3.0 hooks: - # Run the linter. - - id: ruff - args: [ --diff ] - exclude: "contrib/pyln-grpc-proto/pyln/grpc/(primitives|node)_pb2(|_grpc).py" - # Run the formatter. - - id: ruff-format - args: [ --diff ] + - id: flake8 + args: [ "--ignore=E501,E731,E741,W503,F541,E275" ] exclude: "contrib/pyln-grpc-proto/pyln/grpc/(primitives|node)_pb2(|_grpc).py" - repo: https://github.com/shellcheck-py/shellcheck-py From 2ba55819a5955671641ee546695620735c8c15bc Mon Sep 17 00:00:00 2001 From: Se7enZ Date: Thu, 22 Jan 2026 14:13:41 +0100 Subject: [PATCH 15/15] devtools: Set pre-commit default versions for Python and NodeJS. Default to Python 3 and NodeJS to use that which is on the system, avoiding conflicts with `nodeenv`. --- .pre-commit-config.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1f4cdcf504c..65e0f58bec8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,7 @@ +default_language_version: + node: system + python: python3 + repos: - repo: https://github.com/pycqa/flake8 rev: 7.3.0 @@ -65,7 +69,7 @@ repos: rev: v9.23.0 hooks: - id: commitlint - stages: [commit-msg] + stages: [ commit-msg ] - repo: local hooks: