diff --git a/bin/git-mind.js b/bin/git-mind.js index f76b6ce1..35f2de7e 100755 --- a/bin/git-mind.js +++ b/bin/git-mind.js @@ -499,3 +499,21 @@ switch (command) { process.exitCode = command ? 1 : 0; break; } + +async function flushStream(stream) { + if (!stream.writable || stream.destroyed) return; + + await new Promise((resolve, reject) => { + stream.write('', err => { + if (!err || err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') { + resolve(); + return; + } + reject(err); + }); + }); +} + +await flushStream(process.stdout); +await flushStream(process.stderr); +process.exit(process.exitCode ?? 0); diff --git a/docs/design/git-warp-upgrade-audit.md b/docs/design/git-warp-upgrade-audit.md index 9dc09621..cadc4b1c 100644 --- a/docs/design/git-warp-upgrade-audit.md +++ b/docs/design/git-warp-upgrade-audit.md @@ -1,6 +1,8 @@ # git-warp Upgrade Audit -Status: active execution on `feat/git-warp-upgrade-audit` +Status: active execution. The original 14.x audit ran on +`feat/git-warp-upgrade-audit`; the v17 continuation is running on +`feat/isolated-warp-upgrade-fixture`. Related: @@ -47,6 +49,39 @@ That means this cycle upgrades Git Mind across three major versions of the subst This does not automatically mean "upgrade immediately no matter what." It does mean Git Mind should not keep expanding Hill 1 behavior without auditing the real upgrade surface first. +## 2026-05-31 v17 Continuation + +The original audit cycle moved Git Mind to the 14.x substrate. A follow-on +modernization is now needed because the live npm registry reports: + +- latest published `@git-stunts/git-warp` version: `17.0.0` +- latest published `@git-stunts/plumbing` version: `3.0.3` + +`@git-stunts/git-warp@17.0.0` depends on `@git-stunts/plumbing@^3.0.3`, so +the next substrate upgrade should move those packages together. + +This pass keeps the upgrade sequence explicit: + +1. freeze a sanitized Git-native fixture from the current v5 / git-warp 14 state +2. include only `HEAD` and the relevant `refs/warp/gitmind/*` graph refs +3. test that fixture in Docker without mounting this checkout into the container +4. pack the current Git Mind package and copy it into the Docker context +5. run graph/status/export assertions with a scrubbed home and Git config +6. upgrade to git-warp 17 / plumbing 3 and prove the migrated fixture still reads + +This fixture is intentionally a Git bundle rather than a raw working-directory +archive. It preserves the exact Git object and WARP ref state under test while +excluding `node_modules`, local Git config, remotes, hooks, reflogs, stash state, +and host-specific paths. + +Related issue: [#320](https://github.com/flyingrobots/git-mind/issues/320) + +The local playback command for this safety rail is: + +```bash +npm run test:upgrade-fixture +``` + ## Why This Cycle Exists The goal is explicitly **not** to build a lot of new behavior on top of git-warp right now. diff --git a/package-lock.json b/package-lock.json index 72c4b8c5..fdf55479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "5.0.0", "license": "Apache-2.0", "dependencies": { - "@git-stunts/git-warp": "^14.16.2", - "@git-stunts/plumbing": "^2.8.0", - "ajv": "^8.17.1", + "@git-stunts/git-warp": "17.0.0", + "@git-stunts/plumbing": "3.0.3", + "ajv": "^8.20.0", "chalk": "^5.3.0", "figures": "^6.0.1", "js-yaml": "^4.1.1" @@ -743,9 +743,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -804,61 +804,93 @@ } }, "node_modules/@flyingrobots/bijou": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@flyingrobots/bijou/-/bijou-0.2.0.tgz", - "integrity": "sha512-Oix2Kqq4w87KCkyK2W+8u4E4aGVQiraUy8BF3Bk/NRtT+UlUI0ETs+E7GwpwOyOvHvt0cIOjcMmVPxzKa52P4A==", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@flyingrobots/bijou/-/bijou-5.0.0.tgz", + "integrity": "sha512-Vmcs1jZYIxwb2NOn+LCDMK8ZmIKz64eTQI+gEk11Odn32s4ipIrzawrfrrAWZ4UTsdD5c9xWwwJH6SYgo5klBg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@flyingrobots/bijou-i18n": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-i18n/-/bijou-i18n-5.0.0.tgz", + "integrity": "sha512-S3HUHBBLh7fZlijcyuJvtrcJYa+rlhmfW5AAaMZmvIS1P9yd38t7P3JaPGsmKPJjCIVsVgiH3zrjWY15TKB6xA==", + "license": "Apache-2.0", "engines": { "node": ">=18" } }, "node_modules/@flyingrobots/bijou-node": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-node/-/bijou-node-0.2.0.tgz", - "integrity": "sha512-QaIaoBF0OMRHGtLsga1knplfFEmAeC6Lt4SxWkCKIJahMdNqXatCWM3RdzXcbjfcXqRIXyeEpm1agmmwi4gneQ==", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-node/-/bijou-node-5.0.0.tgz", + "integrity": "sha512-Ano8ydJKF/M8MGPeQDMjOuLouKFEOnEaTwYOKAHHMWxDh03G0Yt9HqZPPu364puzJuyFkY1APxtsxUCbru+5IA==", + "license": "Apache-2.0", "dependencies": { - "@flyingrobots/bijou": "0.2.0", - "chalk": "^5.6.2" + "@flyingrobots/bijou-tui": "5.0.0", + "chalk": "^5.6.2", + "gifenc": "^1.0.3", + "oled-font-5x7": "^1.0.3" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@flyingrobots/bijou": "5.0.0" } }, "node_modules/@flyingrobots/bijou-tui": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-tui/-/bijou-tui-0.2.0.tgz", - "integrity": "sha512-pXEo/Am6svRIKvez7926avdGUbfVndlSOpidBPc42YjCQHU5ZQrEuJpjI7niJb63N0ruxu0VXHci8N0wzBYSow==", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-tui/-/bijou-tui-5.0.0.tgz", + "integrity": "sha512-dJAWBIZ8osXIM5y6Mc2KsawHPYR/3JxQEO78yVzzdsQiAx9PZLTQvB8fY6oUQRf+/n3sU9l2Ep0UUsJIbhAmpA==", + "license": "Apache-2.0", "dependencies": { - "@flyingrobots/bijou": "0.2.0" + "@flyingrobots/bijou-i18n": "5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@flyingrobots/bijou": "5.0.0" + } + }, + "node_modules/@flyingrobots/bijou-tui-app": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@flyingrobots/bijou-tui-app/-/bijou-tui-app-5.0.0.tgz", + "integrity": "sha512-NrmTalkI1uIEBosE4tzSF04pMbP+Rs32NNlCRY6njDawDYr8mDBLErziyomio/zhHVVKvnrLuo6g4aLsDPKqHw==", + "license": "Apache-2.0", + "dependencies": { + "@flyingrobots/bijou": "5.0.0", + "@flyingrobots/bijou-tui": "5.0.0" }, "engines": { "node": ">=18" } }, "node_modules/@git-stunts/alfred": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.4.0.tgz", - "integrity": "sha512-80/W7x4pZYRyJB/AQs89aDbi+BcA7/N8G67zdJbKU7lbdrbbtglnY15/iSU66xvLD7/nFTTk9nGNhpw30h6QEQ==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.10.3.tgz", + "integrity": "sha512-dvy7Ej9Jyv9gPh4PtQuMfsZnUa7ycIwoFFnXLrQutRdoTTY4F4OOD2kcSJOs3w8UZhwOyLsHO7PcetaKB9g32w==", "license": "Apache-2.0", "engines": { "node": ">=20.0.0" } }, "node_modules/@git-stunts/git-cas": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@git-stunts/git-cas/-/git-cas-5.3.2.tgz", - "integrity": "sha512-DLh5fBxTTJTHOUjH0VifSZSHSmffrrdt5cLnFUxMZ/+dcfbnqdQLW0/7prNnDAT05qRXPWGNW27dCZNI/5eN0g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@git-stunts/git-cas/-/git-cas-6.0.0.tgz", + "integrity": "sha512-NyTOaCHq6VBGCbL6HKR0bmX3uarumLAR+s2R8pofMGC3WX3YaS1pNdwTJOOzpvcZGWu3FKWAUVU9U0rdEyRoaw==", "license": "Apache-2.0", "dependencies": { - "@flyingrobots/bijou": "^0.2.0", - "@flyingrobots/bijou-node": "^0.2.0", - "@flyingrobots/bijou-tui": "^0.2.0", + "@flyingrobots/bijou": "^5.0.0", + "@flyingrobots/bijou-node": "^5.0.0", + "@flyingrobots/bijou-tui": "^5.0.0", + "@flyingrobots/bijou-tui-app": "^5.0.0", "@git-stunts/alfred": "^0.10.0", - "@git-stunts/plumbing": "^2.8.0", + "@git-stunts/plumbing": "^3.0.3", + "@git-stunts/vault": "^1.0.1", "cbor-x": "^1.6.0", - "commander": "^14.0.3", + "commander": "14.0.3", "zod": "^3.24.1" }, "bin": { @@ -868,32 +900,26 @@ "node": ">=22.0.0" } }, - "node_modules/@git-stunts/git-cas/node_modules/@git-stunts/alfred": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.10.3.tgz", - "integrity": "sha512-dvy7Ej9Jyv9gPh4PtQuMfsZnUa7ycIwoFFnXLrQutRdoTTY4F4OOD2kcSJOs3w8UZhwOyLsHO7PcetaKB9g32w==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@git-stunts/git-warp": { - "version": "14.16.2", - "resolved": "https://registry.npmjs.org/@git-stunts/git-warp/-/git-warp-14.16.2.tgz", - "integrity": "sha512-PHVTay67Agg/E+4fBufZei8OAV5dZO9qbupNGiM/leJ2NCdzstyH03wac5SJ7iW0Uh/4s3weTPoWXx9vvCBegg==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@git-stunts/git-warp/-/git-warp-17.0.0.tgz", + "integrity": "sha512-cQS9epBFQC+10ezK7oGlc2pK1YnOYv5xOI2WUSbr8KBGWApwrO0i3ttJynBw3S5j2k29yvF7biLhwC6BHbPXpA==", "license": "Apache-2.0", + "workspaces": [ + "packages/*" + ], "dependencies": { - "@git-stunts/alfred": "^0.4.0", - "@git-stunts/git-cas": "^5.3.2", - "@git-stunts/plumbing": "^2.8.0", + "@git-stunts/alfred": "^0.10.3", + "@git-stunts/git-cas": "^6.0.0", + "@git-stunts/plumbing": "^3.0.3", "@git-stunts/trailer-codec": "^2.1.1", + "@noble/hashes": "^2.2.0", "boxen": "^7.1.1", "cbor-x": "^1.6.0", "chalk": "^5.3.0", "cli-table3": "^0.6.3", "elkjs": "^0.11.0", "figures": "^6.0.1", - "roaring": "^2.7.0", "roaring-wasm": "^1.1.0", "string-width": "^7.1.0", "wrap-ansi": "^9.0.0", @@ -901,10 +927,13 @@ }, "bin": { "git-warp": "bin/git-warp", - "warp-graph": "bin/warp-graph.js" + "warp-graph": "dist/bin/warp-graph.js" }, "engines": { "node": ">=22.0.0" + }, + "optionalDependencies": { + "roaring": "^2.7.0" } }, "node_modules/@git-stunts/git-warp/node_modules/zod": { @@ -917,9 +946,9 @@ } }, "node_modules/@git-stunts/plumbing": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@git-stunts/plumbing/-/plumbing-2.8.0.tgz", - "integrity": "sha512-wHZQAgPCG8MlcjrgwQx8OoFSxcKGqxCULxxE3XOZk5xiWW3AgSeZgiQ2Z6XCMz1fbaGVOWQOhIvCsyyzlRFbfw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@git-stunts/plumbing/-/plumbing-3.0.3.tgz", + "integrity": "sha512-LmPaq9c51bYA1Ta0KJrmewFTlAwueQv4EdguiI4/TNK9LOeJkkchC2RaOX09WXubauvUeEk8Z32rG8zxdk491g==", "license": "Apache-2.0", "dependencies": { "zod": "^3.24.1" @@ -951,6 +980,29 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/@git-stunts/vault": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@git-stunts/vault/-/vault-1.0.1.tgz", + "integrity": "sha512-oJSoqTUzNEF9QXJghene9Ia/T1tA8l3DZY4KQ2sQiZK1U8Qveqn+IhRDUhZeWcutTiCaI3BauZxhGr0UH53+gQ==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.3.5" + }, + "engines": { + "bun": ">=1.3.5", + "deno": ">=2.0.0", + "node": ">=20.0.0" + } + }, + "node_modules/@git-stunts/vault/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1003,29 +1055,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1092,6 +1121,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "license": "ISC", + "optional": true, "dependencies": { "minipass": "^7.0.4" }, @@ -1153,6 +1183,7 @@ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", "license": "BSD-3-Clause", + "optional": true, "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", @@ -1169,6 +1200,18 @@ "node": ">=18" } }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", @@ -1211,9 +1254,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", "cpu": [ "arm" ], @@ -1225,9 +1268,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", "cpu": [ "arm64" ], @@ -1239,9 +1282,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", "cpu": [ "arm64" ], @@ -1253,9 +1296,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", "cpu": [ "x64" ], @@ -1267,9 +1310,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", "cpu": [ "arm64" ], @@ -1281,9 +1324,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", "cpu": [ "x64" ], @@ -1295,13 +1338,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1309,13 +1355,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1323,13 +1372,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1337,13 +1389,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1351,13 +1406,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1365,13 +1423,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1379,13 +1440,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1393,13 +1457,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1407,13 +1474,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1421,13 +1491,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1435,13 +1508,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1449,13 +1525,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1463,13 +1542,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1477,9 +1559,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", "cpu": [ "x64" ], @@ -1491,9 +1573,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", "cpu": [ "arm64" ], @@ -1505,9 +1587,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", "cpu": [ "arm64" ], @@ -1519,9 +1601,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", "cpu": [ "ia32" ], @@ -1533,9 +1615,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", "cpu": [ "x64" ], @@ -1547,9 +1629,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", "cpu": [ "x64" ], @@ -1746,6 +1828,7 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "license": "ISC", + "optional": true, "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -1778,14 +1861,15 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", + "optional": true, "engines": { "node": ">= 14" } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1977,9 +2061,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -2117,6 +2201,7 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "license": "BlueOak-1.0.0", + "optional": true, "engines": { "node": ">=18" } @@ -2230,6 +2315,7 @@ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "license": "MIT", + "optional": true, "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -2253,6 +2339,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2288,6 +2375,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -2503,9 +2591,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2671,9 +2759,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -2764,9 +2852,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -2827,6 +2915,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gifenc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz", + "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==", + "license": "MIT" + }, "node_modules/glob": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", @@ -2858,17 +2952,40 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "optional": true, "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2937,6 +3054,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", + "optional": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -2996,9 +3114,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "optional": true, "engines": { @@ -3291,9 +3409,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3307,6 +3425,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "devOptional": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -3427,6 +3546,7 @@ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", + "optional": true, "dependencies": { "minipass": "^7.1.2" }, @@ -3438,12 +3558,13 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -3481,6 +3602,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", + "optional": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -3593,6 +3715,7 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "license": "ISC", + "optional": true, "dependencies": { "abbrev": "^3.0.0" }, @@ -3603,6 +3726,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/oled-font-5x7": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/oled-font-5x7/-/oled-font-5x7-1.0.3.tgz", + "integrity": "sha512-l25WvKft8CgXYxtaqKdYrAS1P91rnUUUIiOXojAOvjNCsfFzIl1aEsE2JuaRgMh1Euo7slm5lX0w+1qNkL8PpQ==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3748,9 +3877,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "devOptional": true, "license": "MIT", "engines": { @@ -3761,9 +3890,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -3781,7 +3910,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3884,6 +4013,7 @@ "integrity": "sha512-gNVeV5H+xb2DB/umdvlPbTrKuCn8zexy6ROkfPL4FcSWxOGWrxfQz0pZeZER+kQLrSoPcz3BolYPM6eFYm96XQ==", "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, "dependencies": { "@mapbox/node-pre-gyp": "^2.0.3" }, @@ -3912,9 +4042,9 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", "dev": true, "license": "MIT", "dependencies": { @@ -3928,31 +4058,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" } }, @@ -3967,6 +4097,7 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4238,10 +4369,11 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", "license": "BlueOak-1.0.0", + "optional": true, "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -4279,9 +4411,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -4313,14 +4445,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4337,13 +4486,13 @@ "license": "ISC" }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -4434,7 +4583,8 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/type-check": { "version": "0.4.0", @@ -4498,9 +4648,9 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", "dependencies": { @@ -4672,13 +4822,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "optional": true }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", + "optional": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -4867,6 +5019,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "license": "BlueOak-1.0.0", + "optional": true, "engines": { "node": ">=18" } diff --git a/package.json b/package.json index a21e370f..8e220055 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,15 @@ "scripts": { "test": "vitest run", "test:watch": "vitest", + "test:upgrade-fixture": "sh scripts/test-upgrade-fixture.sh", "test:coverage": "vitest run --coverage --exclude test/contracts.integration.test.js --exclude test/content.test.js --exclude test/version.test.js", "lint": "eslint src/ bin/", "format": "prettier --write 'src/**/*.js' 'bin/**/*.js'" }, "dependencies": { - "@git-stunts/git-warp": "^14.16.2", - "@git-stunts/plumbing": "^2.8.0", - "ajv": "^8.17.1", + "@git-stunts/git-warp": "17.0.0", + "@git-stunts/plumbing": "3.0.3", + "ajv": "^8.20.0", "chalk": "^5.3.0", "figures": "^6.0.1", "js-yaml": "^4.1.1" diff --git a/scripts/test-upgrade-fixture.sh b/scripts/test-upgrade-fixture.sh new file mode 100755 index 00000000..637ccdf3 --- /dev/null +++ b/scripts/test-upgrade-fixture.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env sh +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +fixture_name=gitmind-v5-warp14 +fixture_dir="$repo_root/test/fixtures/upgrade" +docker_dir="$repo_root/scripts/upgrade-fixture" +context=$(mktemp -d "${TMPDIR:-/tmp}/gitmind-upgrade-fixture.XXXXXX") +image= + +cleanup() { + if [ -n "$image" ]; then + docker image rm "$image" >/dev/null 2>&1 || true + fi + rm -rf "$context" +} + +trap cleanup EXIT INT TERM + +mkdir -p "$context/artifacts" "$context/fixture" "$context/runner" + +package_file=$(cd "$repo_root" && npm pack --pack-destination "$context/artifacts" --silent) +mv "$context/artifacts/$package_file" "$context/artifacts/git-mind-package.tgz" +cp "$repo_root/package-lock.json" "$context/artifacts/package-lock.json" + +cp "$fixture_dir/$fixture_name.bundle" "$context/fixture/$fixture_name.bundle" +cp "$fixture_dir/$fixture_name.fixture.json" "$context/fixture/$fixture_name.fixture.json" +cp "$docker_dir/Dockerfile" "$context/Dockerfile" +cp "$docker_dir/run-upgrade-fixture.mjs" "$context/runner/run-upgrade-fixture.mjs" + +short_sha=$(git -C "$repo_root" rev-parse --short HEAD) +image="git-mind-upgrade-fixture:$fixture_name-$short_sha" + +docker build -t "$image" "$context" +docker run --rm --network none \ + -e HOME=/tmp/home \ + -e GIT_CONFIG_NOSYSTEM=1 \ + -e GIT_CONFIG_GLOBAL=/dev/null \ + -e SSH_AUTH_SOCK= \ + -e GITHUB_TOKEN= \ + -e NPM_TOKEN= \ + "$image" diff --git a/scripts/upgrade-fixture/Dockerfile b/scripts/upgrade-fixture/Dockerfile new file mode 100644 index 00000000..2b83a43c --- /dev/null +++ b/scripts/upgrade-fixture/Dockerfile @@ -0,0 +1,33 @@ +FROM node:22-bookworm-slim@sha256:7af03b14a13c8cdd38e45058fd957bf00a72bbe17feac43b1c15a689c029c732 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + g++ \ + git \ + make \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /upgrade + +COPY artifacts/git-mind-package.tgz /tmp/git-mind-package.tgz +COPY artifacts/package-lock.json /tmp/package-lock.json +RUN mkdir -p /opt/git-mind \ + && tar -xzf /tmp/git-mind-package.tgz -C /opt/git-mind --strip-components=1 \ + && cp /tmp/package-lock.json /opt/git-mind/package-lock.json \ + && cd /opt/git-mind \ + && npm ci --omit=dev \ + && ln -s /opt/git-mind/bin/git-mind.js /usr/local/bin/git-mind \ + && npm cache clean --force + +COPY fixture/gitmind-v5-warp14.bundle /fixtures/gitmind-v5-warp14.bundle +COPY fixture/gitmind-v5-warp14.fixture.json /fixtures/gitmind-v5-warp14.fixture.json +COPY runner/run-upgrade-fixture.mjs /usr/local/bin/run-upgrade-fixture.mjs + +ENV HOME=/tmp/home +ENV GIT_MIND_PACKAGE_ROOT=/opt/git-mind +ENV GIT_CONFIG_NOSYSTEM=1 +ENV GIT_CONFIG_GLOBAL=/dev/null + +CMD ["node", "/usr/local/bin/run-upgrade-fixture.mjs"] diff --git a/scripts/upgrade-fixture/run-upgrade-fixture.mjs b/scripts/upgrade-fixture/run-upgrade-fixture.mjs new file mode 100755 index 00000000..cdcdc478 --- /dev/null +++ b/scripts/upgrade-fixture/run-upgrade-fixture.mjs @@ -0,0 +1,237 @@ +#!/usr/bin/env node + +import { createHash } from 'node:crypto'; +import { createReadStream } from 'node:fs'; +import { mkdir, readFile, rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { execFile as execFileCallback } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFile = promisify(execFileCallback); + +const FIXTURE_NAME = 'gitmind-v5-warp14'; +const FIXTURE_PATH = `/fixtures/${FIXTURE_NAME}.bundle`; +const METADATA_PATH = `/fixtures/${FIXTURE_NAME}.fixture.json`; +const WORK_ROOT = '/work'; +const REPO_DIR = join(WORK_ROOT, 'repo'); +const SCRUBBED_ENV = { + ...process.env, + HOME: '/tmp/home', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + SSH_AUTH_SOCK: '', + GITHUB_TOKEN: '', + NPM_TOKEN: '', +}; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +async function run(command, args, options = {}) { + try { + const result = await execFile(command, args, { + ...options, + env: SCRUBBED_ENV, + maxBuffer: 20 * 1024 * 1024, + }); + return result.stdout.trim(); + } catch (err) { + const stdout = err.stdout ? `\nstdout:\n${err.stdout}` : ''; + const stderr = err.stderr ? `\nstderr:\n${err.stderr}` : ''; + throw new Error( + `Command failed: ${command} ${args.join(' ')}${stdout}${stderr}`, + { cause: err }, + ); + } +} + +async function sha256File(filePath) { + const hash = createHash('sha256'); + await new Promise((resolve, reject) => { + const stream = createReadStream(filePath); + stream.on('data', chunk => hash.update(chunk)); + stream.on('error', reject); + stream.on('end', resolve); + }); + return hash.digest('hex'); +} + +function parseJson(text, label) { + try { + return JSON.parse(text); + } catch (err) { + throw new Error(`Failed to parse ${label} as JSON:\n${text}`, { cause: err }); + } +} + +async function fetchFixtureRefs(metadata) { + const refspecs = metadata.refs.map(ref => { + if (ref.name === 'HEAD') { + return 'HEAD:refs/heads/fixture'; + } + return `${ref.name}:${ref.name}`; + }); + + await run('git', ['init', REPO_DIR]); + await run('git', ['-C', REPO_DIR, 'fetch', '--no-tags', FIXTURE_PATH, ...refspecs]); + await run('git', ['-C', REPO_DIR, 'checkout', 'fixture']); + await run('git', ['-C', REPO_DIR, 'config', 'user.name', 'Git Mind Upgrade Fixture']); + await run('git', ['-C', REPO_DIR, 'config', 'user.email', 'git-mind-upgrade-fixture@example.invalid']); +} + +async function assertFixtureRefs(metadata) { + const head = await run('git', ['-C', REPO_DIR, 'rev-parse', 'HEAD']); + assert( + head === metadata.source.commit, + `expected HEAD ${metadata.source.commit}, got ${head}`, + ); + + for (const ref of metadata.refs) { + if (ref.name === 'HEAD') continue; + const actual = await run('git', ['-C', REPO_DIR, 'rev-parse', ref.name]); + assert(actual === ref.oid, `expected ${ref.name} ${ref.oid}, got ${actual}`); + } +} + +async function runGitWarpUpgrade(metadata) { + const packageRoot = process.env.GIT_MIND_PACKAGE_ROOT + ?? join(await run('npm', ['root', '-g']), '@neuroglyph', 'git-mind'); + const upgradeScript = join( + packageRoot, + 'node_modules', + '@git-stunts', + 'git-warp', + 'dist', + 'scripts', + 'upgrade-v16-to-v17.js', + ); + + const result = parseJson( + await run('node', [ + upgradeScript, + '--repo', + REPO_DIR, + '--graph', + metadata.migration.graphName, + '--json', + ]), + 'git-warp upgrade', + ); + + assert(result.graphCount === 1, `expected one migrated graph, got ${result.graphCount}`); + + const graph = result.graphs[0]; + assert(graph.graphName === metadata.migration.graphName, 'migrated graph name changed'); + assert(graph.checkpoint.status === 'upgraded', 'checkpoint was not upgraded'); + assert( + graph.checkpoint.previousSchema === metadata.migration.previousCheckpointSchema, + 'previous checkpoint schema changed', + ); + assert( + graph.checkpoint.currentSchema === metadata.migration.currentCheckpointSchema, + 'current checkpoint schema changed', + ); + + const legacyCheckpoint = metadata.refs.find( + ref => ref.name === 'refs/warp/gitmind/checkpoints/head', + ); + assert(legacyCheckpoint, 'fixture metadata missing legacy checkpoint ref'); + assert( + graph.checkpoint.previousCheckpointSha === legacyCheckpoint.oid, + 'migration did not start from the fixture checkpoint', + ); + assert( + graph.checkpoint.upgradedCheckpointSha !== legacyCheckpoint.oid, + 'migration did not write a new checkpoint', + ); +} + +async function assertGitMindStatus(metadata) { + const status = parseJson( + await run('git-mind', ['status', '--json'], { cwd: REPO_DIR }), + 'git-mind status', + ); + + assert(status.nodes.total === metadata.expected.status.nodes, 'node count changed'); + assert(status.edges.total === metadata.expected.status.edges, 'edge count changed'); + assert( + status.health.blockedItems === metadata.expected.status.blockedItems, + 'blocked item count changed', + ); + assert( + status.health.lowConfidence === metadata.expected.status.lowConfidence, + 'low-confidence count changed', + ); + assert( + status.health.orphanNodes === metadata.expected.status.orphanNodes, + 'orphan node count changed', + ); +} + +async function assertSentinelNodes(metadata) { + const nodes = parseJson( + await run('git-mind', ['nodes', '--json'], { cwd: REPO_DIR }), + 'git-mind nodes', + ); + + for (const node of metadata.expected.sentinelNodes) { + assert(nodes.nodes.includes(node), `missing sentinel node: ${node}`); + } +} + +async function assertExport(metadata) { + const exported = parseJson( + await run('git-mind', ['export', '--json'], { cwd: REPO_DIR }), + 'git-mind export', + ); + + assert(exported.nodes.length === metadata.expected.export.nodes, 'export node count changed'); + assert(exported.edges.length === metadata.expected.export.edges, 'export edge count changed'); +} + +async function assertContentBoundary() { + const content = parseJson( + await run('git-mind', ['content', 'meta', 'doc:ROADMAP', '--json'], { cwd: REPO_DIR }), + 'git-mind content meta', + ); + + assert(content.hasContent === false, 'expected doc:ROADMAP to have no attached content'); +} + +async function main() { + const metadata = parseJson(await readFile(METADATA_PATH, 'utf8'), 'fixture metadata'); + const initialHash = await sha256File(FIXTURE_PATH); + assert(initialHash === metadata.artifact.sha256, 'fixture checksum mismatch'); + + await rm(WORK_ROOT, { recursive: true, force: true }); + await mkdir(WORK_ROOT, { recursive: true }); + await mkdir('/tmp/home', { recursive: true }); + + await fetchFixtureRefs(metadata); + await run('git', ['-C', REPO_DIR, 'fsck', '--full']); + await assertFixtureRefs(metadata); + await runGitWarpUpgrade(metadata); + await assertGitMindStatus(metadata); + await assertSentinelNodes(metadata); + await assertExport(metadata); + await assertContentBoundary(); + + const finalHash = await sha256File(FIXTURE_PATH); + assert(finalHash === initialHash, 'fixture bundle changed during test'); + + console.log(JSON.stringify({ + fixture: metadata.name, + sourceCommit: metadata.source.commit, + nodes: metadata.expected.status.nodes, + edges: metadata.expected.status.edges, + result: 'ok', + }, null, 2)); +} + +main().catch(err => { + console.error(err.stack || err.message); + process.exitCode = 1; +}); diff --git a/src/graph.js b/src/graph.js index 43d5c74a..e3c09cad 100644 --- a/src/graph.js +++ b/src/graph.js @@ -3,20 +3,135 @@ * Wraps @git-stunts/git-warp graph operations for git-mind. */ -import WarpGraph, { GitGraphAdapter } from '@git-stunts/git-warp'; -import GitPlumbing, { ShellRunnerFactory } from '@git-stunts/plumbing'; -import { createHash, createHmac, timingSafeEqual } from 'node:crypto'; +import WarpGraph, { GitGraphAdapter, NodeCryptoAdapter } from '@git-stunts/git-warp'; +import GitPlumbing from '@git-stunts/plumbing'; import { resolve } from 'node:path'; -/** Minimal CryptoPort adapter using node:crypto. */ -const crypto = { - async hash(algo, data) { return createHash(algo).update(data).digest('hex'); }, - async hmac(algo, key, data) { return createHmac(algo, key).update(data).digest(); }, - timingSafeEqual(a, b) { return timingSafeEqual(a, b); }, -}; - const GRAPH_NAME = 'gitmind'; +function bind(owner, methodName) { + if (!owner || typeof owner[methodName] !== 'function') { + throw new Error(`git-warp surface missing ${methodName}()`); + } + return owner[methodName].bind(owner); +} + +function bindOptional(owner, methodName) { + if (owner && typeof owner[methodName] === 'function') { + return owner[methodName].bind(owner); + } + return async () => { + throw new Error(`git-warp surface missing ${methodName}()`); + }; +} + +function unwrapCore(graph) { + return typeof graph.core === 'function' ? graph.core() : graph; +} + +/** + * Provide the v14-shaped graph methods Git Mind currently uses while the + * substrate exposes those capabilities through v17 app/core/query surfaces. + * + * @param {import('@git-stunts/git-warp').default | Record} graph + * @returns {import('@git-stunts/git-warp').default} + */ +function compatGraph(graph) { + const core = unwrapCore(graph); + const query = graph.query && typeof graph.query === 'object' ? graph.query : core; + const patches = graph.patches && typeof graph.patches === 'object' ? graph.patches : core; + let materialized = false; + + async function ensureMaterialized() { + if (!materialized && typeof core.materialize === 'function') { + await core.materialize(); + materialized = true; + } + } + + function read(methodName) { + const fn = bind(query, methodName); + return async (...args) => { + await ensureMaterialized(); + return fn(...args); + }; + } + + function readOptional(methodName) { + const fn = query && typeof query[methodName] === 'function' + ? query[methodName].bind(query) + : null; + return async (...args) => { + if (!fn) { + throw new Error(`git-warp surface missing ${methodName}()`); + } + await ensureMaterialized(); + return fn(...args); + }; + } + + function markDirtyPatch(patch) { + if (!patch || typeof patch.commit !== 'function') return patch; + + return new Proxy(patch, { + get(target, prop, receiver) { + if (prop === 'commit') { + return async (...args) => { + const result = await target.commit(...args); + materialized = false; + return result; + }; + } + + const value = Reflect.get(target, prop, receiver); + return typeof value === 'function' ? value.bind(target) : value; + }, + }); + } + + return { + graphName: graph.graphName ?? core.graphName, + writerId: graph.writerId ?? core.writerId, + core: () => core, + + createPatch: async (...args) => { + await ensureMaterialized(); + return markDirtyPatch(await bind(patches, 'createPatch')(...args)); + }, + patch: async (...args) => { + const result = await bindOptional(patches, 'patch')(...args); + materialized = false; + return result; + }, + patchMany: async (...args) => { + const result = await bindOptional(patches, 'patchMany')(...args); + materialized = false; + return result; + }, + discoverTicks: bindOptional(patches, 'discoverTicks'), + + hasNode: read('hasNode'), + getNodeProps: read('getNodeProps'), + getEdgeProps: readOptional('getEdgeProps'), + getNodes: read('getNodes'), + getEdges: read('getEdges'), + getContentOid: readOptional('getContentOid'), + getContent: readOptional('getContent'), + getContentMeta: readOptional('getContentMeta'), + + materialize: async (...args) => { + const result = await bindOptional(core, 'materialize')(...args); + materialized = true; + return result; + }, + worldline: bindOptional(query, 'worldline'), + observer: async (...args) => { + await ensureMaterialized(); + return compatGraph(await bind(query, 'observer')(...args)); + }, + }; +} + /** * Initialize a new git-mind graph in a repository. * @param {string} repoPath - Path to the Git repository @@ -27,8 +142,7 @@ export async function initGraph(repoPath, opts = {}) { const cwd = resolve(repoPath); const writerId = opts.writerId ?? 'local'; - const runner = ShellRunnerFactory.create(); - const plumbing = new GitPlumbing({ cwd, runner }); + const plumbing = await GitPlumbing.createDefault({ cwd }); const persistence = new GitGraphAdapter({ plumbing }); const graph = await WarpGraph.open({ @@ -36,10 +150,10 @@ export async function initGraph(repoPath, opts = {}) { persistence, writerId, autoMaterialize: true, - crypto, + crypto: new NodeCryptoAdapter(), }); - return graph; + return compatGraph(graph); } /** diff --git a/src/review.js b/src/review.js index 84e90f0a..d6651e73 100644 --- a/src/review.js +++ b/src/review.js @@ -5,8 +5,7 @@ */ import { createHash } from 'node:crypto'; -import { isLowConfidence } from './validators.js'; -import { removeEdge, createEdge } from './edges.js'; +import { isLowConfidence, validateEdge } from './validators.js'; import { getProp } from './prop-bag.js'; const VALID_REVIEW_ACTIONS = new Set(['accept', 'reject', 'adjust', 'skip']); @@ -52,14 +51,12 @@ function makeDecisionId(source, target, type) { } /** - * Record a decision node in the graph. + * Add a decision node to a pending patch. * - * @param {import('@git-stunts/git-warp').default} graph + * @param {object} patch * @param {ReviewDecision} decision - * @returns {Promise} */ -async function recordDecision(graph, decision) { - const patch = await graph.createPatch(); +function addDecisionToPatch(patch, decision) { patch.addNode(decision.id); patch.setProperty(decision.id, 'action', decision.action); patch.setProperty(decision.id, 'source', decision.source); @@ -73,7 +70,6 @@ async function recordDecision(graph, decision) { if (decision.reviewer) { patch.setProperty(decision.id, 'reviewer', decision.reviewer); } - await patch.commit(); } /** @@ -136,12 +132,6 @@ export async function getPendingSuggestions(graph) { * @returns {Promise} */ export async function acceptSuggestion(graph, suggestion, opts = {}) { - // Update edge confidence to 1.0 and add reviewedAt - const patch = await graph.createPatch(); - patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'confidence', 1.0); - patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'reviewedAt', new Date().toISOString()); - await patch.commit(); - const decision = { id: makeDecisionId(suggestion.source, suggestion.target, suggestion.type), action: 'accept', @@ -154,7 +144,11 @@ export async function acceptSuggestion(graph, suggestion, opts = {}) { reviewer: opts.reviewer, }; - await recordDecision(graph, decision); + const patch = await graph.createPatch(); + patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'confidence', 1.0); + patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'reviewedAt', new Date().toISOString()); + addDecisionToPatch(patch, decision); + await patch.commit(); return decision; } @@ -167,8 +161,6 @@ export async function acceptSuggestion(graph, suggestion, opts = {}) { * @returns {Promise} */ export async function rejectSuggestion(graph, suggestion, opts = {}) { - await removeEdge(graph, suggestion.source, suggestion.target, suggestion.type); - const decision = { id: makeDecisionId(suggestion.source, suggestion.target, suggestion.type), action: 'reject', @@ -181,7 +173,10 @@ export async function rejectSuggestion(graph, suggestion, opts = {}) { reviewer: opts.reviewer, }; - await recordDecision(graph, decision); + const patch = await graph.createPatch(); + patch.removeEdge(suggestion.source, suggestion.target, suggestion.type); + addDecisionToPatch(patch, decision); + await patch.commit(); return decision; } @@ -197,32 +192,16 @@ export async function rejectSuggestion(graph, suggestion, opts = {}) { export async function adjustSuggestion(graph, original, adjustments = {}) { const newType = adjustments.type ?? original.type; const newConf = adjustments.confidence ?? original.confidence; - - // If type changed, create new edge first, then remove old (safer ordering) - if (newType !== original.type) { - await createEdge(graph, { - source: original.source, - target: original.target, - type: newType, - confidence: newConf, - rationale: adjustments.rationale ?? original.rationale, - }); - // Set reviewedAt on the new edge - const patch = await graph.createPatch(); - patch.setEdgeProperty(original.source, original.target, newType, 'reviewedAt', new Date().toISOString()); - await patch.commit(); - await removeEdge(graph, original.source, original.target, original.type); - } else { - // Update existing edge - const patch = await graph.createPatch(); - patch.setEdgeProperty(original.source, original.target, original.type, 'confidence', newConf); - if (adjustments.rationale) { - patch.setEdgeProperty(original.source, original.target, original.type, 'rationale', adjustments.rationale); - } - patch.setEdgeProperty(original.source, original.target, original.type, 'reviewedAt', new Date().toISOString()); - await patch.commit(); + const rationale = adjustments.rationale ?? original.rationale; + const validation = validateEdge(original.source, original.target, newType, newConf); + if (!validation.valid) { + throw new Error(validation.errors.join('; ')); + } + for (const warning of validation.warnings) { + console.warn(`[git-mind] ${warning}`); } + const reviewedAt = new Date().toISOString(); const decision = { id: makeDecisionId(original.source, original.target, original.type), action: 'adjust', @@ -230,12 +209,31 @@ export async function adjustSuggestion(graph, original, adjustments = {}) { target: original.target, edgeType: newType, confidence: newConf, - rationale: adjustments.rationale ?? original.rationale, + rationale, timestamp: Math.floor(Date.now() / 1000), reviewer: adjustments.reviewer, }; - await recordDecision(graph, decision); + const patch = await graph.createPatch(); + if (newType !== original.type) { + patch.addEdge(original.source, original.target, newType); + patch.setEdgeProperty(original.source, original.target, newType, 'confidence', newConf); + patch.setEdgeProperty(original.source, original.target, newType, 'createdAt', reviewedAt); + patch.setEdgeProperty(original.source, original.target, newType, 'reviewedAt', reviewedAt); + if (rationale) { + patch.setEdgeProperty(original.source, original.target, newType, 'rationale', rationale); + } + patch.removeEdge(original.source, original.target, original.type); + } else { + patch.setEdgeProperty(original.source, original.target, original.type, 'confidence', newConf); + if (adjustments.rationale) { + patch.setEdgeProperty(original.source, original.target, original.type, 'rationale', adjustments.rationale); + } + patch.setEdgeProperty(original.source, original.target, original.type, 'reviewedAt', reviewedAt); + } + + addDecisionToPatch(patch, decision); + await patch.commit(); return decision; } @@ -326,12 +324,35 @@ export async function batchDecision(graph, action, opts = {}) { const pending = await getPendingSuggestions(graph); const decisions = []; + if (pending.length === 0) { + return { processed: 0, decisions }; + } + + const patch = await graph.createPatch(); for (const suggestion of pending) { - const decision = action === 'accept' - ? await acceptSuggestion(graph, suggestion, opts) - : await rejectSuggestion(graph, suggestion, opts); + const decision = { + id: makeDecisionId(suggestion.source, suggestion.target, suggestion.type), + action, + source: suggestion.source, + target: suggestion.target, + edgeType: suggestion.type, + confidence: action === 'accept' ? 1.0 : suggestion.confidence, + rationale: suggestion.rationale, + timestamp: Math.floor(Date.now() / 1000), + reviewer: opts.reviewer, + }; + + if (action === 'accept') { + patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'confidence', 1.0); + patch.setEdgeProperty(suggestion.source, suggestion.target, suggestion.type, 'reviewedAt', new Date().toISOString()); + } else { + patch.removeEdge(suggestion.source, suggestion.target, suggestion.type); + } + + addDecisionToPatch(patch, decision); decisions.push(decision); } + await patch.commit(); return { processed: decisions.length, decisions }; } diff --git a/test/cli-flush.test.js b/test/cli-flush.test.js new file mode 100644 index 00000000..7581fae1 --- /dev/null +++ b/test/cli-flush.test.js @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { readFile } from 'node:fs/promises'; + +const cliUrl = new URL('../bin/git-mind.js', import.meta.url); + +async function loadFlushStream() { + const source = await readFile(cliUrl, 'utf8'); + const start = source.indexOf('async function flushStream'); + const endMarker = '\n}\n\nawait flushStream(process.stdout);'; + const end = source.indexOf(endMarker, start) + 3; + + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(3); + + const fnSource = source.slice(start, end); + return Function(`${fnSource}; return flushStream;`)(); +} + +function streamError(code) { + const err = Object.assign(new Error(code), { code }); + return { + writable: true, + destroyed: false, + write(_chunk, callback) { + callback(err); + }, + }; +} + +describe('CLI stream flushing', () => { + it('ignores broken pipe flush errors during shutdown', async () => { + const flushStream = await loadFlushStream(); + + await expect(flushStream(streamError('EPIPE'))).resolves.toBeUndefined(); + await expect(flushStream(streamError('ERR_STREAM_DESTROYED'))).resolves.toBeUndefined(); + }); + + it('still rejects unexpected flush errors', async () => { + const flushStream = await loadFlushStream(); + + await expect(flushStream(streamError('EIO'))).rejects.toMatchObject({ code: 'EIO' }); + }); +}); diff --git a/test/fixtures/upgrade/README.md b/test/fixtures/upgrade/README.md new file mode 100644 index 00000000..ad6ae8df --- /dev/null +++ b/test/fixtures/upgrade/README.md @@ -0,0 +1,50 @@ +# Upgrade Fixtures + +This directory contains frozen repository-shaped fixtures for dependency +upgrade tests. + +## gitmind-v5-warp14 + +`gitmind-v5-warp14.bundle` is a sanitized Git bundle captured from +`@neuroglyph/git-mind@5.0.0` on the `@git-stunts/git-warp` 14.x substrate. +It is intentionally Git-native rather than a raw working-directory tarball. + +The bundle includes: + +- `HEAD` +- `refs/warp/gitmind/checkpoints/head` +- `refs/warp/gitmind/writers/local` + +It intentionally excludes: + +- `node_modules` +- local Git config +- remotes +- hooks +- reflogs +- stash state +- host-specific paths + +The companion `gitmind-v5-warp14.fixture.json` records the expected object +IDs, artifact checksum, dependency versions, graph counts, and sentinel nodes. +The Docker upgrade harness first verifies these legacy refs, then runs the +git-warp v17 checkpoint migration inside the isolated container before reading +the graph with the package under test. + +## Regenerating + +Regenerate this fixture only when the intended frozen source state changes. +Do not regenerate it as part of ordinary dependency updates. + +```bash +git bundle create \ + test/fixtures/upgrade/gitmind-v5-warp14.bundle \ + HEAD \ + refs/warp/gitmind/checkpoints/head \ + refs/warp/gitmind/writers/local + +git bundle verify test/fixtures/upgrade/gitmind-v5-warp14.bundle +shasum -a 256 test/fixtures/upgrade/gitmind-v5-warp14.bundle +``` + +Update the metadata file in the same commit when the bundle changes. diff --git a/test/fixtures/upgrade/gitmind-v5-warp14.bundle b/test/fixtures/upgrade/gitmind-v5-warp14.bundle new file mode 100644 index 00000000..eb30d381 Binary files /dev/null and b/test/fixtures/upgrade/gitmind-v5-warp14.bundle differ diff --git a/test/fixtures/upgrade/gitmind-v5-warp14.fixture.json b/test/fixtures/upgrade/gitmind-v5-warp14.fixture.json new file mode 100644 index 00000000..257601a7 --- /dev/null +++ b/test/fixtures/upgrade/gitmind-v5-warp14.fixture.json @@ -0,0 +1,63 @@ +{ + "schemaVersion": 1, + "name": "gitmind-v5-warp14", + "description": "Sanitized Git Mind v5 repository snapshot for git-warp 14 to 17 upgrade testing.", + "source": { + "repository": "flyingrobots/git-mind", + "commit": "5ed15dca896a947cac5fd82f13a34e5feb112047", + "capturedAt": "2026-05-31T20:52:47Z", + "package": { + "name": "@neuroglyph/git-mind", + "version": "5.0.0" + }, + "dependencies": { + "@git-stunts/git-warp": "^14.16.2", + "@git-stunts/plumbing": "^2.8.0" + } + }, + "artifact": { + "path": "gitmind-v5-warp14.bundle", + "format": "git-bundle", + "bytes": 8093284, + "sha256": "1486b444ec5d21cf0cec3c48e724cdeeed46d21ed0335cc27145046e36423e34" + }, + "refs": [ + { + "name": "HEAD", + "oid": "5ed15dca896a947cac5fd82f13a34e5feb112047" + }, + { + "name": "refs/warp/gitmind/checkpoints/head", + "oid": "1b36910e74c5cfa91a67f619006125939082f165" + }, + { + "name": "refs/warp/gitmind/writers/local", + "oid": "ae32a25a6453dcb01079bdd4f8b984ebf36b2d4f" + } + ], + "migration": { + "graphName": "gitmind", + "previousCheckpointSchema": 2, + "currentCheckpointSchema": 5 + }, + "expected": { + "status": { + "nodes": 128, + "edges": 227, + "blockedItems": 46, + "lowConfidence": 0, + "orphanNodes": 6 + }, + "export": { + "nodes": 125, + "edges": 227 + }, + "sentinelNodes": [ + "doc:ROADMAP", + "file:src/graph.js", + "spec:warp-integration", + "task:learn-git-mind", + "epoch:561c33b" + ] + } +} diff --git a/test/graph.test.js b/test/graph.test.js index 709f573c..7b5bf43c 100644 --- a/test/graph.test.js +++ b/test/graph.test.js @@ -1,11 +1,13 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { mkdtemp, rm } from 'node:fs/promises'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { execSync } from 'node:child_process'; import { initGraph } from '../src/graph.js'; import { getProp } from '../src/prop-bag.js'; +const graphSourceUrl = new URL('../src/graph.js', import.meta.url); + describe('graph', () => { let tempDir; @@ -47,4 +49,37 @@ describe('graph', () => { const props = await graph2.getNodeProps('test-node'); expect(getProp(props, 'label')).toBe('hello'); }); + + it('observer returns a read-compatible filtered graph surface', async () => { + const graph = await initGraph(tempDir); + + const patch = await graph.createPatch(); + patch.addNode('task:one'); + patch.addNode('task:two'); + patch.addNode('spec:one'); + patch.setProperty('task:one', 'status', 'todo'); + patch.setProperty('task:one', 'secret', 'hidden'); + patch.addEdge('task:one', 'task:two', 'blocks'); + patch.addEdge('task:one', 'spec:one', 'implements'); + await patch.commit(); + + const observer = await graph.observer('tasks', { match: 'task:*', expose: ['status'] }); + const nodes = await observer.getNodes(); + const edges = await observer.getEdges(); + const props = await observer.getNodeProps('task:one'); + + expect(nodes.sort()).toEqual(['task:one', 'task:two']); + expect(edges.map(({ from, to, label }) => ({ from, to, label }))).toEqual([ + { from: 'task:one', to: 'task:two', label: 'blocks' }, + ]); + expect(getProp(props, 'status')).toBe('todo'); + expect(getProp(props, 'secret')).toBeUndefined(); + await expect(observer.getNodeProps('spec:one')).resolves.toBeNull(); + }); + + it('routes observer construction through the compatibility binding helper', async () => { + const source = await readFile(graphSourceUrl, 'utf8'); + + expect(source).toContain("return compatGraph(await bind(query, 'observer')(...args));"); + }); }); diff --git a/test/review.test.js b/test/review.test.js index 714162b8..8e7fdd58 100644 --- a/test/review.test.js +++ b/test/review.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { mkdtemp, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; @@ -133,6 +133,57 @@ describe('review', () => { expect(edges[0].props.reviewedAt).toBeTruthy(); }); + it('commits same-type adjustments and decision atomically', async () => { + await createEdge(graph, { source: 'task:a', target: 'spec:b', type: 'implements', confidence: 0.3 }); + + const createPatchSpy = vi.spyOn(graph, 'createPatch'); + const original = { source: 'task:a', target: 'spec:b', type: 'implements', confidence: 0.3 }; + await adjustSuggestion(graph, original, { confidence: 0.9, rationale: 'looks good' }); + + const createPatchCalls = createPatchSpy.mock.calls.length; + createPatchSpy.mockRestore(); + expect(createPatchCalls).toBe(1); + }); + + it('commits type changes and decision atomically', async () => { + await createEdge(graph, { source: 'task:a', target: 'spec:b', type: 'implements', confidence: 0.3 }); + + const createPatchSpy = vi.spyOn(graph, 'createPatch'); + const original = { + source: 'task:a', + target: 'spec:b', + type: 'implements', + confidence: 0.3, + rationale: 'weak relation', + }; + const decision = await adjustSuggestion(graph, original, { + type: 'augments', + confidence: 0.9, + rationale: 'better relation', + }); + + const createPatchCalls = createPatchSpy.mock.calls.length; + createPatchSpy.mockRestore(); + expect(createPatchCalls).toBe(1); + + expect(decision.edgeType).toBe('augments'); + + const oldEdges = await findEdges(graph, 'task:a', 'spec:b', 'implements'); + expect(oldEdges).toHaveLength(0); + + const newEdges = await findEdges(graph, 'task:a', 'spec:b', 'augments'); + expect(newEdges).toHaveLength(1); + expect(newEdges[0].props.confidence).toBe(0.9); + expect(newEdges[0].props.rationale).toBe('better relation'); + expect(newEdges[0].props.createdAt).toBeTruthy(); + expect(newEdges[0].props.reviewedAt).toBeTruthy(); + + const history = await getReviewHistory(graph); + expect(history).toHaveLength(1); + expect(history[0].action).toBe('adjust'); + expect(history[0].edgeType).toBe('augments'); + }); + // ── skipSuggestion ───────────────────────────────────────── it('returns decision without modifying graph', async () => { diff --git a/test/upgrade-fixture.test.js b/test/upgrade-fixture.test.js new file mode 100644 index 00000000..de029d94 --- /dev/null +++ b/test/upgrade-fixture.test.js @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; +import { readFile } from 'node:fs/promises'; + +const dockerfileUrl = new URL('../scripts/upgrade-fixture/Dockerfile', import.meta.url); +const fixtureScriptUrl = new URL('../scripts/test-upgrade-fixture.sh', import.meta.url); +const packageJsonUrl = new URL('../package.json', import.meta.url); +const packageLockUrl = new URL('../package-lock.json', import.meta.url); +const runnerUrl = new URL('../scripts/upgrade-fixture/run-upgrade-fixture.mjs', import.meta.url); + +describe('upgrade fixture harness', () => { + it('pins the Docker base image by digest', async () => { + const dockerfile = await readFile(dockerfileUrl, 'utf8'); + + expect(dockerfile).toMatch(/^FROM node:22-bookworm-slim@sha256:[a-f0-9]{64}$/m); + }); + + it('installs the packed package with the audited lockfile', async () => { + const dockerfile = await readFile(dockerfileUrl, 'utf8'); + const fixtureScript = await readFile(fixtureScriptUrl, 'utf8'); + const runner = await readFile(runnerUrl, 'utf8'); + + expect(fixtureScript).toContain( + 'cp "$repo_root/package-lock.json" "$context/artifacts/package-lock.json"', + ); + expect(dockerfile).toContain('COPY artifacts/package-lock.json /tmp/package-lock.json'); + expect(dockerfile).toContain('npm ci --omit=dev'); + expect(dockerfile).toContain('ENV GIT_MIND_PACKAGE_ROOT=/opt/git-mind'); + expect(runner).toContain('process.env.GIT_MIND_PACKAGE_ROOT'); + expect(dockerfile).not.toContain('npm install -g /tmp/git-mind-package.tgz'); + }); + + it('pins the audited substrate versions in the manifest and lockfile root', async () => { + const manifest = JSON.parse(await readFile(packageJsonUrl, 'utf8')); + const lockfile = JSON.parse(await readFile(packageLockUrl, 'utf8')); + const lockedRoot = lockfile.packages['']; + + expect(manifest.dependencies['@git-stunts/git-warp']).toBe('17.0.0'); + expect(manifest.dependencies['@git-stunts/plumbing']).toBe('3.0.3'); + expect(lockedRoot.dependencies['@git-stunts/git-warp']).toBe('17.0.0'); + expect(lockedRoot.dependencies['@git-stunts/plumbing']).toBe('3.0.3'); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index 86f2b6bf..64165257 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + maxWorkers: 4, + minWorkers: 1, + testTimeout: 30_000, poolOptions: { forks: { execArgv: ['--disable-warning=DEP0169'],