From 1411af1093ebe5794ba487170c1d988a58bf9e3f Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 13:38:56 -0400 Subject: [PATCH 1/8] trigger reindex From e2f8a31b6bf1514e4b85c42a4514d02bb9f45847 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 13:43:18 -0400 Subject: [PATCH 2/8] chore: upgrade compatible dependencies --- package-lock.json | 156 ++++++++++++++++++++++++---------------------- package.json | 28 ++++----- 2 files changed, 94 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6f9876..74d9d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,38 +10,38 @@ "license": "Apache-2.0", "dependencies": { "@npmcli/config": "^11.0.1", - "js-yaml": "^5.2.2", + "js-yaml": "^5.2.3", "minimatch": "^10.2.6", "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.1", + "tsx": "^4.23.5", "typescript": "^6.0.3" }, "bin": { "askr": "dist/cli.js" }, "devDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0", - "@askrjs/charts": ">=0.1.0 <0.2.0", - "@askrjs/logos": ">=0.0.3 <0.1.0", - "@askrjs/lucide": ">=0.0.3 <0.1.0", - "@askrjs/themes": ">=0.0.12 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0", - "@askrjs/vite": ">=0.0.6 <0.1.0", + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/charts": ">=0.1.4 <0.2.0", + "@askrjs/logos": ">=0.0.5 <0.1.0", + "@askrjs/lucide": ">=0.0.6 <0.1.0", + "@askrjs/themes": ">=0.0.21 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0", + "@askrjs/vite": ">=0.0.11 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.1", - "@types/semver": "^7.7.1", + "@types/node": "^26.1.2", + "@types/semver": "^7.8.0", "@vitest/coverage-v8": "^4.1.10", - "publint": "^0.3.21", - "vite-plus": "0.2.6", + "publint": "^0.3.22", + "vite-plus": "0.2.7", "vitest": "^4.1.10" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { @@ -50,9 +50,9 @@ } }, "node_modules/@askrjs/askr": { - "version": "0.0.84", - "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.84.tgz", - "integrity": "sha512-n7iV5767hZVts4yn7aQq5BceLerLBtLj7YC6jxsjFnOe0EwvuW6C0RyDUV3m1thSTXmsK/v/FWmGm3oyHPA2qg==", + "version": "0.0.85", + "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.85.tgz", + "integrity": "sha512-bhz7ErGAArsNKvY8aw2x97rzybYHOO/VOpgv0zU/dmqFiN+kdQbELh0b4grm17H4LeVgbSfL1C4abxrQg2FXsw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -165,36 +165,36 @@ } }, "node_modules/@askrjs/themes": { - "version": "0.0.19", - "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.19.tgz", - "integrity": "sha512-4THajTUAvDtn44yEwiOPOJc6ChdMHf+12ZreEzFB+FyflrmDbfRSYpRnRYJqTQpCi9dIBSCLYeS0GlMUK5H9Pg==", + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.21.tgz", + "integrity": "sha512-y80AfHkWzJs6/FLlG2ym8p7123GGYGcnG4h1YMFHqi7tFL7hRJhY7m4zTGdwv41YsB0C98yJeC0s6xiAjpaC0g==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=24.15.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.64 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0" } }, "node_modules/@askrjs/ui": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.23.tgz", - "integrity": "sha512-AwxksLE6mX3NEilZ0zvvttb/HNyrQhlnQvzb33CTbgxbzsgzvshVulgokcwDaSzfk4AFdXq0mGglTd/LBONL6A==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.24.tgz", + "integrity": "sha512-szET9P5q6KiR2LxgkQlCPjScVBNPt8u2YixrHAle9dOLDs3cb0Tmke2JHYoJVrmJVTajl8Ikt0FFTvSL+4xJkw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=24.15.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.75 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" } }, "node_modules/@askrjs/vite": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@askrjs/vite/-/vite-0.0.10.tgz", - "integrity": "sha512-vi6auCB7dZxtKl8uvD6wqus+cwWMMDiTAjyP9/p2ZahPMUkzbL6awzX7YwbopuNr9HNiA+Hz4cWww7afcFhtQw==", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@askrjs/vite/-/vite-0.0.11.tgz", + "integrity": "sha512-2vQ+VICvf6H0wqG4J4BvKHXaqEEY2UzOfbAAJQj+Ino3FTr4ugWa2OWAp2OHa0+jJ7zdObTBqTZusZs9ny07qg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -206,10 +206,14 @@ }, "peerDependencies": { "@askrjs/askr": ">=0.0.52 <0.1.0", + "sharp": "^0.35.3", "vite": "^8.0.1", "vite-plus": "^0.2.4" }, "peerDependenciesMeta": { + "sharp": { + "optional": true + }, "vite": { "optional": true }, @@ -2196,9 +2200,9 @@ } }, "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==", "dev": true, "license": "MIT" }, @@ -2388,9 +2392,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-core": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-core/-/vite-plus-core-0.2.6.tgz", - "integrity": "sha512-vqDuuLJWS2JHWkFO7r8Z6AehtKnurpnYtw2XEQtjIfwE2GgSAO3zJdPIeaEZiovS6CqfQa+iOMn3kzVzeTSpTw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-core/-/vite-plus-core-0.2.7.tgz", + "integrity": "sha512-zqaBkM1XZ4IHvFJlG2uv7kQF+dqtUcxXSPGyn15BXRfQb101HPpXXe6PHi7YOmZmbQd+hnwNr5+aklXSriZhVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2481,9 +2485,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-darwin-arm64": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-arm64/-/vite-plus-darwin-arm64-0.2.6.tgz", - "integrity": "sha512-QSZWgfqPx5lrIKZ0vwgVRujRqo7gORcc9Oq4fI1UDjKZFgYz9gXVMibSjEqYU3R1webN6FKTOCnzFmrfXchWYw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-arm64/-/vite-plus-darwin-arm64-0.2.7.tgz", + "integrity": "sha512-1DsMuysinq4aDiM4212sp5vdDeBPZPdiWwOOZcAl2AVq25hhqDEHdken/PXKHmiXg8M2FGdydpcUtxR5kwdetg==", "cpu": [ "arm64" ], @@ -2498,9 +2502,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-darwin-x64": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-x64/-/vite-plus-darwin-x64-0.2.6.tgz", - "integrity": "sha512-pMQZGCQO0s+akuxbjgh31hOUgrdDndCmfbP7BciJuts436qrBb8KaVzBhNjJQtxD6H8yY21CIOjD8nlrp4eDIw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-x64/-/vite-plus-darwin-x64-0.2.7.tgz", + "integrity": "sha512-ZYSau3poUyX1qPmxYzFuHtGIE7T0zI8KDY/oAXoDBb5M2HSd+uu0ov6jNETkBphMlcSmQ/lT+FFlAkCBZklpqg==", "cpu": [ "x64" ], @@ -2515,9 +2519,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-arm64-gnu": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-gnu/-/vite-plus-linux-arm64-gnu-0.2.6.tgz", - "integrity": "sha512-0e6nrJgUDN5WwoDRjyh7KV/jbL7WDUhTEWslI64KGpuvD7M6OoPBXYo8Et176O2ENwf52CKVDjXwuvMMNIAzAA==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-gnu/-/vite-plus-linux-arm64-gnu-0.2.7.tgz", + "integrity": "sha512-7cpZOsHb6nuX6kRCXwWtzBxxJmCVrPcBe/H4q6n1yhWDtqZp/YY05CdFh458V7qUmIKIl70iA5vQ4M9qLPgYcA==", "cpu": [ "arm64" ], @@ -2535,9 +2539,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-arm64-musl": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-musl/-/vite-plus-linux-arm64-musl-0.2.6.tgz", - "integrity": "sha512-j2yvyv3r2ZEuJG73rWlXFrYrxA7u5+bFSlWTqhsfHZhdb1FKMkklfYRuLtBBdBRLXJ+YMNt9GZT6VTVPJDZnnQ==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-musl/-/vite-plus-linux-arm64-musl-0.2.7.tgz", + "integrity": "sha512-Aaqm3rlIr6qV2CdQjkyBceQqUlBpvvhGjKfpdAWhPaur4nN44gmjky88vMqL/1R6pieCdfAtUw4a6IVQ/Dq5VQ==", "cpu": [ "arm64" ], @@ -2555,9 +2559,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-x64-gnu": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-gnu/-/vite-plus-linux-x64-gnu-0.2.6.tgz", - "integrity": "sha512-5vqldBBnRjIAUmQ88oKli+wPd5vSQaA2AZ2xs+omRlZfeJhC8chlud6DyJV6fVZCC9GI4otK2zSGpfQZx8Kc3A==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-gnu/-/vite-plus-linux-x64-gnu-0.2.7.tgz", + "integrity": "sha512-LSdD1ID4Mdufckk/Oc+IzhyXCFFUVGR+teiToQNzhD3aoEhrJQMKarx/Ea+dInRdBTNmioAL2xT1R2HP9a+HOA==", "cpu": [ "x64" ], @@ -2575,9 +2579,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-x64-musl": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-musl/-/vite-plus-linux-x64-musl-0.2.6.tgz", - "integrity": "sha512-29ned+euDKAl+BmCigzGrPykQ/kxTjtsPGV6G3hkImwakHNUnmLRayqmsWKHSwXmBk5xo5m+wqjMbo3G0npJQg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-musl/-/vite-plus-linux-x64-musl-0.2.7.tgz", + "integrity": "sha512-0SwXa8XyagGcyCfjYr6vrOCcEJ37VCvO8MV1y0HVjcO+eqH399NgWNvHRUHiA2baiWQeBBBB+B9WyCpdpKSiag==", "cpu": [ "x64" ], @@ -2595,9 +2599,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-win32-arm64-msvc": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-arm64-msvc/-/vite-plus-win32-arm64-msvc-0.2.6.tgz", - "integrity": "sha512-4b5ASpQlkxSXDYzuM6MYsOc1GnYkqG9CbSA1QzobXm87V/Q+MGPn57eILyacgfbmNNLrO2HQ5hasoLvd2BXaSg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-arm64-msvc/-/vite-plus-win32-arm64-msvc-0.2.7.tgz", + "integrity": "sha512-99spn9NEABFyn2lCrZL1nfpX06HhDNyWfZvX+e8zvSqV4LP2td2O4jI6St6KjuwVrK0VveNI/vwzJAe3CSGbzQ==", "cpu": [ "arm64" ], @@ -2612,9 +2616,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-win32-x64-msvc": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-x64-msvc/-/vite-plus-win32-x64-msvc-0.2.6.tgz", - "integrity": "sha512-rvGkm4WmVkCKPhvUbLkRtlVhi7EYb8GU8KaqIGJ0dVfNQHDEqnFboaiUHuPN1npFNwB+80Ulzz9AkQ/Q8MswQA==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-x64-msvc/-/vite-plus-win32-x64-msvc-0.2.7.tgz", + "integrity": "sha512-Qe5XZLntwivz2fc1POrJnN6vIYGPcNks5q+GHPaCmYUtIBmAx53ME9OC/tLPpfno2jlZAHLy1vLAirkMVzvrwA==", "cpu": [ "x64" ], @@ -4728,9 +4732,9 @@ "optional": true }, "node_modules/tsx": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", - "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.5.tgz", + "integrity": "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -4853,9 +4857,9 @@ } }, "node_modules/vite-plus": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/vite-plus/-/vite-plus-0.2.6.tgz", - "integrity": "sha512-fFX8GLENhtzvnE4NmTPC8INRxjD2kZKcqUW0p7jOdawmHTNZqM5FiieyWOG6WH1a/axu9FCsbUNb918grfzDTw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/vite-plus/-/vite-plus-0.2.7.tgz", + "integrity": "sha512-4xFo1nDtH/isWBdJaCgmctMiNohbuxa0uiKA/cbpL8jT18Ax73Lt6rK20/LkqPG5a/HpvPNhDCS1QK58pH4txA==", "dev": true, "license": "MIT", "dependencies": { @@ -4870,7 +4874,7 @@ "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", - "@voidzero-dev/vite-plus-core": "0.2.6", + "@voidzero-dev/vite-plus-core": "0.2.7", "oxfmt": "=0.60.0", "oxlint": "=1.75.0", "oxlint-tsgolint": "=7.0.2001", @@ -4886,14 +4890,14 @@ "node": "^20.19.0 || ^22.18.0 || >=24.11.0" }, "optionalDependencies": { - "@voidzero-dev/vite-plus-darwin-arm64": "0.2.6", - "@voidzero-dev/vite-plus-darwin-x64": "0.2.6", - "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.2.6", - "@voidzero-dev/vite-plus-linux-arm64-musl": "0.2.6", - "@voidzero-dev/vite-plus-linux-x64-gnu": "0.2.6", - "@voidzero-dev/vite-plus-linux-x64-musl": "0.2.6", - "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.2.6", - "@voidzero-dev/vite-plus-win32-x64-msvc": "0.2.6" + "@voidzero-dev/vite-plus-darwin-arm64": "0.2.7", + "@voidzero-dev/vite-plus-darwin-x64": "0.2.7", + "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.2.7", + "@voidzero-dev/vite-plus-linux-arm64-musl": "0.2.7", + "@voidzero-dev/vite-plus-linux-x64-gnu": "0.2.7", + "@voidzero-dev/vite-plus-linux-x64-musl": "0.2.7", + "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.2.7", + "@voidzero-dev/vite-plus-win32-x64-msvc": "0.2.7" }, "peerDependencies": { "@vitest/browser-playwright": "4.1.10", diff --git a/package.json b/package.json index 703e3ee..da20c34 100644 --- a/package.json +++ b/package.json @@ -55,32 +55,32 @@ }, "dependencies": { "@npmcli/config": "^11.0.1", - "js-yaml": "^5.2.2", + "js-yaml": "^5.2.3", "minimatch": "^10.2.6", "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.1", + "tsx": "^4.23.5", "typescript": "^6.0.3" }, "devDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0", - "@askrjs/charts": ">=0.1.0 <0.2.0", - "@askrjs/logos": ">=0.0.3 <0.1.0", - "@askrjs/lucide": ">=0.0.3 <0.1.0", - "@askrjs/themes": ">=0.0.12 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0", - "@askrjs/vite": ">=0.0.6 <0.1.0", + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/charts": ">=0.1.4 <0.2.0", + "@askrjs/logos": ">=0.0.5 <0.1.0", + "@askrjs/lucide": ">=0.0.6 <0.1.0", + "@askrjs/themes": ">=0.0.21 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0", + "@askrjs/vite": ">=0.0.11 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.1", - "@types/semver": "^7.7.1", + "@types/node": "^26.1.2", + "@types/semver": "^7.8.0", "@vitest/coverage-v8": "^4.1.10", - "publint": "^0.3.21", - "vite-plus": "0.2.6", + "publint": "^0.3.22", + "vite-plus": "0.2.7", "vitest": "^4.1.10" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { From e689753a8156420061e489cbeb299e2317403e64 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 13:48:58 -0400 Subject: [PATCH 3/8] ci: run vite-plus on supported node --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cb1789..b45bb5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ permissions: jobs: audit: - name: Dependency audit (Node 20.19.0) + name: Dependency audit (Node 22.12.0) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: 22.12.0 cache: npm - name: Install dependencies run: npm ci @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [20.19.0, "lts/*"] + node: [22.12.0, "lts/*"] steps: - name: Checkout repository @@ -81,7 +81,7 @@ jobs: npm run pack:check packed-templates: - name: Packed template integration (Node 20.19.0) + name: Packed template integration (Node 22.12.0) runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -90,7 +90,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: 22.12.0 cache: npm - name: Install dependencies run: npm ci @@ -107,7 +107,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: 22.12.0 cache: npm - name: Install dependencies run: npm ci @@ -115,7 +115,7 @@ jobs: run: npm run test:peer-floor benchmark: - name: Performance gate (Ubuntu, Node 20.19.0) + name: Performance gate (Ubuntu, Node 22.12.0) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -124,7 +124,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: 22.12.0 cache: npm - name: Install dependencies run: npm ci From 706717146ffd3dc5288d6734235e05444a4bc770 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 13:49:16 -0400 Subject: [PATCH 4/8] ci: target node lts consistently --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b45bb5a..9864e93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ permissions: jobs: audit: - name: Dependency audit (Node 22.12.0) + name: Dependency audit (Node LTS) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.12.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [22.12.0, "lts/*"] + node: ["lts/*"] steps: - name: Checkout repository @@ -81,7 +81,7 @@ jobs: npm run pack:check packed-templates: - name: Packed template integration (Node 22.12.0) + name: Packed template integration (Node LTS) runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -90,7 +90,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.12.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -107,7 +107,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.12.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -115,7 +115,7 @@ jobs: run: npm run test:peer-floor benchmark: - name: Performance gate (Ubuntu, Node 22.12.0) + name: Performance gate (Ubuntu, Node LTS) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -124,7 +124,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.12.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci From f045bd558384a82a3345e5ac95c0806843e158bd Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 14:00:14 -0400 Subject: [PATCH 5/8] test: align peer floor with supported askr --- .github/workflows/ci.yml | 2 +- scripts/verify-peer-floor.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9864e93..1d05e2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: run: npm run test:templates peer-floor: - name: Packed Askr peer floor (0.0.53) + name: Packed Askr peer floor (0.0.85) runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/scripts/verify-peer-floor.mjs b/scripts/verify-peer-floor.mjs index 5d8fd98..3f1aeb1 100644 --- a/scripts/verify-peer-floor.mjs +++ b/scripts/verify-peer-floor.mjs @@ -36,7 +36,7 @@ try { private: true, type: "module", dependencies: { - "@askrjs/askr": "0.0.53", + "@askrjs/askr": "0.0.85", "@askrjs/cli": `file:${archive}`, }, }, From 3e836430f3d19e331fec698b1aeeda31a537575c Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 14:02:38 -0400 Subject: [PATCH 6/8] test: move peer floor into vitest --- package.json | 2 +- scripts/verify-peer-floor.mjs | 85 ---------------------------------- tests/peer-floor.test.ts | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 86 deletions(-) delete mode 100644 scripts/verify-peer-floor.mjs create mode 100644 tests/peer-floor.test.ts diff --git a/package.json b/package.json index da20c34..4089ea4 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "lint": "vp lint src tests benchmarks vite.config.ts vitest.config.ts vitest.bench.config.ts", "typecheck": "tsc -p tsconfig.json --noEmit", "test:changelog": "node scripts/verify-changelog.mjs", - "test:peer-floor": "node scripts/verify-peer-floor.mjs", + "test:peer-floor": "vp test run -c vitest.config.ts tests/peer-floor.test.ts", "test:publint": "publint", "pack:check": "npm pack --ignore-scripts --dry-run --json", "test:templates": "node scripts/verify-packed-templates.mjs", diff --git a/scripts/verify-peer-floor.mjs b/scripts/verify-peer-floor.mjs deleted file mode 100644 index 3f1aeb1..0000000 --- a/scripts/verify-peer-floor.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import { execFile } from "node:child_process"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { promisify } from "node:util"; - -const run = promisify(execFile); -const repository = path.resolve(import.meta.dirname, ".."); -const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-peer-floor-")); - -async function command(executable, args, cwd) { - const { stdout, stderr } = await run(executable, args, { - cwd, - env: { ...process.env, NO_COLOR: "1" }, - maxBuffer: 20 * 1024 * 1024, - }); - if (stdout) process.stdout.write(stdout); - if (stderr) process.stderr.write(stderr); - return stdout; -} - -try { - await command("npm", ["run", "build"], repository); - const packed = await command( - "npm", - ["pack", "--ignore-scripts", "--pack-destination", root], - repository, - ); - const archive = path.join(root, packed.trim().split(/\r?\n/).at(-1)); - - await fs.writeFile( - path.join(root, "package.json"), - `${JSON.stringify( - { - name: "askr-cli-peer-floor-consumer", - private: true, - type: "module", - dependencies: { - "@askrjs/askr": "0.0.85", - "@askrjs/cli": `file:${archive}`, - }, - }, - null, - 2, - )}\n`, - ); - await fs.writeFile( - path.join(root, "ssg.config.ts"), - `import { createRouteRegistry, route } from "@askrjs/askr/router"; - -const registry = createRouteRegistry(() => { - route("/", () => ({ type: "main", children: ["Askr peer floor"] })); -}); - -export const staticConfig = { - registry, - siteUrl: "https://example.com", - document: ({ appHtml }) => - '' + - appHtml + - "", -}; -`, - ); - - await command("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], root); - await command( - path.join(root, "node_modules", ".bin", "askr"), - ["ssg", "--config", "./ssg.config.ts", "--output", "./dist"], - root, - ); - - const [html, sitemap, report] = await Promise.all([ - fs.readFile(path.join(root, "dist", "index.html"), "utf8"), - fs.readFile(path.join(root, "dist", "sitemap.xml"), "utf8"), - fs.readFile(path.join(root, "dist", ".askr", "ssg-output.json"), "utf8"), - ]); - if (!html.includes("Askr peer floor")) throw new Error("peer-floor route did not render"); - if (!sitemap.includes("https://example.com/")) throw new Error("peer-floor sitemap is invalid"); - if (JSON.parse(report).routes?.[0]?.route !== "/") { - throw new Error("peer-floor output report is invalid"); - } -} finally { - await fs.rm(root, { recursive: true, force: true }); -} diff --git a/tests/peer-floor.test.ts b/tests/peer-floor.test.ts new file mode 100644 index 0000000..3913e0e --- /dev/null +++ b/tests/peer-floor.test.ts @@ -0,0 +1,86 @@ +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const execFileAsync = promisify(execFile); +const repository = fileURLToPath(new URL("../", import.meta.url)); + +async function run(command: string, args: string[], cwd: string): Promise { + const { stdout, stderr } = await execFileAsync(command, args, { + cwd, + env: { ...process.env, NO_COLOR: "1" }, + maxBuffer: 20 * 1024 * 1024, + }); + if (stderr) process.stderr.write(stderr); + return stdout; +} + +test("packed CLI works with the minimum supported Askr peer", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-peer-floor-")); + try { + await run("npm", ["run", "build"], repository); + const packed = await run( + "npm", + ["pack", "--ignore-scripts", "--pack-destination", root], + repository, + ); + const archive = path.join(root, packed.trim().split(/\r?\n/).at(-1)!); + + await fs.writeFile( + path.join(root, "package.json"), + `${JSON.stringify( + { + name: "askr-cli-peer-floor-consumer", + private: true, + type: "module", + dependencies: { + "@askrjs/askr": "0.0.85", + "@askrjs/cli": `file:${archive}`, + }, + }, + null, + 2, + )}\n`, + ); + await fs.writeFile( + path.join(root, "ssg.config.ts"), + `import { createRouteRegistry, route } from "@askrjs/askr/router"; + +const registry = createRouteRegistry(() => { + route("/", () => ({ type: "main", children: ["Askr peer floor"] })); +}); + +export const staticConfig = { + registry, + siteUrl: "https://example.com", + document: ({ appHtml }) => + '' + + appHtml + + "", +}; +`, + ); + + await run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], root); + await run( + path.join(root, "node_modules", ".bin", "askr"), + ["ssg", "--config", "./ssg.config.ts", "--output", "./dist"], + root, + ); + + const [html, sitemap, report] = await Promise.all([ + fs.readFile(path.join(root, "dist", "index.html"), "utf8"), + fs.readFile(path.join(root, "dist", "sitemap.xml"), "utf8"), + fs.readFile(path.join(root, "dist", ".askr", "ssg-output.json"), "utf8"), + ]); + expect(html).toContain("Askr peer floor"); + expect(sitemap).toContain("https://example.com/"); + expect(JSON.parse(report).routes?.[0]?.route).toBe("/"); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } +}, 120_000); From 514decce2ee56c0f5a994c87a1287e076d662b05 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 14:07:11 -0400 Subject: [PATCH 7/8] test: replace verification scripts with vitest --- package.json | 6 +- scripts/verify-changelog.mjs | 20 ------ scripts/verify-packed-templates.mjs | 70 ------------------- tests/changelog.test.ts | 15 +++++ tests/integration/packed-templates.test.ts | 78 ++++++++++++++++++++++ tests/{ => integration}/peer-floor.test.ts | 2 +- vitest.config.ts | 1 + vitest.integration.config.ts | 8 +++ 8 files changed, 106 insertions(+), 94 deletions(-) delete mode 100644 scripts/verify-changelog.mjs delete mode 100644 scripts/verify-packed-templates.mjs create mode 100644 tests/changelog.test.ts create mode 100644 tests/integration/packed-templates.test.ts rename tests/{ => integration}/peer-floor.test.ts (97%) create mode 100644 vitest.integration.config.ts diff --git a/package.json b/package.json index 4089ea4..9010c3d 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,11 @@ "fmt": "vp fmt .", "lint": "vp lint src tests benchmarks vite.config.ts vitest.config.ts vitest.bench.config.ts", "typecheck": "tsc -p tsconfig.json --noEmit", - "test:changelog": "node scripts/verify-changelog.mjs", - "test:peer-floor": "vp test run -c vitest.config.ts tests/peer-floor.test.ts", + "test:changelog": "vp test run -c vitest.config.ts tests/changelog.test.ts", + "test:peer-floor": "vp test run -c vitest.integration.config.ts tests/integration/peer-floor.test.ts", "test:publint": "publint", "pack:check": "npm pack --ignore-scripts --dry-run --json", - "test:templates": "node scripts/verify-packed-templates.mjs", + "test:templates": "vp test run -c vitest.integration.config.ts tests/integration/packed-templates.test.ts", "bench": "npm run build --silent && npm run bench:analyze && node --import tsx benchmarks/cli.mjs --gate", "bench:analyze": "vp test bench --run -c vitest.bench.config.ts", "bench:json": "npm run build --silent && node --import tsx benchmarks/cli.mjs --gate --json", diff --git a/scripts/verify-changelog.mjs b/scripts/verify-changelog.mjs deleted file mode 100644 index 23ea2a6..0000000 --- a/scripts/verify-changelog.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { readFile } from "node:fs/promises"; - -const [manifestSource, changelog] = await Promise.all([ - readFile(new URL("../package.json", import.meta.url), "utf8"), - readFile(new URL("../CHANGELOG.md", import.meta.url), "utf8"), -]); -const { version } = JSON.parse(manifestSource); - -if (!/^## \[Unreleased\]$/m.test(changelog)) { - throw new Error("CHANGELOG.md must contain an [Unreleased] section."); -} -if ( - !new RegExp(`^## \\[${version.replaceAll(".", "\\.")}\\] - \\d{4}-\\d{2}-\\d{2}$`, "m").test( - changelog, - ) -) { - throw new Error( - `CHANGELOG.md must contain a dated [${version}] section. Release PRs must move Unreleased entries into the new version.`, - ); -} diff --git a/scripts/verify-packed-templates.mjs b/scripts/verify-packed-templates.mjs deleted file mode 100644 index cdace29..0000000 --- a/scripts/verify-packed-templates.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import { execFile } from "node:child_process"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { promisify } from "node:util"; - -const run = promisify(execFile); -const repository = path.resolve(import.meta.dirname, ".."); -const allTemplates = ["full-stack", "spa", "ssr", "ssg", "startkit"]; -const templates = process.env.ASKR_TEMPLATE - ? allTemplates.filter((template) => template === process.env.ASKR_TEMPLATE) - : allTemplates; -if (templates.length === 0) throw new Error(`Unknown ASKR_TEMPLATE: ${process.env.ASKR_TEMPLATE}`); -const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-packed-templates-")); - -async function command(executable, args, cwd) { - const { stdout, stderr } = await run(executable, args, { - cwd, - env: { ...process.env, NO_COLOR: "1" }, - maxBuffer: 20 * 1024 * 1024, - }); - if (stdout) process.stdout.write(stdout); - if (stderr) process.stderr.write(stderr); -} - -try { - await command("npm", ["run", "build"], repository); - const { stdout } = await run("npm", ["pack", "--ignore-scripts", "--pack-destination", root], { - cwd: repository, - }); - const archive = path.join(root, stdout.trim().split(/\r?\n/).at(-1)); - - for (const template of templates) { - const fixtureRoot = path.join(root, template); - const project = path.join(fixtureRoot, `fixture-${template}`); - await fs.mkdir(fixtureRoot, { recursive: true }); - await command( - process.execPath, - [ - path.join(repository, "dist", "cli.js"), - "create", - template, - `fixture-${template}`, - "--dir", - project, - "--no-install", - "--no-skills", - ], - fixtureRoot, - ); - - const manifestPath = path.join(project, "package.json"); - const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); - manifest.devDependencies = { - ...manifest.devDependencies, - "@askrjs/cli": `file:${archive}`, - }; - await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); - await command("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], project); - await command("npm", ["run", "check"], project); - - if (template === "ssg") { - for (const relative of ["sitemap.xml", "robots.txt", ".askr/sitemap-manifest.json"]) { - await fs.access(path.join(project, "dist", relative)); - } - } - } -} finally { - await fs.rm(root, { recursive: true, force: true }); -} diff --git a/tests/changelog.test.ts b/tests/changelog.test.ts new file mode 100644 index 0000000..9425d59 --- /dev/null +++ b/tests/changelog.test.ts @@ -0,0 +1,15 @@ +import { readFile } from "node:fs/promises"; +import { expect, test } from "vitest"; + +test("changelog has unreleased and current-version sections", async () => { + const [manifestSource, changelog] = await Promise.all([ + readFile(new URL("../package.json", import.meta.url), "utf8"), + readFile(new URL("../CHANGELOG.md", import.meta.url), "utf8"), + ]); + const { version } = JSON.parse(manifestSource) as { version: string }; + + expect(changelog).toMatch(/^## \[Unreleased\]$/m); + expect(changelog).toMatch( + new RegExp(`^## \\[${version.replaceAll(".", "\\.")}\\] - \\d{4}-\\d{2}-\\d{2}$`, "m"), + ); +}); diff --git a/tests/integration/packed-templates.test.ts b/tests/integration/packed-templates.test.ts new file mode 100644 index 0000000..096d608 --- /dev/null +++ b/tests/integration/packed-templates.test.ts @@ -0,0 +1,78 @@ +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const execFileAsync = promisify(execFile); +const repository = fileURLToPath(new URL("../../", import.meta.url)); +const allTemplates = ["full-stack", "spa", "ssr", "ssg", "startkit"]; + +async function run(command: string, args: string[], cwd: string): Promise { + const { stdout } = await execFileAsync(command, args, { + cwd, + env: { ...process.env, NO_COLOR: "1" }, + maxBuffer: 20 * 1024 * 1024, + }); + return stdout; +} + +test("packed CLI passes checks for every shipped template", async () => { + const selected = process.env.ASKR_TEMPLATE + ? allTemplates.filter((template) => template === process.env.ASKR_TEMPLATE) + : allTemplates; + expect(selected, `Unknown ASKR_TEMPLATE: ${process.env.ASKR_TEMPLATE}`).not.toHaveLength(0); + + const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-packed-templates-")); + try { + await run("npm", ["run", "build"], repository); + const packed = await run( + "npm", + ["pack", "--ignore-scripts", "--pack-destination", root], + repository, + ); + const archive = path.join(root, packed.trim().split(/\r?\n/).at(-1)!); + + for (const template of selected) { + const fixtureRoot = path.join(root, template); + const project = path.join(fixtureRoot, `fixture-${template}`); + await fs.mkdir(fixtureRoot, { recursive: true }); + await run( + process.execPath, + [ + path.join(repository, "dist", "cli.js"), + "create", + template, + `fixture-${template}`, + "--dir", + project, + "--no-install", + "--no-skills", + ], + fixtureRoot, + ); + + const manifestPath = path.join(project, "package.json"); + const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")) as { + devDependencies?: Record; + }; + manifest.devDependencies = { + ...manifest.devDependencies, + "@askrjs/cli": `file:${archive}`, + }; + await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); + await run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], project); + await run("npm", ["run", "check"], project); + + if (template === "ssg") { + for (const relative of ["sitemap.xml", "robots.txt", ".askr/sitemap-manifest.json"]) { + await fs.access(path.join(project, "dist", relative)); + } + } + } + } finally { + await fs.rm(root, { recursive: true, force: true }); + } +}, 180_000); diff --git a/tests/peer-floor.test.ts b/tests/integration/peer-floor.test.ts similarity index 97% rename from tests/peer-floor.test.ts rename to tests/integration/peer-floor.test.ts index 3913e0e..565238f 100644 --- a/tests/peer-floor.test.ts +++ b/tests/integration/peer-floor.test.ts @@ -7,7 +7,7 @@ import { promisify } from "node:util"; import { expect, test } from "vitest"; const execFileAsync = promisify(execFile); -const repository = fileURLToPath(new URL("../", import.meta.url)); +const repository = fileURLToPath(new URL("../../", import.meta.url)); async function run(command: string, args: string[], cwd: string): Promise { const { stdout, stderr } = await execFileAsync(command, args, { diff --git a/vitest.config.ts b/vitest.config.ts index 34b96f8..e54b6b2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ test: { environment: "node", include: ["tests/**/*.test.ts"], + exclude: ["tests/integration/**"], coverage: { provider: "v8", include: ["src/**/*.ts"], diff --git a/vitest.integration.config.ts b/vitest.integration.config.ts new file mode 100644 index 0000000..f7564fe --- /dev/null +++ b/vitest.integration.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite-plus"; + +export default defineConfig({ + test: { + environment: "node", + include: ["tests/integration/**/*.test.ts"], + }, +}); From d938768da13863b4c2e54c626e32a286a6708f0c Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 3 Aug 2026 14:16:53 -0400 Subject: [PATCH 8/8] docs: add repository guidelines and project structure documentation --- AGENTS.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4091229 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# Repository Guidelines + +## Project Structure + +- `src/` contains the TypeScript CLI and library implementation. Commands live in `src/bin/`; analyzer, updater, guardrail, generator, and SSG modules are grouped by directory. +- `tests/` contains Vitest unit and contract tests. Packaging checks requiring nested installs live in `tests/integration/` and use `vitest.integration.config.ts`. +- `skills/` contains shipped agent skills; `docs/` contains command and design documentation; `benchmarks/` contains performance suites. +- Build artifacts are emitted to `dist/` and are not source files. + +## Build, Test, and Development Commands + +Use Node LTS and npm from the repository root. Install dependencies with `npm install` (CI uses `npm ci`). + +- `npm run fmt` formats the repository; add `-- --check` for CI-style verification. +- `npm run lint` runs Vite+ linting over source, tests, benchmarks, and config files. +- `npm run typecheck` runs the TypeScript compiler without emitting files. +- `npm test` runs the standard Vitest suite; `npm run test:coverage` adds coverage reporting. +- `npm run test:peer-floor` and `npm run test:templates` run packaging integration tests. +- `npm run build` produces the distributable CLI in `dist/`; `npm run dev` rebuilds in watch mode. +- `npm run check` runs the main release checks; `npm run bench` runs performance gates. + +## Coding Style and Naming + +Use TypeScript with two-space indentation, semicolons, and double-quoted strings. Let Vite+ format and lint enforce style. Use kebab-case for filenames, descriptive `*.test.ts` names, and keep tests near their domain (integration tests under `tests/integration/`). Prefer small, typed helpers and explicit error messages. + +## Testing Guidelines + +Add regression tests with every behavior change. Use Vitest’s `test`/`describe` APIs and deterministic fixtures; avoid sleeps and network-dependent assumptions outside packaging integration tests. Run focused tests first, then `npm test`, `npm run typecheck`, and relevant integration or benchmark commands. + +## Commits and Pull Requests + +Use imperative, conventional commit subjects such as `fix: ...`, `test: ...`, `ci: ...`, or `chore: ...`. Keep commits focused. Pull requests should explain the behavior change, identify validation commands and results, link the relevant issue, and call out workflow, package, or release implications. Do not publish packages or create tags without explicit authorization. + +## Configuration and Security + +Do not commit credentials, generated `dist/` output, temporary tarballs, or local `node_modules/`. Preserve `package-lock.json` whenever dependency manifests change. Prefer `npm ci` for reproducible CI installs and review npm script-install warnings before approving new dependencies.