From d5c785802afc00998f0f0f928dc23662743741bd Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 13:47:25 +0100 Subject: [PATCH 01/16] chore: add @eslint/markdown dependency to package.json --- package-lock.json | 1320 ++++++++++++++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 1236 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index b5cc26a..078d7a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@bahmutov/cypress-esbuild-preprocessor": "^2.2.8", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "^9.39.1", + "@eslint/markdown": "8.0.3", "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.65.0", "axe-core": "^4.11.4", @@ -1308,6 +1309,56 @@ "url": "https://eslint.org/donate" } }, + "node_modules/@eslint/markdown": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@eslint/markdown/-/markdown-8.0.3.tgz", + "integrity": "sha512-rBTSSShrq7e4O+PWfeE4azH4/CWPNrC+VGxBXiW00o3vYVJnznsZiDayj3KC9JztIMVRZxZHn2nrDIUau/4j7A==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "github-slugger": "^2.0.0", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-frontmatter": "^2.0.1", + "mdast-util-gfm": "^3.1.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm": "^3.0.0", + "micromark-extension-math": "^3.1.0", + "micromark-util-normalize-identifier": "^2.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/markdown/node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/markdown/node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -1562,18 +1613,57 @@ "node": ">=14" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -1602,6 +1692,12 @@ "license": "MIT", "peer": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.65.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", @@ -2609,6 +2705,16 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2637,6 +2743,16 @@ "node": ">=8" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -3190,6 +3306,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -3331,6 +3460,15 @@ "node": ">=14.17" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detective-amd": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.1.0.tgz", @@ -3472,6 +3610,19 @@ "typescript": "^5.4.4 || ^6.0.2" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", @@ -4338,6 +4489,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4677,6 +4841,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/fp-ts": { "version": "2.16.11", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", @@ -4898,6 +5070,12 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -6087,6 +6265,31 @@ "node": ">=4.0" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6288,6 +6491,16 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6339,6 +6552,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6348,134 +6571,982 @@ "node": ">= 0.4" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", - "peer": true + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", - "peer": true, "engines": { - "node": ">= 8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", - "peer": true, "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", "license": "MIT", - "peer": true, - "bin": { - "mime": "cli.js" + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" }, - "engines": { - "node": ">=10.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", - "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", - "peer": true, "dependencies": { - "mime-db": "1.52.0" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", - "peer": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": ">=16 || 14 >=14.17" + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", "license": "MIT", - "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "peer": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" }, @@ -9260,6 +10331,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -9766,6 +10906,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 65cb8a8..9fbc872 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@bahmutov/cypress-esbuild-preprocessor": "^2.2.8", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "^9.39.1", + "@eslint/markdown": "8.0.3", "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.65.0", "axe-core": "^4.11.4", From 2b5b58c0321c534846294611c33ebbd0dadd6561 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:14:24 +0100 Subject: [PATCH 02/16] feat: add tests for CLAUDE.md linting rules with node and browser configurations --- test/integration/claude-md-config.test.js | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/integration/claude-md-config.test.js diff --git a/test/integration/claude-md-config.test.js b/test/integration/claude-md-config.test.js new file mode 100644 index 0000000..76f66c0 --- /dev/null +++ b/test/integration/claude-md-config.test.js @@ -0,0 +1,39 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import { fileURLToPath } from 'node:url'; +import { ESLint } from 'eslint'; + +const nodeConfig = fileURLToPath(new URL('../../index.js', import.meta.url)); +const browserConfig = fileURLToPath(new URL('../../browser.js', import.meta.url)); + +const oversizedWithPlaceholder = `This file provides guidance to Claude Code\n\n${Array.from( + { length: 130 }, + (_, i) => `line ${i}`, +).join('\n')}\n`; + +async function lintClaudeMd(configFile, code) { + const eslint = new ESLint({ overrideConfigFile: configFile }); + const [result] = await eslint.lintText(code, { filePath: 'CLAUDE.md' }); + return result.messages.map(message => message.ruleId); +} + +test('node config lints CLAUDE.md with the claude-md rules', async () => { + const ruleIds = await lintClaudeMd(nodeConfig, oversizedWithPlaceholder); + assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); + assert.ok(ruleIds.includes('claude-md/no-placeholder-text'), 'expected no-placeholder-text to fire'); +}); + +test('browser config lints CLAUDE.md with the claude-md rules', async () => { + const ruleIds = await lintClaudeMd(browserConfig, oversizedWithPlaceholder); + assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); + assert.ok(ruleIds.includes('claude-md/no-placeholder-text'), 'expected no-placeholder-text to fire'); +}); + +test('a healthy CLAUDE.md produces no claude-md warnings', async () => { + const healthy = '# my-project\n\nA specific description of what this repo does.\n'; + const ruleIds = await lintClaudeMd(nodeConfig, healthy); + assert.ok( + !ruleIds.some(id => id?.startsWith('claude-md/')), + `expected no claude-md warnings, got: ${ruleIds.join(', ')}`, + ); +}); From 6891e278b5db62ba39dd5fab646ec22233f7bab6 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:14:32 +0100 Subject: [PATCH 03/16] feat: add tests for no-placeholder-text rule in CLAUDE.md --- test/rules/no-placeholder-text.test.js | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/rules/no-placeholder-text.test.js diff --git a/test/rules/no-placeholder-text.test.js b/test/rules/no-placeholder-text.test.js new file mode 100644 index 0000000..07ec904 --- /dev/null +++ b/test/rules/no-placeholder-text.test.js @@ -0,0 +1,48 @@ +import { describe, it } from 'node:test'; +import { RuleTester } from 'eslint'; +import markdown from '@eslint/markdown'; +import rule from '../../plugins/claude-md/rules/no-placeholder-text.js'; + +RuleTester.describe = describe; +RuleTester.it = it; + +const ruleTester = new RuleTester({ + plugins: { markdown }, + language: 'markdown/gfm', +}); + +ruleTester.run('claude-md/no-placeholder-text', rule, { + valid: [ + // Real, project-specific content is fine. + { code: '# shared-githooks\n\nNPM package that delivers git hooks.\n' }, + // Custom patterns that do not appear in the file. + { + code: '# Repo\n\nDoes a specific thing.\n', + options: [{ patterns: ['TODO: describe this repo'] }], + }, + // Providing `patterns` replaces the defaults: the default opener is no + // longer flagged when a custom list is supplied. + { + code: 'This file provides guidance to Claude Code\n', + options: [{ patterns: ['SOME OTHER PHRASE'] }], + }, + ], + invalid: [ + { + // The default banned opener, flagged on its own line. + code: '# Repo\n\nThis file provides guidance to Claude Code (claude.ai/code).\n', + errors: [{ messageId: 'placeholderText', line: 3 }], + }, + { + // Matching is case-insensitive. + code: 'this FILE provides GUIDANCE to claude code\n', + errors: [{ messageId: 'placeholderText', line: 1 }], + }, + { + // A custom pattern is matched (case-insensitive substring). + code: '# Repo\n\nTODO: fill this in later\n', + options: [{ patterns: ['TODO: fill this in'] }], + errors: [{ messageId: 'placeholderText', line: 3 }], + }, + ], +}); From dee69437a6ac4728fdf232b556af5ed7be238b8a Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:14:52 +0100 Subject: [PATCH 04/16] feat: add tests for max-lines rule in CLAUDE.md --- test/rules/max-lines.test.js | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/rules/max-lines.test.js diff --git a/test/rules/max-lines.test.js b/test/rules/max-lines.test.js new file mode 100644 index 0000000..1f662c1 --- /dev/null +++ b/test/rules/max-lines.test.js @@ -0,0 +1,40 @@ +import { describe, it } from 'node:test'; +import { RuleTester } from 'eslint'; +import markdown from '@eslint/markdown'; +import rule from '../../plugins/claude-md/rules/max-lines.js'; + +// Report each RuleTester case as a real node:test subtest. +RuleTester.describe = describe; +RuleTester.it = it; + +const ruleTester = new RuleTester({ + plugins: { markdown }, + language: 'markdown/gfm', +}); + +ruleTester.run('claude-md/max-lines', rule, { + valid: [ + // A file exactly at the limit passes (N lines, max N). + { code: 'a\nb\nc\n', options: [{ max: 3 }] }, + // Under the limit passes. + { code: 'a\nb\n', options: [{ max: 3 }] }, + // A file with no trailing newline still counts every content line. + { code: 'a\nb\nc', options: [{ max: 3 }] }, + // Default max is 100 — a small file is fine with no options. + { code: '# Title\n\nSome context.\n' }, + ], + invalid: [ + { + // 4 content lines with max 3 -> warns, pointing at the first extra line. + code: 'a\nb\nc\nd\n', + options: [{ max: 3 }], + errors: [{ messageId: 'tooManyLines', line: 4 }], + }, + { + // No trailing newline still counts every content line. + code: 'a\nb\nc\nd', + options: [{ max: 3 }], + errors: [{ messageId: 'tooManyLines', line: 4 }], + }, + ], +}); From e6c2b68d948e244f12a5ef3c2ac976f2e1183aac Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:32:00 +0100 Subject: [PATCH 05/16] refactor: simplify CLAUDE.md tests by removing placeholder checks and consolidating oversized test cases --- test/integration/claude-md-config.test.js | 15 +++---- test/rules/no-placeholder-text.test.js | 48 ----------------------- 2 files changed, 5 insertions(+), 58 deletions(-) delete mode 100644 test/rules/no-placeholder-text.test.js diff --git a/test/integration/claude-md-config.test.js b/test/integration/claude-md-config.test.js index 76f66c0..3e80961 100644 --- a/test/integration/claude-md-config.test.js +++ b/test/integration/claude-md-config.test.js @@ -6,10 +6,7 @@ import { ESLint } from 'eslint'; const nodeConfig = fileURLToPath(new URL('../../index.js', import.meta.url)); const browserConfig = fileURLToPath(new URL('../../browser.js', import.meta.url)); -const oversizedWithPlaceholder = `This file provides guidance to Claude Code\n\n${Array.from( - { length: 130 }, - (_, i) => `line ${i}`, -).join('\n')}\n`; +const oversized = `${Array.from({ length: 130 }, (_, i) => `line ${i}`).join('\n')}\n`; async function lintClaudeMd(configFile, code) { const eslint = new ESLint({ overrideConfigFile: configFile }); @@ -17,16 +14,14 @@ async function lintClaudeMd(configFile, code) { return result.messages.map(message => message.ruleId); } -test('node config lints CLAUDE.md with the claude-md rules', async () => { - const ruleIds = await lintClaudeMd(nodeConfig, oversizedWithPlaceholder); +test('node config lints an oversized CLAUDE.md', async () => { + const ruleIds = await lintClaudeMd(nodeConfig, oversized); assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); - assert.ok(ruleIds.includes('claude-md/no-placeholder-text'), 'expected no-placeholder-text to fire'); }); -test('browser config lints CLAUDE.md with the claude-md rules', async () => { - const ruleIds = await lintClaudeMd(browserConfig, oversizedWithPlaceholder); +test('browser config lints an oversized CLAUDE.md', async () => { + const ruleIds = await lintClaudeMd(browserConfig, oversized); assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); - assert.ok(ruleIds.includes('claude-md/no-placeholder-text'), 'expected no-placeholder-text to fire'); }); test('a healthy CLAUDE.md produces no claude-md warnings', async () => { diff --git a/test/rules/no-placeholder-text.test.js b/test/rules/no-placeholder-text.test.js deleted file mode 100644 index 07ec904..0000000 --- a/test/rules/no-placeholder-text.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, it } from 'node:test'; -import { RuleTester } from 'eslint'; -import markdown from '@eslint/markdown'; -import rule from '../../plugins/claude-md/rules/no-placeholder-text.js'; - -RuleTester.describe = describe; -RuleTester.it = it; - -const ruleTester = new RuleTester({ - plugins: { markdown }, - language: 'markdown/gfm', -}); - -ruleTester.run('claude-md/no-placeholder-text', rule, { - valid: [ - // Real, project-specific content is fine. - { code: '# shared-githooks\n\nNPM package that delivers git hooks.\n' }, - // Custom patterns that do not appear in the file. - { - code: '# Repo\n\nDoes a specific thing.\n', - options: [{ patterns: ['TODO: describe this repo'] }], - }, - // Providing `patterns` replaces the defaults: the default opener is no - // longer flagged when a custom list is supplied. - { - code: 'This file provides guidance to Claude Code\n', - options: [{ patterns: ['SOME OTHER PHRASE'] }], - }, - ], - invalid: [ - { - // The default banned opener, flagged on its own line. - code: '# Repo\n\nThis file provides guidance to Claude Code (claude.ai/code).\n', - errors: [{ messageId: 'placeholderText', line: 3 }], - }, - { - // Matching is case-insensitive. - code: 'this FILE provides GUIDANCE to claude code\n', - errors: [{ messageId: 'placeholderText', line: 1 }], - }, - { - // A custom pattern is matched (case-insensitive substring). - code: '# Repo\n\nTODO: fill this in later\n', - options: [{ patterns: ['TODO: fill this in'] }], - errors: [{ messageId: 'placeholderText', line: 3 }], - }, - ], -}); From ab9862b4e4fefae8d04c62a9d3060ce7ae49428f Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:32:36 +0100 Subject: [PATCH 06/16] feat: add max-lines rule for CLAUDE.md to enforce line count limits --- plugins/claude-md/rules/max-lines.js | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 plugins/claude-md/rules/max-lines.js diff --git a/plugins/claude-md/rules/max-lines.js b/plugins/claude-md/rules/max-lines.js new file mode 100644 index 0000000..e759041 --- /dev/null +++ b/plugins/claude-md/rules/max-lines.js @@ -0,0 +1,46 @@ +const DEFAULT_MAX = 100; + +export default { + meta: { + type: 'suggestion', + docs: { + description: 'Warn when CLAUDE.md exceeds a maximum number of lines', + recommended: true, + }, + schema: [ + { + type: 'object', + properties: { max: { type: 'integer', minimum: 1 } }, + additionalProperties: false, + }, + ], + messages: { + tooManyLines: + 'CLAUDE.md has {{count}} lines, over the {{max}}-line guideline. Trim it or split sections into files Claude Code can import on demand.', + }, + }, + create(context) { + const { max = DEFAULT_MAX } = context.options[0] || {}; + const { sourceCode } = context; + + return { + root() { + const { lines } = sourceCode; + // `lines` splits on line breaks; a trailing newline yields a final + // empty entry that should not count as a content line. + const count = lines.length > 0 && lines[lines.length - 1] === '' ? lines.length - 1 : lines.length; + + if (count > max) { + context.report({ + loc: { + start: { line: max + 1, column: 0 }, + end: { line: lines.length, column: lines[lines.length - 1].length }, + }, + messageId: 'tooManyLines', + data: { count, max }, + }); + } + }, + }; + }, +}; From 6db895d5f6d5df1f18187e7bd91517ae3bfe0444 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:32:43 +0100 Subject: [PATCH 07/16] feat: add CLAUDE.md ESLint plugin with max-lines rule --- plugins/claude-md/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 plugins/claude-md/index.js diff --git a/plugins/claude-md/index.js b/plugins/claude-md/index.js new file mode 100644 index 0000000..0db4c70 --- /dev/null +++ b/plugins/claude-md/index.js @@ -0,0 +1,11 @@ +import maxLines from './rules/max-lines.js'; + +// First-party ESLint plugin holding lint rules for `CLAUDE.md` project-context +// files. Rules are referenced as `claude-md/` wherever the plugin is +// registered (see claude-md.config.js). +export default { + meta: { name: 'claude-md' }, + rules: { + 'max-lines': maxLines, + }, +}; From 0a296f7f54538ada408139651a08465b3349c454 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:39:37 +0100 Subject: [PATCH 08/16] feat: add CLAUDE.md linting configuration to browser and index ESLint setups --- browser.js | 8 ++++++-- claude-md.config.js | 21 +++++++++++++++++++++ index.js | 8 ++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 claude-md.config.js diff --git a/browser.js b/browser.js index d379599..3dd8803 100644 --- a/browser.js +++ b/browser.js @@ -4,6 +4,7 @@ import tseslint from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import { claudeMdConfig } from './claude-md.config.js'; // Base rules adapted from the browser config const baseRules = { @@ -71,8 +72,9 @@ export default [ { ignores: commonIgnores, }, - // Base config for all JavaScript files - js.configs.recommended, + // Base recommended rules — scoped to JS/TS so they never run against + // non-JS languages (e.g. the markdown used for CLAUDE.md below). + { ...js.configs.recommended, files: ['**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'] }, { files: ['**/*.{js,mjs,cjs,jsx}'], languageOptions: { @@ -165,4 +167,6 @@ export default [ ...reactRules, }, }, + // CLAUDE.md project-context checks (see claude-md.config.js) + claudeMdConfig, ]; diff --git a/claude-md.config.js b/claude-md.config.js new file mode 100644 index 0000000..7edbaee --- /dev/null +++ b/claude-md.config.js @@ -0,0 +1,21 @@ +import markdown from '@eslint/markdown'; +import claudeMd from './plugins/claude-md/index.js'; + +// Flat-config block that lints `CLAUDE.md` project-context files. Spread into +// both the Node (`index.js`) and browser (`browser.js`) configs so any repo +// extending either one gets the checks automatically. Severity is `warn` so it +// never blocks; consumers can bump a rule to `error` or turn it `off` in their +// own eslint.config.js. +// +// `@eslint/markdown` supplies the `markdown/gfm` language — without it ESLint +// would try to parse CLAUDE.md as JavaScript and error. +export const claudeMdConfig = { + files: ['**/CLAUDE.md'], + language: 'markdown/gfm', + plugins: { markdown, 'claude-md': claudeMd }, + rules: { + 'claude-md/max-lines': ['warn', { max: 100 }], + }, +}; + +export default claudeMdConfig; diff --git a/index.js b/index.js index 1c14b07..961ea0e 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import js from '@eslint/js'; import globals from 'globals'; import tseslint from 'typescript-eslint'; +import { claudeMdConfig } from './claude-md.config.js'; // Base rules for all JavaScript files const baseRules = { @@ -59,8 +60,9 @@ export default [ { ignores: commonIgnores, }, - // Base config for all JavaScript files - js.configs.recommended, + // Base recommended rules — scoped to JS/TS so they never run against + // non-JS languages (e.g. the markdown used for CLAUDE.md below). + { ...js.configs.recommended, files: ['**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'] }, { files: ['**/*.{js,mjs,cjs}'], languageOptions: { @@ -80,4 +82,6 @@ export default [ }, rules: baseRules, }, + // CLAUDE.md project-context checks (see claude-md.config.js) + claudeMdConfig, ]; From e09dca4ee2cd6a7effd5fe788814b1d892327ac6 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:39:44 +0100 Subject: [PATCH 09/16] fix: update test script to run Node.js tests instead of echo error --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fbc872..16fb343 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "scripts": { "postinstall": "node bin/check-peer-deps.cjs", "lint": "npx eslint -c ./index.js *.js --fix", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "node --test", "prepare": "git rev-parse --git-dir > /dev/null 2>&1 && git config core.hooksPath .githooks || true" }, "engines": { From a17ccfb7c5fcb4285815b9003ce3e4c92db1abd4 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 14:47:54 +0100 Subject: [PATCH 10/16] feat: add CLAUDE.md usage documentation for linting rules --- examples/claude-md-usage.md | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 examples/claude-md-usage.md diff --git a/examples/claude-md-usage.md b/examples/claude-md-usage.md new file mode 100644 index 0000000..33a099d --- /dev/null +++ b/examples/claude-md-usage.md @@ -0,0 +1,65 @@ +# CLAUDE.md linting + +The Node (`.`) and browser (`./browser`) configs include a rule that lints each +repo's `CLAUDE.md` project-context file. It is **on by default** for any project +that extends either config — no extra setup. + +`CLAUDE.md` is what gives Claude Code project context (conventions, commands, +gotchas). Oversized context files are harder for Claude Code to use effectively +and tend to accumulate stale guidance, so this rule keeps the file small enough +to stay useful. + +## What it checks + +| Rule | Severity | Purpose | +| --- | --- | --- | +| `claude-md/max-lines` | `warn` | Flags a `CLAUDE.md` longer than the guideline (default **100** lines). | + +It is a **warning** — it never fails a lint run on its own. Bump it to `error` +in your own config if you want CI to enforce it. + +## Important: your lint command must include `CLAUDE.md` + +ESLint only lints files you point it at. `eslint .` picks up `CLAUDE.md` +automatically; a narrowed command such as `eslint src/**/*.ts` will **not**. +If you lint a subset of files, add `CLAUDE.md` (or `**/CLAUDE.md`) to the set. + +## Overriding + +The rule is tunable per project in your `eslint.config.js`. Because it targets +`**/CLAUDE.md`, scope overrides to that file so they don't touch other files: + +```js +import wtConfig from '@hs-web-team/eslint-config-node'; + +export default [ + ...wtConfig, + { + files: ['**/CLAUDE.md'], + rules: { + // Raise the line limit for a large repo. + 'claude-md/max-lines': ['warn', { max: 150 }], + }, + }, +]; +``` + +Turn it off entirely: + +```js +{ + files: ['**/CLAUDE.md'], + rules: { 'claude-md/max-lines': 'off' }, +} +``` + +## Rule options + +### `claude-md/max-lines` + +```js +['warn', { max: 100 }] // default max is 100 +``` + +- `max` (integer, ≥ 1) — the maximum number of content lines. A trailing + newline does not count as an extra line. From e2a7877d423544813437d50364b752fa1bec1ac6 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 15:04:07 +0100 Subject: [PATCH 11/16] chore: update version to 4.3.0-next.0 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 078d7a7..5c11e39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.2.2", + "version": "4.3.0-next.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hs-web-team/eslint-config-node", - "version": "4.2.2", + "version": "4.3.0-next.0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 16fb343..78a7ac0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.2.2", + "version": "4.3.0-next.0", "description": "HubSpot Marketing WebTeam shared configurations for ESLint, Prettier, Stylelint, and Cypress", "main": "index.js", "type": "module", From e3f44a33e1b7dec4eea71ce5b840d6358580e82e Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 15:04:31 +0100 Subject: [PATCH 12/16] feat: add ADR for linting CLAUDE.md with ESLint rule to enforce max lines --- docs/adr/0001-lint-claude-md-with-eslint.md | 123 ++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 docs/adr/0001-lint-claude-md-with-eslint.md diff --git a/docs/adr/0001-lint-claude-md-with-eslint.md b/docs/adr/0001-lint-claude-md-with-eslint.md new file mode 100644 index 0000000..0b4e06b --- /dev/null +++ b/docs/adr/0001-lint-claude-md-with-eslint.md @@ -0,0 +1,123 @@ +# 1. Lint CLAUDE.md with an ESLint rule + +- Status: Accepted +- Date: 2026-08-19 +- Deciders: HubSpot Web Team + +## Context + +`CLAUDE.md` gives Claude Code the project context it needs to be useful in a +repo: conventions, common commands, and gotchas. One failure mode quietly +degrades that context: the file **grows too large**. Oversized context files +are harder for Claude Code to use effectively and tend to accumulate stale or +redundant guidance. We treat **100 lines** as the practical guideline. + +An earlier prototype enforced this as a warn-only check in a shared **git +pre-push hook** (HubSpotMarketingWebTeam/shared-githooks PR #35, recorded in +that repo's ADR 0001). During review the point was raised that a check on a +file's content — line count, banned boilerplate, deprecated constructs — is +conceptually a **linting** concern: deterministic, per-file, and something each +project should be able to tune or disable. This package +(`@hs-web-team/eslint-config-node`) is the shared home for exactly those rules, +already installed as a dev dependency across the team's projects, so it is the +natural place for the check to live. + +This ADR supersedes the pre-push-hook approach. The `shared-githooks` ADR 0001 +should be marked superseded and its `CLAUDE.md` check removed. + +## Decision + +Add a first-party ESLint plugin, `claude-md`, to this package and wire it +**always-on** into both shared configs (`index.js` for Node, `browser.js` for +browser/React). The plugin ships one rule: + +- **`claude-md/max-lines`** — warns when `CLAUDE.md` exceeds a maximum number of + lines. Default `max` is **100**; a trailing newline does not count as a + content line. It reports at the first line past the limit. + +Wiring (see `claude-md.config.js`): + +```js +{ + files: ['**/CLAUDE.md'], + language: 'markdown/gfm', + plugins: { markdown, 'claude-md': claudeMd }, + rules: { 'claude-md/max-lines': ['warn', { max: 100 }] }, +} +``` + +Properties: + +- **Warn-only.** Severity is `warn`, so the rule never fails a lint run on its + own — consistent with the nudge-not-block philosophy of the original hook. +- **Always on, fully overridable.** Every project that extends either config + gets the check with no setup, and can retune (`{ max: 150 }`), promote + (`error`), or disable (`off`) it per project by scoping an override to + `files: ['**/CLAUDE.md']`. +- **Markdown language dependency.** ESLint cannot parse `.md` on its own — it + would feed `CLAUDE.md` to the JavaScript parser and error. We add + `@eslint/markdown`, which registers the `markdown/gfm` language used to parse + the file into an AST the rule can inspect. +- **Scoped base rules.** `js.configs.recommended` was previously registered + with no `files` key, so it applied to *every* linted file. Once `CLAUDE.md` + became lintable, its recommended JS rules (e.g. `no-irregular-whitespace`) + ran against the markdown AST and crashed. We scoped that config to + `**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}`. This is a no-op for existing + consumers — non-JS files were never lintable before — but it is a subtle + change worth recording. + +## Considered options + +**Enforcement mechanism — ESLint rule vs. git pre-push hook.** We chose the +**ESLint rule**. A file-content check is a linting concern, and expressing it as +a rule makes it per-project overridable — the property the hook lacked. The +hook's advantages were real (it fires for every push regardless of a repo's +lint setup, and can surface a desktop notification to GUI git clients), but they +did not outweigh living in the tool built for opinionated, tunable file rules. + +**Missing-`CLAUDE.md` check — keep vs. drop.** We **dropped** it. ESLint only +runs rules against files that exist and match, so "this repo has no `CLAUDE.md`" +cannot be expressed as a rule. The prototype hook warned on a missing file; that +capability does not survive the move and we accepted the loss rather than keep a +hook alongside the rule. + +**Rule set — which checks to ship.** Review suggested three deterministic +checks: max lines, banned boilerplate/placeholder text, and flagging deprecated +"commands" in favour of skills. We shipped **`max-lines` only**. It is the +objective, high-signal check that maps directly to the context-bloat concern. A +`no-placeholder-text` rule was prototyped and dropped (YAGNI): matching a single +canonical phrase is a weak proxy for quality — false positives for files that +keep a generated opener but are well-customised, false negatives for genuinely +empty files that reword it — with no evidence yet of a recurring problem. The +rule structure makes adding more checks later cheap. + +**Delivery — always-on in the default config vs. opt-in export.** We chose +**always-on**. Because the rule is warn-only and overridable, the cost to a +project that does not want it is one line in `eslint.config.js`; that does not +justify an opt-in export that most projects would have to remember to add. + +## Consequences + +- Projects extending either config will see a warning when their `CLAUDE.md` + exceeds 100 lines. Lint runs still pass (warnings only). +- **The rule only fires if the project's lint command actually includes + `CLAUDE.md` in its file set.** `eslint .` picks it up automatically; a + narrowed command such as `eslint src/**/*.ts` does not. Projects that lint a + subset must add `CLAUDE.md` (or `**/CLAUDE.md`) to the set. This is weaker, + less guaranteed coverage than the always-on hook, and it depends on projects + being on ESLint 9 flat config. +- There is no longer any warning for a **missing** `CLAUDE.md`. +- Adds `@eslint/markdown` as a runtime dependency of this package (it installs + into consumers' dev dependency tree via the config). +- Establishes `docs/adr/` and the ADR format for this repository. +- This package's own `CLAUDE.md` (currently ~145 lines) now warns against the + rule; trimming it is tracked separately. + +## Future work (not yet implemented) + +- **More `CLAUDE.md` rules.** The plugin can grow additional checks as concrete + needs appear (e.g. flagging deprecated constructs). Each should stay warn-only + and overridable. +- **Shared, reusable config export.** If more `CLAUDE.md` rules land, consider + exposing them as a named `claude-md` config object so projects can opt into or + out of the whole group in one place. From a69152d1c4c828d2c5319c4947c0a24985b5ea75 Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 15:05:00 +0100 Subject: [PATCH 13/16] chore: update version to 4.3.0-next.1 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c11e39..a78038b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.0", + "version": "4.3.0-next.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.0", + "version": "4.3.0-next.1", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 78a7ac0..5667e93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.0", + "version": "4.3.0-next.1", "description": "HubSpot Marketing WebTeam shared configurations for ESLint, Prettier, Stylelint, and Cypress", "main": "index.js", "type": "module", From d1ad0b2766cb52ab5045b11b66b9dc15685cc73c Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 16:38:31 +0100 Subject: [PATCH 14/16] feat: add ESLint rule to enforce maximum lines in CLAUDE.md --- claude-md.config.js => claude-code.config.js | 0 examples/{claude-md-usage.md => claude-code-usage.md} | 0 plugins/{claude-md => claude-code}/index.js | 0 plugins/{claude-md => claude-code}/rules/max-lines.js | 0 .../{claude-md-config.test.js => claude-code-config.test.js} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename claude-md.config.js => claude-code.config.js (100%) rename examples/{claude-md-usage.md => claude-code-usage.md} (100%) rename plugins/{claude-md => claude-code}/index.js (100%) rename plugins/{claude-md => claude-code}/rules/max-lines.js (100%) rename test/integration/{claude-md-config.test.js => claude-code-config.test.js} (100%) diff --git a/claude-md.config.js b/claude-code.config.js similarity index 100% rename from claude-md.config.js rename to claude-code.config.js diff --git a/examples/claude-md-usage.md b/examples/claude-code-usage.md similarity index 100% rename from examples/claude-md-usage.md rename to examples/claude-code-usage.md diff --git a/plugins/claude-md/index.js b/plugins/claude-code/index.js similarity index 100% rename from plugins/claude-md/index.js rename to plugins/claude-code/index.js diff --git a/plugins/claude-md/rules/max-lines.js b/plugins/claude-code/rules/max-lines.js similarity index 100% rename from plugins/claude-md/rules/max-lines.js rename to plugins/claude-code/rules/max-lines.js diff --git a/test/integration/claude-md-config.test.js b/test/integration/claude-code-config.test.js similarity index 100% rename from test/integration/claude-md-config.test.js rename to test/integration/claude-code-config.test.js From 93d176c3f7203a16d9ce3c64f47f0534a0ff1aec Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 16:41:41 +0100 Subject: [PATCH 15/16] refactor: rename CLAUDE.md linting plugin and update references to claude-code --- browser.js | 6 +++--- claude-code.config.js | 10 +++++----- docs/adr/0001-lint-claude-md-with-eslint.md | 14 +++++++------- examples/claude-code-usage.md | 8 ++++---- index.js | 6 +++--- plugins/claude-code/index.js | 6 +++--- test/integration/claude-code-config.test.js | 10 +++++----- test/rules/max-lines.test.js | 4 ++-- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/browser.js b/browser.js index 3dd8803..69e4889 100644 --- a/browser.js +++ b/browser.js @@ -4,7 +4,7 @@ import tseslint from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; -import { claudeMdConfig } from './claude-md.config.js'; +import { claudeCodeConfig } from './claude-code.config.js'; // Base rules adapted from the browser config const baseRules = { @@ -167,6 +167,6 @@ export default [ ...reactRules, }, }, - // CLAUDE.md project-context checks (see claude-md.config.js) - claudeMdConfig, + // CLAUDE.md project-context checks (see claude-code.config.js) + claudeCodeConfig, ]; diff --git a/claude-code.config.js b/claude-code.config.js index 7edbaee..ab4d1ee 100644 --- a/claude-code.config.js +++ b/claude-code.config.js @@ -1,5 +1,5 @@ import markdown from '@eslint/markdown'; -import claudeMd from './plugins/claude-md/index.js'; +import claudeCode from './plugins/claude-code/index.js'; // Flat-config block that lints `CLAUDE.md` project-context files. Spread into // both the Node (`index.js`) and browser (`browser.js`) configs so any repo @@ -9,13 +9,13 @@ import claudeMd from './plugins/claude-md/index.js'; // // `@eslint/markdown` supplies the `markdown/gfm` language — without it ESLint // would try to parse CLAUDE.md as JavaScript and error. -export const claudeMdConfig = { +export const claudeCodeConfig = { files: ['**/CLAUDE.md'], language: 'markdown/gfm', - plugins: { markdown, 'claude-md': claudeMd }, + plugins: { markdown, 'claude-code': claudeCode }, rules: { - 'claude-md/max-lines': ['warn', { max: 100 }], + 'claude-code/max-lines': ['warn', { max: 100 }], }, }; -export default claudeMdConfig; +export default claudeCodeConfig; diff --git a/docs/adr/0001-lint-claude-md-with-eslint.md b/docs/adr/0001-lint-claude-md-with-eslint.md index 0b4e06b..19a2758 100644 --- a/docs/adr/0001-lint-claude-md-with-eslint.md +++ b/docs/adr/0001-lint-claude-md-with-eslint.md @@ -27,22 +27,22 @@ should be marked superseded and its `CLAUDE.md` check removed. ## Decision -Add a first-party ESLint plugin, `claude-md`, to this package and wire it +Add a first-party ESLint plugin, `claude-code`, to this package and wire it **always-on** into both shared configs (`index.js` for Node, `browser.js` for browser/React). The plugin ships one rule: -- **`claude-md/max-lines`** — warns when `CLAUDE.md` exceeds a maximum number of - lines. Default `max` is **100**; a trailing newline does not count as a +- **`claude-code/max-lines`** — warns when `CLAUDE.md` exceeds a maximum number + of lines. Default `max` is **100**; a trailing newline does not count as a content line. It reports at the first line past the limit. -Wiring (see `claude-md.config.js`): +Wiring (see `claude-code.config.js`): ```js { files: ['**/CLAUDE.md'], language: 'markdown/gfm', - plugins: { markdown, 'claude-md': claudeMd }, - rules: { 'claude-md/max-lines': ['warn', { max: 100 }] }, + plugins: { markdown, 'claude-code': claudeCode }, + rules: { 'claude-code/max-lines': ['warn', { max: 100 }] }, } ``` @@ -119,5 +119,5 @@ justify an opt-in export that most projects would have to remember to add. needs appear (e.g. flagging deprecated constructs). Each should stay warn-only and overridable. - **Shared, reusable config export.** If more `CLAUDE.md` rules land, consider - exposing them as a named `claude-md` config object so projects can opt into or + exposing them as a named `claude-code` config object so projects can opt into or out of the whole group in one place. diff --git a/examples/claude-code-usage.md b/examples/claude-code-usage.md index 33a099d..62353d2 100644 --- a/examples/claude-code-usage.md +++ b/examples/claude-code-usage.md @@ -13,7 +13,7 @@ to stay useful. | Rule | Severity | Purpose | | --- | --- | --- | -| `claude-md/max-lines` | `warn` | Flags a `CLAUDE.md` longer than the guideline (default **100** lines). | +| `claude-code/max-lines` | `warn` | Flags a `CLAUDE.md` longer than the guideline (default **100** lines). | It is a **warning** — it never fails a lint run on its own. Bump it to `error` in your own config if you want CI to enforce it. @@ -38,7 +38,7 @@ export default [ files: ['**/CLAUDE.md'], rules: { // Raise the line limit for a large repo. - 'claude-md/max-lines': ['warn', { max: 150 }], + 'claude-code/max-lines': ['warn', { max: 150 }], }, }, ]; @@ -49,13 +49,13 @@ Turn it off entirely: ```js { files: ['**/CLAUDE.md'], - rules: { 'claude-md/max-lines': 'off' }, + rules: { 'claude-code/max-lines': 'off' }, } ``` ## Rule options -### `claude-md/max-lines` +### `claude-code/max-lines` ```js ['warn', { max: 100 }] // default max is 100 diff --git a/index.js b/index.js index 961ea0e..3c007ff 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ import js from '@eslint/js'; import globals from 'globals'; import tseslint from 'typescript-eslint'; -import { claudeMdConfig } from './claude-md.config.js'; +import { claudeCodeConfig } from './claude-code.config.js'; // Base rules for all JavaScript files const baseRules = { @@ -82,6 +82,6 @@ export default [ }, rules: baseRules, }, - // CLAUDE.md project-context checks (see claude-md.config.js) - claudeMdConfig, + // CLAUDE.md project-context checks (see claude-code.config.js) + claudeCodeConfig, ]; diff --git a/plugins/claude-code/index.js b/plugins/claude-code/index.js index 0db4c70..dd52ee0 100644 --- a/plugins/claude-code/index.js +++ b/plugins/claude-code/index.js @@ -1,10 +1,10 @@ import maxLines from './rules/max-lines.js'; // First-party ESLint plugin holding lint rules for `CLAUDE.md` project-context -// files. Rules are referenced as `claude-md/` wherever the plugin is -// registered (see claude-md.config.js). +// files. Rules are referenced as `claude-code/` wherever the plugin is +// registered (see claude-code.config.js). export default { - meta: { name: 'claude-md' }, + meta: { name: 'claude-code' }, rules: { 'max-lines': maxLines, }, diff --git a/test/integration/claude-code-config.test.js b/test/integration/claude-code-config.test.js index 3e80961..64ccb3c 100644 --- a/test/integration/claude-code-config.test.js +++ b/test/integration/claude-code-config.test.js @@ -16,19 +16,19 @@ async function lintClaudeMd(configFile, code) { test('node config lints an oversized CLAUDE.md', async () => { const ruleIds = await lintClaudeMd(nodeConfig, oversized); - assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); + assert.ok(ruleIds.includes('claude-code/max-lines'), 'expected max-lines to fire'); }); test('browser config lints an oversized CLAUDE.md', async () => { const ruleIds = await lintClaudeMd(browserConfig, oversized); - assert.ok(ruleIds.includes('claude-md/max-lines'), 'expected max-lines to fire'); + assert.ok(ruleIds.includes('claude-code/max-lines'), 'expected max-lines to fire'); }); -test('a healthy CLAUDE.md produces no claude-md warnings', async () => { +test('a healthy CLAUDE.md produces no claude-code warnings', async () => { const healthy = '# my-project\n\nA specific description of what this repo does.\n'; const ruleIds = await lintClaudeMd(nodeConfig, healthy); assert.ok( - !ruleIds.some(id => id?.startsWith('claude-md/')), - `expected no claude-md warnings, got: ${ruleIds.join(', ')}`, + !ruleIds.some(id => id?.startsWith('claude-code/')), + `expected no claude-code warnings, got: ${ruleIds.join(', ')}`, ); }); diff --git a/test/rules/max-lines.test.js b/test/rules/max-lines.test.js index 1f662c1..607babd 100644 --- a/test/rules/max-lines.test.js +++ b/test/rules/max-lines.test.js @@ -1,7 +1,7 @@ import { describe, it } from 'node:test'; import { RuleTester } from 'eslint'; import markdown from '@eslint/markdown'; -import rule from '../../plugins/claude-md/rules/max-lines.js'; +import rule from '../../plugins/claude-code/rules/max-lines.js'; // Report each RuleTester case as a real node:test subtest. RuleTester.describe = describe; @@ -12,7 +12,7 @@ const ruleTester = new RuleTester({ language: 'markdown/gfm', }); -ruleTester.run('claude-md/max-lines', rule, { +ruleTester.run('claude-code/max-lines', rule, { valid: [ // A file exactly at the limit passes (N lines, max N). { code: 'a\nb\nc\n', options: [{ max: 3 }] }, From cf581ed96b09ea930a30bec037e3c4b74ba2072d Mon Sep 17 00:00:00 2001 From: Alessio Michelini Date: Wed, 19 Aug 2026 17:01:59 +0100 Subject: [PATCH 16/16] chore: update version to 4.3.0-next.2 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a78038b..7153104 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.1", + "version": "4.3.0-next.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.1", + "version": "4.3.0-next.2", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 5667e93..8c0b55d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hs-web-team/eslint-config-node", - "version": "4.3.0-next.1", + "version": "4.3.0-next.2", "description": "HubSpot Marketing WebTeam shared configurations for ESLint, Prettier, Stylelint, and Cypress", "main": "index.js", "type": "module",