diff --git a/AGENTS.md b/AGENTS.md index c2016fd7..be4298ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,7 +89,7 @@ Canonical entities must enforce EF max-length caps and FK `Guid` validity at the # GitNexus — Code Intelligence -This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **PatchHound** (17719 symbols, 105103 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. @@ -101,19 +101,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela - When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. -## When Debugging - -1. `gitnexus_query({query: ""})` — find execution flows related to the issue -2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation -3. `READ gitnexus://repo/PatchHound/process/{processName}` — trace the full execution flow step by step -4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed - -## When Refactoring - -- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. -- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. -- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. - ## Never Do - NEVER edit a function, class, or method without first running `gitnexus_impact` on it. @@ -121,25 +108,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela - NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. - NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. -## Tools Quick Reference - -| Tool | When to use | Command | -|------|-------------|---------| -| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | -| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | -| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | -| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | -| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | -| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | - -## Impact Risk Levels - -| Depth | Meaning | Action | -|-------|---------|--------| -| d=1 | WILL BREAK — direct callers/importers | MUST update these | -| d=2 | LIKELY AFFECTED — indirect deps | Should test | -| d=3 | MAY NEED TESTING — transitive | Test if critical path | - ## Resources | Resource | Use for | @@ -149,32 +117,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela | `gitnexus://repo/PatchHound/processes` | All execution flows | | `gitnexus://repo/PatchHound/process/{name}` | Step-by-step execution trace | -## Self-Check Before Finishing - -Before completing any code modification task, verify: -1. `gitnexus_impact` was run for all modified symbols -2. No HIGH/CRITICAL risk warnings were ignored -3. `gitnexus_detect_changes()` confirms changes match expected scope -4. All d=1 (WILL BREAK) dependents were updated - -## Keeping the Index Fresh - -After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: - -```bash -npx gitnexus analyze -``` - -If the index previously included embeddings, preserve them by adding `--embeddings`: - -```bash -npx gitnexus analyze --embeddings -``` - -To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** - -> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. - ## CLI | Task | Read this skill file | diff --git a/CLAUDE.md b/CLAUDE.md index d2e7c64a..5c4f692c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,7 +93,7 @@ Dashboard open-exposure summaries should count only exposures on active, healthy # GitNexus — Code Intelligence -This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **PatchHound** (17719 symbols, 105103 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. @@ -105,19 +105,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela - When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. -## When Debugging - -1. `gitnexus_query({query: ""})` — find execution flows related to the issue -2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation -3. `READ gitnexus://repo/PatchHound/process/{processName}` — trace the full execution flow step by step -4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed - -## When Refactoring - -- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. -- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. -- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. - ## Never Do - NEVER edit a function, class, or method without first running `gitnexus_impact` on it. @@ -125,25 +112,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela - NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. - NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. -## Tools Quick Reference - -| Tool | When to use | Command | -|------|-------------|---------| -| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | -| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | -| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | -| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | -| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | -| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | - -## Impact Risk Levels - -| Depth | Meaning | Action | -|-------|---------|--------| -| d=1 | WILL BREAK — direct callers/importers | MUST update these | -| d=2 | LIKELY AFFECTED — indirect deps | Should test | -| d=3 | MAY NEED TESTING — transitive | Test if critical path | - ## Resources | Resource | Use for | @@ -153,32 +121,6 @@ This project is indexed by GitNexus as **PatchHound** (11438 symbols, 97109 rela | `gitnexus://repo/PatchHound/processes` | All execution flows | | `gitnexus://repo/PatchHound/process/{name}` | Step-by-step execution trace | -## Self-Check Before Finishing - -Before completing any code modification task, verify: -1. `gitnexus_impact` was run for all modified symbols -2. No HIGH/CRITICAL risk warnings were ignored -3. `gitnexus_detect_changes()` confirms changes match expected scope -4. All d=1 (WILL BREAK) dependents were updated - -## Keeping the Index Fresh - -After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: - -```bash -npx gitnexus analyze -``` - -If the index previously included embeddings, preserve them by adding `--embeddings`: - -```bash -npx gitnexus analyze --embeddings -``` - -To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** - -> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. - ## CLI | Task | Read this skill file | diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7e062c94..fe430e89 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4409,9 +4409,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.40.tgz", - "integrity": "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -5030,9 +5030,9 @@ } }, "node_modules/@tanstack/history": { - "version": "1.161.4", - "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.4.tgz", - "integrity": "sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz", + "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==", "license": "MIT", "engines": { "node": ">=20.19" @@ -5069,17 +5069,15 @@ } }, "node_modules/@tanstack/react-router": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.166.2.tgz", - "integrity": "sha512-pKhUtrvVLlhjWhsHkJSuIzh1J4LcP+8ErbIqRLORX9Js8dUFMKoT0+8oFpi+P8QRpuhm/7rzjYiWfcyTsqQZtA==", + "version": "1.170.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.8.tgz", + "integrity": "sha512-Qw2ju6jjnIsMpuW+VrnHZWHuugqs592PWsnI56sG28qNhg14CgRLahOcNajfuJR9P4MxKGP94WVzmFKSYUz/ig==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.4", - "@tanstack/react-store": "^0.9.1", - "@tanstack/router-core": "1.166.2", - "isbot": "^5.1.22", - "tiny-invariant": "^1.3.3", - "tiny-warning": "^1.0.3" + "@tanstack/history": "1.162.0", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "1.171.6", + "isbot": "^5.1.22" }, "engines": { "node": ">=20.19" @@ -5094,18 +5092,19 @@ } }, "node_modules/@tanstack/react-start": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.166.2.tgz", - "integrity": "sha512-ryeDIITTVmGmOkTrdg4dL4Sl+LXK5w8BZtzLtsr3YxNhQaPwxqX4r69iuBt5M8jyXEsWwbJJdToN3xLr7CO5XQ==", - "license": "MIT", - "dependencies": { - "@tanstack/react-router": "1.166.2", - "@tanstack/react-start-client": "1.166.2", - "@tanstack/react-start-server": "1.166.2", - "@tanstack/router-utils": "^1.161.4", - "@tanstack/start-client-core": "1.166.2", - "@tanstack/start-plugin-core": "1.166.2", - "@tanstack/start-server-core": "1.166.2", + "version": "1.168.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.168.13.tgz", + "integrity": "sha512-E2pHQ92NiND1/HiD5Ax71xFXxiRZ2reOfU5W4BqxUL5plap3p8xSw1c6L8Np1E60vsxknuPCYRZESKkRy/LkOA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.8", + "@tanstack/react-start-client": "1.168.4", + "@tanstack/react-start-rsc": "0.1.13", + "@tanstack/react-start-server": "1.167.9", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-plugin-core": "1.171.6", + "@tanstack/start-server-core": "1.169.4", "pathe": "^2.0.3" }, "engines": { @@ -5116,22 +5115,32 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { + "@rsbuild/core": "^2.0.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "vite": { + "optional": true + } } }, "node_modules/@tanstack/react-start-client": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.166.2.tgz", - "integrity": "sha512-Dlup62M5WV3LrLzLSiK8QTsYcSFKUlBffwP8evBzBAN/A4lDaTJat+EBqUxOYwR8gDVm1GR/H4O+7tg6ur2MBA==", + "version": "1.168.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.168.4.tgz", + "integrity": "sha512-PDJ7xEuUKrlBiQz2PrVN9pD2ErmWeFpckYW1WUE8JCAeVi8U7C6rQNTQe4hQxBhycRfRdD53M6UfdWdQODIxyg==", "license": "MIT", "dependencies": { - "@tanstack/react-router": "1.166.2", - "@tanstack/router-core": "1.166.2", - "@tanstack/start-client-core": "1.166.2", - "tiny-invariant": "^1.3.3", - "tiny-warning": "^1.0.3" + "@tanstack/react-router": "1.170.8", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4" }, "engines": { "node": ">=22.12.0" @@ -5145,17 +5154,60 @@ "react-dom": ">=18.0.0 || >=19.0.0" } }, + "node_modules/@tanstack/react-start-rsc": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-rsc/-/react-start-rsc-0.1.13.tgz", + "integrity": "sha512-nl5pKkxy1RnRxOLjy/c3g/RKdQSQYWzK5iuLlsRaO9TbLuMhQlNAn255xQgVXG56G9xCtDg8/nD0ZycxSlSkWA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.8", + "@tanstack/react-start-server": "1.167.9", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-plugin-core": "1.171.6", + "@tanstack/start-server-core": "1.169.4", + "@tanstack/start-storage-context": "1.167.8", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rspack/core": ">=2.0.0-0", + "@vitejs/plugin-rsc": ">=0.5.20", + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0", + "react-server-dom-rspack": ">=0.0.2" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "react-server-dom-rspack": { + "optional": true + } + } + }, "node_modules/@tanstack/react-start-server": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.166.2.tgz", - "integrity": "sha512-0E7gp/8YFeCa3y65xI0ubyI8IzQjSiWn9D2Uo0r0Izi4/jQkv4Ee+niE4NqPAohY5oyxmcoLhnDqEyo0MqHIpQ==", + "version": "1.167.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.167.9.tgz", + "integrity": "sha512-a1SGeeoIEg411vEN6DThB2Bm5tiYBb0tCC/RaG8BSjRVtsY6kxD9cP1+LOpZwjRSgfdyqtSbe1v78ZDB9z0/uw==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.4", - "@tanstack/react-router": "1.166.2", - "@tanstack/router-core": "1.166.2", - "@tanstack/start-client-core": "1.166.2", - "@tanstack/start-server-core": "1.166.2" + "@tanstack/history": "1.162.0", + "@tanstack/react-router": "1.170.8", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-server-core": "1.169.4" }, "engines": { "node": ">=22.12.0" @@ -5170,12 +5222,12 @@ } }, "node_modules/@tanstack/react-store": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.1.tgz", - "integrity": "sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", "license": "MIT", "dependencies": { - "@tanstack/store": "0.9.1", + "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "funding": { @@ -5208,18 +5260,15 @@ } }, "node_modules/@tanstack/router-core": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.166.2.tgz", - "integrity": "sha512-zn3NhENOAX9ToQiX077UV2OH3aJKOvV2ZMNZZxZ3gDG3i3WqL8NfWfEgetEAfMN37/Mnt90PpotYgf7IyuoKqQ==", + "version": "1.171.6", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.6.tgz", + "integrity": "sha512-Ol6DQ+j6rf/rPVELIzo8LHwOQV2KL+zry3b+39kL/GKrt7YId52WJRAFMzuseY4XceSW+PU7sG/Cc1QkwJr0hg==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.4", - "@tanstack/store": "^0.9.1", - "cookie-es": "^2.0.0", - "seroval": "^1.4.2", - "seroval-plugins": "^1.4.2", - "tiny-invariant": "^1.3.3", - "tiny-warning": "^1.0.3" + "@tanstack/history": "1.162.0", + "cookie-es": "^3.0.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4" }, "engines": { "node": ">=20.19" @@ -5230,19 +5279,19 @@ } }, "node_modules/@tanstack/router-generator": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.166.2.tgz", - "integrity": "sha512-wbvdyP1PKKQKk4aVlGeK9S5uDy8zodTr3tEZ2gRKNavJLusXbEWqtoo42JxHFFNB6dtguehFMt8PyZPAtkgWwQ==", + "version": "1.167.10", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.167.10.tgz", + "integrity": "sha512-CjbjWRSo6djLU/C7ncb9IbKUcf4IwpdqhLGngkwKkXaVFXGxEAafA/uhvOCv/UEUVR7NI3tJqqQmxYXGcJPbjw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.166.2", - "@tanstack/router-utils": "1.161.4", - "@tanstack/virtual-file-routes": "1.161.4", + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "jiti": "^2.7.0", + "magic-string": "^0.30.21", "prettier": "^3.5.0", - "recast": "^0.23.11", - "source-map": "^0.7.4", - "tsx": "^4.19.2", - "zod": "^3.24.2" + "zod": "^4.4.3" }, "engines": { "node": ">=20.19" @@ -5252,19 +5301,10 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@tanstack/router-generator/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@tanstack/router-plugin": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.166.2.tgz", - "integrity": "sha512-TnyV/7//Vp5fR49mmNbOWHGz9IJTm1lqVxzPdtpzg7D5PjkW2HFmLFLtWwpJgz2R7AJJWR4Ge5kIPmC+fVZ6eQ==", + "version": "1.168.11", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.168.11.tgz", + "integrity": "sha512-b2eom/8xCWL/OiWxKub8kYsr8p+kvmB/eXwYGqCWG8vilcJo+eQCSyp54nKt0AZ5k/ET1+eINc+4mwL3bVeAgg==", "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -5273,13 +5313,13 @@ "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", - "@tanstack/router-core": "1.166.2", - "@tanstack/router-generator": "1.166.2", - "@tanstack/router-utils": "1.161.4", - "@tanstack/virtual-file-routes": "1.161.4", - "chokidar": "^3.6.0", - "unplugin": "^2.1.2", - "zod": "^3.24.2" + "@tanstack/router-core": "1.171.6", + "@tanstack/router-generator": "1.167.10", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "chokidar": "^5.0.0", + "unplugin": "^3.0.0", + "zod": "^4.4.3" }, "engines": { "node": ">=20.19" @@ -5289,10 +5329,10 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@rsbuild/core": ">=1.0.2", - "@tanstack/react-router": "^1.166.2", - "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", - "vite-plugin-solid": "^2.11.10", + "@rsbuild/core": ">=1.0.2 || ^2.0.0", + "@tanstack/react-router": "^1.170.8", + "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", + "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "peerDependenciesMeta": { @@ -5313,67 +5353,10 @@ } } }, - "node_modules/@tanstack/router-plugin/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@tanstack/router-plugin/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@tanstack/router-plugin/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@tanstack/router-plugin/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@tanstack/router-utils": { - "version": "1.161.4", - "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.4.tgz", - "integrity": "sha512-r8TpjyIZoqrXXaf2DDyjd44gjGBoyE+/oEaaH68yLI9ySPO1gUWmQENZ1MZnmBnpUGN24NOZxdjDLc8npK0SAw==", + "version": "1.162.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.162.1.tgz", + "integrity": "sha512-62layyTGmclHDQS/eidwKRfN1hhCKwViG7iEBcVmL0MXgcAB3OOucWCEcDDGd9Cu11H6b4QQ5oOo47MWIqwz0A==", "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -5395,17 +5378,15 @@ } }, "node_modules/@tanstack/start-client-core": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.166.2.tgz", - "integrity": "sha512-weADfq6bBWgib7Tf7J+P5zF7LHfUm8UuMhijwTfYjaDQ5za04N4PQk5msw2+VchUnSL44aQUA0WeOmoEkG1KMQ==", + "version": "1.170.4", + "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.170.4.tgz", + "integrity": "sha512-j/Deupf0zR7P5QObN38xTHufCRZkWTb6a/7aauu8eBmzOzDVggvuEdYHRZWiwJ9HRKbR2/SIJASVKeTtj1OcWw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.166.2", - "@tanstack/start-fn-stubs": "1.161.4", - "@tanstack/start-storage-context": "1.166.2", - "seroval": "^1.4.2", - "tiny-invariant": "^1.3.3", - "tiny-warning": "^1.0.3" + "@tanstack/router-core": "1.171.6", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-storage-context": "1.167.8", + "seroval": "^1.5.4" }, "engines": { "node": ">=22.12.0" @@ -5416,9 +5397,9 @@ } }, "node_modules/@tanstack/start-fn-stubs": { - "version": "1.161.4", - "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.161.4.tgz", - "integrity": "sha512-b8s6iSQ+ny0P4lGK0n3DKaL6EI7SECG0/89svDeYieVw2+MaFOJVcQo3rU3BUvmuOcIkgkE5IhdzkmzPXH6yfA==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.162.0.tgz", + "integrity": "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==", "license": "MIT", "engines": { "node": ">=22.12.0" @@ -5429,32 +5410,33 @@ } }, "node_modules/@tanstack/start-plugin-core": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.166.2.tgz", - "integrity": "sha512-geJE4Y/IQ3ZDRB/AahCoUuLliK4bYREcWVF1ViJfD1qCjsQlXMhDSruvswiMWMTxKfMuzX/XuIJWhmtndxymjQ==", + "version": "1.171.6", + "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.171.6.tgz", + "integrity": "sha512-e0AUN+omib0qLgs0r3zoKRSeHEkwL8qs8skvbl8zgDQXw9zF73K7ZXE7QarSzbqfLAiehVqlv0iPETp8ogUftQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", - "@rolldown/pluginutils": "1.0.0-beta.40", - "@tanstack/router-core": "1.166.2", - "@tanstack/router-generator": "1.166.2", - "@tanstack/router-plugin": "1.166.2", - "@tanstack/router-utils": "1.161.4", - "@tanstack/start-client-core": "1.166.2", - "@tanstack/start-server-core": "1.166.2", - "cheerio": "^1.0.0", + "@rolldown/pluginutils": "1.0.1", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-generator": "1.167.10", + "@tanstack/router-plugin": "1.168.11", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-server-core": "1.169.4", "exsolve": "^1.0.7", + "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", + "seroval": "^1.5.4", "source-map": "^0.7.6", - "srvx": "^0.11.7", + "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", - "zod": "^3.24.2" + "zod": "^4.4.3" }, "engines": { "node": ">=22.12.0" @@ -5464,7 +5446,16 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { + "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } } }, "node_modules/@tanstack/start-plugin-core/node_modules/@babel/code-frame": { @@ -5481,28 +5472,280 @@ "node": ">=6.9.0" } }, - "node_modules/@tanstack/start-plugin-core/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, "funding": { - "url": "https://github.com/sponsors/colinhacks" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/@tanstack/start-server-core": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.166.2.tgz", - "integrity": "sha512-9BDZsaLyHVux5tJRRBRYa2xW2jUaKr4PbJkTCSSOnAByOGUVJy7N+790/Q1Kq/LVud+0h42vZHWSRDDywfnedQ==", + "version": "1.169.4", + "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.169.4.tgz", + "integrity": "sha512-iM3HamWRQPROuAb+22frV/+GkqG2a3rL0X14N+Y0Dt5OajrIumPuprOn9ldUXsbdg89RTBf1KoJNDPeYGOqH4g==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.4", - "@tanstack/router-core": "1.166.2", - "@tanstack/start-client-core": "1.166.2", - "@tanstack/start-storage-context": "1.166.2", - "h3-v2": "npm:h3@2.0.1-rc.14", - "seroval": "^1.4.2", - "tiny-invariant": "^1.3.3" + "@tanstack/history": "1.162.0", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-storage-context": "1.167.8", + "fetchdts": "^0.1.6", + "h3-v2": "npm:h3@2.0.1-rc.20", + "seroval": "^1.5.4" }, "engines": { "node": ">=22.12.0" @@ -5513,12 +5756,12 @@ } }, "node_modules/@tanstack/start-storage-context": { - "version": "1.166.2", - "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.166.2.tgz", - "integrity": "sha512-c3QPApFAhiDXDZ/zLvop5InErqCrawWuO751FpItGnelOlpOAPMw5/h//1u/RnIcOv2l/ffDBCbp+N09eFPhaA==", + "version": "1.167.8", + "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.167.8.tgz", + "integrity": "sha512-y9T+bIIp1ihLAXyS2+r+UovSupfu4KydSXpnoeRsw/14/E0huJsX7xB/n6XXOdmDYAaJ2WGOrG9wYjzeIDuBAw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.166.2" + "@tanstack/router-core": "1.171.6" }, "engines": { "node": ">=22.12.0" @@ -5529,9 +5772,9 @@ } }, "node_modules/@tanstack/store": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.1.tgz", - "integrity": "sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", "license": "MIT", "funding": { "type": "github", @@ -5552,9 +5795,9 @@ } }, "node_modules/@tanstack/virtual-file-routes": { - "version": "1.161.4", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.161.4.tgz", - "integrity": "sha512-42WoRePf8v690qG8yGRe/YOh+oHni9vUaUUfoqlS91U2scd3a5rkLtVsc6b7z60w3RogH0I00vdrC5AaeiZ18w==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.162.0.tgz", + "integrity": "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==", "license": "MIT", "engines": { "node": ">=20.19" @@ -6126,9 +6369,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "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": { @@ -6540,31 +6783,6 @@ "node": ">=14" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6603,18 +6821,6 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/babel-dead-code-elimination": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", @@ -6686,28 +6892,10 @@ "require-from-string": "^2.0.2" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, "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.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6715,18 +6903,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -6913,53 +7089,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -7106,9 +7239,9 @@ } }, "node_modules/cookie-es": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", - "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", "license": "MIT" }, "node_modules/crelt": { @@ -7156,46 +7289,18 @@ } } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css.escape": { @@ -7549,7 +7654,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7591,61 +7695,6 @@ "license": "MIT", "peer": true }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -7695,19 +7744,6 @@ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "license": "ISC" }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, "node_modules/enhanced-resolve": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", @@ -7722,18 +7758,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -7795,6 +7819,7 @@ "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -8026,19 +8051,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -8217,6 +8229,12 @@ } } }, + "node_modules/fetchdts": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/fetchdts/-/fetchdts-0.1.7.tgz", + "integrity": "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -8230,18 +8248,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8320,30 +8326,6 @@ "node": ">=6" } }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/globals": { "version": "17.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", @@ -8398,13 +8380,13 @@ }, "node_modules/h3-v2": { "name": "h3", - "version": "2.0.1-rc.14", - "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.14.tgz", - "integrity": "sha512-163qbGmTr/9rqQRNuqMqtgXnOUAkE4KTdauiC9y0E5iG1I65kte9NyfWvZw5RTDMt6eY+DtyoNzrQ9wA2BfvGQ==", + "version": "2.0.1-rc.20", + "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.20.tgz", + "integrity": "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==", "license": "MIT", "dependencies": { - "rou3": "^0.7.12", - "srvx": "^0.11.2" + "rou3": "^0.8.1", + "srvx": "^0.11.13" }, "bin": { "h3": "bin/h3.mjs" @@ -8421,13 +8403,6 @@ } } }, - "node_modules/h3/node_modules/rou3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.8.1.tgz", - "integrity": "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8525,37 +8500,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -8584,18 +8528,6 @@ "node": ">= 14" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8743,18 +8675,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -8769,6 +8689,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8778,6 +8699,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -8796,15 +8718,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -8852,10 +8765,9 @@ } }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -9495,7 +9407,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -10694,6 +10605,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "devOptional": true, "funding": [ { "type": "github", @@ -10893,27 +10805,6 @@ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/oauth4webapi": { "version": "3.8.5", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", @@ -11064,55 +10955,6 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11250,6 +11092,7 @@ "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -11687,7 +11530,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -11697,31 +11539,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/recast/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/recharts": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz", @@ -11873,15 +11690,6 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/rolldown": { "version": "1.0.0-rc.7", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.7.tgz", @@ -11927,6 +11735,7 @@ "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "devOptional": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -11968,9 +11777,9 @@ } }, "node_modules/rou3": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", - "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.8.1.tgz", + "integrity": "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==", "license": "MIT" }, "node_modules/sade": { @@ -12005,12 +11814,6 @@ ], "license": "MIT" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -12040,18 +11843,18 @@ } }, "node_modules/seroval": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz", - "integrity": "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", + "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/seroval-plugins": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.0.tgz", - "integrity": "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", + "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", "license": "MIT", "engines": { "node": ">=10" @@ -12113,6 +11916,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12307,12 +12111,6 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -12376,18 +12174,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -12474,25 +12260,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -12560,9 +12327,9 @@ } }, "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/uncrypto": { @@ -12575,6 +12342,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -12716,18 +12484,17 @@ } }, "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=18.12.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/update-browserslist-db": { @@ -12903,6 +12670,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", @@ -12994,9 +12762,9 @@ } }, "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", "license": "MIT", "workspaces": [ "tests/deps/*", @@ -13004,7 +12772,7 @@ "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "vite": { @@ -13125,28 +12893,6 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-url": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", @@ -13284,9 +13030,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "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" diff --git a/frontend/src/api/remediation.functions.ts b/frontend/src/api/remediation.functions.ts index 7b1e2a8d..69e50294 100644 --- a/frontend/src/api/remediation.functions.ts +++ b/frontend/src/api/remediation.functions.ts @@ -4,6 +4,7 @@ import { authMiddleware } from '@/server/middleware' import { apiGet, apiPost } from '@/server/api' import { analystRecommendationSchema, + aiRecommendationDraftSchema, decisionContextSchema, pagedDecisionListSchema, remediationDecisionSchema, @@ -126,6 +127,14 @@ export const generateThreatIntel = createServerFn({ method: 'POST' }) return threatIntelSchema.parse(data) }) +export const generateAiRecommendationDraft = createServerFn({ method: 'POST' }) + .middleware([authMiddleware]) + .inputValidator(z.object({ caseId: z.string().uuid() })) + .handler(async ({ context, data: { caseId } }) => { + const data = await apiPost(`/remediation/cases/${caseId}/analysis/ai-recommendation`, context, {}) + return aiRecommendationDraftSchema.parse(data) + }) + export const fetchDecisionList = createServerFn({ method: 'GET' }) .middleware([authMiddleware]) .inputValidator( diff --git a/frontend/src/api/remediation.schemas.ts b/frontend/src/api/remediation.schemas.ts index 2789ac22..3c8c2145 100644 --- a/frontend/src/api/remediation.schemas.ts +++ b/frontend/src/api/remediation.schemas.ts @@ -144,6 +144,12 @@ export const threatIntelSchema = z.object({ unavailableMessage: z.string().nullable(), }) +export const aiRecommendationDraftSchema = z.object({ + recommendedOutcome: z.string(), + priorityOverride: z.string(), + rationale: z.string(), +}) + export const patchAssessmentSchema = z.object({ vulnerabilityId: z.string().uuid().nullable(), recommendation: z.string().nullable(), @@ -246,6 +252,7 @@ export type PatchAssessment = z.infer export type DecisionApprovalResolution = z.infer export type VulnerabilityOverride = z.infer export type ThreatIntel = z.infer +export type AiRecommendationDraft = z.infer export type DecisionListItem = z.infer export type DecisionListSummary = z.infer export type PagedDecisionList = z.infer diff --git a/frontend/src/components/features/remediation/SecurityAnalystWorkbench.test.tsx b/frontend/src/components/features/remediation/SecurityAnalystWorkbench.test.tsx index 13285e76..e6e4b399 100644 --- a/frontend/src/components/features/remediation/SecurityAnalystWorkbench.test.tsx +++ b/frontend/src/components/features/remediation/SecurityAnalystWorkbench.test.tsx @@ -1,8 +1,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' import type { AnchorHTMLAttributes, TextareaHTMLAttributes } from 'react' import type { DecisionContext } from '@/api/remediation.schemas' +import { addRecommendation, generateAiRecommendationDraft } from '@/api/remediation.functions' import { requestVulnerabilityAssessment } from '@/api/vulnerabilities.functions' import { SecurityAnalystWorkbench } from './SecurityAnalystWorkbench' @@ -12,6 +13,7 @@ vi.mock('@tanstack/react-router', () => ({ vi.mock('@/api/remediation.functions', () => ({ addRecommendation: vi.fn(), + generateAiRecommendationDraft: vi.fn(), })) vi.mock('@/api/vulnerabilities.functions', () => ({ @@ -418,4 +420,62 @@ describe('SecurityAnalystWorkbench', () => { expect(screen.getByText(/The AI response was not valid JSON/i)).toBeInTheDocument() expect(screen.getByText(error)).toBeInTheDocument() }) + + it('only shows the AI recommendation button when task vulnerabilities have assessments', () => { + const { rerender } = renderWorkbench({ + ...dataFixture, + patchAssessment: { + ...dataFixture.patchAssessment, + recommendation: null, + }, + patchAssessments: dataFixture.patchAssessments.map((assessment) => ({ + ...assessment, + recommendation: null, + assessedAt: null, + urgencyReason: null, + urgencyTier: null, + })), + }) + + expect(screen.queryByRole('button', { name: /Apply AI Recommendation/i })).not.toBeInTheDocument() + + rerender( + + + , + ) + + expect(screen.getByRole('button', { name: /Apply AI Recommendation/i })).toBeInTheDocument() + }) + + it('fills recommendation fields from the AI draft without submitting', async () => { + vi.mocked(generateAiRecommendationDraft).mockResolvedValue({ + recommendedOutcome: 'ApprovedForPatching', + priorityOverride: 'High', + rationale: 'Prioritize patching because the assessed SLA is 14 days and exposure remains open.', + }) + renderWorkbench() + + fireEvent.click(screen.getByRole('button', { name: /Apply AI Recommendation/i })) + + expect(screen.getByText('Work in progress')).toBeInTheDocument() + + await waitFor(() => { + expect(screen.queryByText('Work in progress')).not.toBeInTheDocument() + }) + + expect(generateAiRecommendationDraft).toHaveBeenCalledWith({ + data: { caseId: dataFixture.remediationCaseId }, + }) + expect(screen.getByLabelText(/Recommendation rationale/i)).toHaveValue( + 'Prioritize patching because the assessed SLA is 14 days and exposure remains open.', + ) + expect(screen.getByRole('combobox', { name: /Recommended remediation/i })).toHaveTextContent('Patch this software') + expect(screen.getByRole('combobox', { name: /Priority/i })).toHaveTextContent('High') + expect(addRecommendation).not.toHaveBeenCalled() + }) }) diff --git a/frontend/src/components/features/remediation/SecurityAnalystWorkbench.tsx b/frontend/src/components/features/remediation/SecurityAnalystWorkbench.tsx index c7df8201..fac52a66 100644 --- a/frontend/src/components/features/remediation/SecurityAnalystWorkbench.tsx +++ b/frontend/src/components/features/remediation/SecurityAnalystWorkbench.tsx @@ -1,9 +1,9 @@ import { useMemo, useState, type ReactNode } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' -import { ExternalLink, LoaderCircle, NotebookPen, Pencil, Save, SearchCheck, ShieldAlert, Trash2 } from 'lucide-react' +import { ExternalLink, LoaderCircle, NotebookPen, Pencil, Save, SearchCheck, ShieldAlert, Sparkles, Trash2 } from 'lucide-react' import type { DecisionContext, DecisionVuln } from '@/api/remediation.schemas' -import { addRecommendation } from '@/api/remediation.functions' +import { addRecommendation, generateAiRecommendationDraft } from '@/api/remediation.functions' import { requestVulnerabilityAssessment } from '@/api/vulnerabilities.functions' import { createWorkNote, deleteWorkNote, fetchWorkNotes, updateWorkNote } from '@/api/work-notes.functions' import type { WorkNote } from '@/api/work-notes.schemas' @@ -20,6 +20,13 @@ import { } from '@/components/ui/select' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet' import { Textarea } from '@/components/ui/textarea' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' import { useTenantScope } from '@/components/layout/tenant-scope' import { getApiErrorMessage } from '@/lib/api-errors' import { formatDateTime, formatNullableDateTime, startCase } from '@/lib/formatting' @@ -55,6 +62,7 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna const [rationale, setRationale] = useState(currentRecommendation?.rationale ?? '') const [priorityOverride, setPriorityOverride] = useState(currentRecommendation?.priorityOverride ?? '') const [isSaving, setIsSaving] = useState(false) + const [isApplyingAiRecommendation, setIsApplyingAiRecommendation] = useState(false) const [requestingAssessment, setRequestingAssessment] = useState(false) const [requestingAssessmentIds, setRequestingAssessmentIds] = useState([]) const [error, setError] = useState(null) @@ -91,6 +99,17 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna highestRiskDriverDetail, threatDriverCount, }) + const assessedTaskVulnerabilityIds = useMemo(() => { + const taskVulnerabilityIds = new Set(vulnerabilities.map((vulnerability) => vulnerability.vulnerabilityId)) + return data.patchAssessments + .filter((assessment) => + assessment.vulnerabilityId + && taskVulnerabilityIds.has(assessment.vulnerabilityId) + && (assessment.assessedAt || assessment.recommendation || assessment.urgencyReason) + ) + .map((assessment) => assessment.vulnerabilityId) + }, [data.patchAssessments, vulnerabilities]) + const canApplyAiRecommendation = assessedTaskVulnerabilityIds.length > 0 async function handleSaveRecommendation() { if (!canSave) return @@ -141,6 +160,22 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna } } + async function handleApplyAiRecommendation() { + if (!canApplyAiRecommendation || isApplyingAiRecommendation) return + setIsApplyingAiRecommendation(true) + setError(null) + try { + const draft = await generateAiRecommendationDraft({ data: { caseId } }) + setRecommendedOutcome(draft.recommendedOutcome) + setPriorityOverride(draft.priorityOverride) + setRationale(draft.rationale) + } catch (err) { + setError(getApiErrorMessage(err, 'Unable to apply the AI recommendation draft.')) + } finally { + setIsApplyingAiRecommendation(false) + } + } + return (
@@ -240,7 +275,9 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna onValueChange={(value) => setRecommendedOutcome(value ?? "")} > - + + {recommendedOutcome ? outcomeLabel(recommendedOutcome) : undefined} + {OUTCOMES.map((outcome) => ( @@ -265,7 +302,9 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna } > - + + {priorityOverride || undefined} + No priority @@ -295,6 +334,21 @@ export function SecurityAnalystWorkbench({ data, caseId, queryKey }: SecurityAna
+ {canApplyAiRecommendation ? ( + + ) : null}
); } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..da22c189 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PatchHound", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/src/PatchHound.Api/Controllers/RemediationDecisionsController.cs b/src/PatchHound.Api/Controllers/RemediationDecisionsController.cs index dc393dd4..73e193a1 100644 --- a/src/PatchHound.Api/Controllers/RemediationDecisionsController.cs +++ b/src/PatchHound.Api/Controllers/RemediationDecisionsController.cs @@ -25,6 +25,7 @@ public class RemediationDecisionsController( RemediationWorkflowAuthorizationService workflowAuthorizationService, RemediationWorkflowService workflowService, ThreatIntelGenerationService threatIntelService, + AiRecommendationDraftService aiRecommendationDraftService, PatchHoundDbContext dbContext, ITenantContext tenantContext ) : ControllerBase @@ -417,6 +418,27 @@ CancellationToken ct return Ok(result.Value); } + [HttpPost("analysis/ai-recommendation")] + [Authorize(Policy = Policies.GenerateAiReports)] + public async Task> GenerateAiRecommendationDraft( + Guid caseId, + CancellationToken ct + ) + { + if (tenantContext.CurrentTenantId is not Guid tenantId) + return BadRequest(new ProblemDetails { Title = "No active tenant is selected." }); + + var result = await aiRecommendationDraftService.GenerateAsync(tenantId, caseId, ct); + if (!result.IsSuccess) + { + if (result.Error == "Remediation case not found.") + return NotFound(new ProblemDetails { Title = result.Error }); + return BadRequest(new ProblemDetails { Title = result.Error }); + } + + return Ok(result.Value); + } + [HttpGet("audit-trail")] [Authorize(Policy = Policies.ViewVulnerabilities)] public async Task>> GetAuditTrail( diff --git a/src/PatchHound.Api/Models/Decisions/RemediationDecisionDto.cs b/src/PatchHound.Api/Models/Decisions/RemediationDecisionDto.cs index 307dd227..abd500e8 100644 --- a/src/PatchHound.Api/Models/Decisions/RemediationDecisionDto.cs +++ b/src/PatchHound.Api/Models/Decisions/RemediationDecisionDto.cs @@ -53,6 +53,12 @@ public record ThreatIntelDto( string? UnavailableMessage ); +public record AiRecommendationDraftDto( + string RecommendedOutcome, + string PriorityOverride, + string Rationale +); + public record DecisionSummaryDto( int TotalVulnerabilities, int CriticalCount, diff --git a/src/PatchHound.Api/Program.cs b/src/PatchHound.Api/Program.cs index f7514f9f..2c8d30b1 100644 --- a/src/PatchHound.Api/Program.cs +++ b/src/PatchHound.Api/Program.cs @@ -420,6 +420,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/PatchHound.Api/Services/AiRecommendationDraftService.cs b/src/PatchHound.Api/Services/AiRecommendationDraftService.cs new file mode 100644 index 00000000..23c6f114 --- /dev/null +++ b/src/PatchHound.Api/Services/AiRecommendationDraftService.cs @@ -0,0 +1,188 @@ +using System.Text; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using PatchHound.Api.Models.Decisions; +using PatchHound.Core.Common; +using PatchHound.Core.Constants; +using PatchHound.Core.Enums; +using PatchHound.Core.Models; +using PatchHound.Core.Services; +using PatchHound.Infrastructure.Data; + +namespace PatchHound.Api.Services; + +public class AiRecommendationDraftService( + PatchHoundDbContext dbContext, + TenantAiTextGenerationService aiTextGenerationService +) +{ + private const int MaxPatchAssessmentsForPrompt = 25; + + private const string SystemPrompt = + "You are helping a security analyst draft a remediation recommendation. " + + "Use only the supplied patch priority assessments and SLA context. " + + "Summarize the urgency reasons, then choose the remediation outcome and priority. " + + "Return only valid JSON with properties: recommendedOutcome, priorityOverride, rationale. " + + "recommendedOutcome must be one of ApprovedForPatching, RiskAcceptance, AlternateMitigation, PatchingDeferred. " + + "priorityOverride must be one of Critical, High, Medium, Low. " + + "The rationale should be concise markdown suitable for an analyst to edit before saving."; + + private static readonly HashSet SupportedOutcomes = new(StringComparer.OrdinalIgnoreCase) + { + RemediationOutcome.ApprovedForPatching.ToString(), + RemediationOutcome.RiskAcceptance.ToString(), + RemediationOutcome.AlternateMitigation.ToString(), + RemediationOutcome.PatchingDeferred.ToString(), + }; + + private static readonly HashSet SupportedPriorities = new(StringComparer.OrdinalIgnoreCase) + { + "Critical", + "High", + "Medium", + "Low", + }; + + public async Task> GenerateAsync( + Guid tenantId, + Guid caseId, + CancellationToken ct) + { + var case_ = await dbContext.RemediationCases.AsNoTracking() + .FirstOrDefaultAsync(item => item.TenantId == tenantId && item.Id == caseId, ct); + if (case_ is null) + return Result.Failure("Remediation case not found."); + + var softwareName = await dbContext.SoftwareProducts.AsNoTracking() + .Where(product => product.Id == case_.SoftwareProductId) + .Select(product => $"{product.Vendor} {product.Name}".Trim()) + .FirstOrDefaultAsync(ct) ?? "Unknown software"; + + var openVulnerabilityIds = await dbContext.DeviceVulnerabilityExposures.AsNoTracking() + .Where(exposure => + exposure.TenantId == tenantId + && exposure.SoftwareProductId == case_.SoftwareProductId + && exposure.Status == ExposureStatus.Open) + .Select(exposure => exposure.VulnerabilityId) + .Distinct() + .ToListAsync(ct); + + if (openVulnerabilityIds.Count == 0) + return Result.Failure("No open vulnerabilities found for this remediation case."); + + var rankedAssessments = dbContext.VulnerabilityPatchAssessments.AsNoTracking() + .Where(assessment => openVulnerabilityIds.Contains(assessment.VulnerabilityId)) + .Join( + dbContext.Vulnerabilities.AsNoTracking(), + assessment => assessment.VulnerabilityId, + vulnerability => vulnerability.Id, + (assessment, vulnerability) => new + { + vulnerability.ExternalId, + vulnerability.Title, + vulnerability.VendorSeverity, + vulnerability.CvssScore, + assessment.Recommendation, + assessment.Confidence, + assessment.Summary, + assessment.UrgencyTier, + assessment.UrgencyTargetSla, + assessment.UrgencyReason, + assessment.AssessedAt, + }) + .OrderByDescending(item => item.UrgencyTier == PatchUrgencyTier.Emergency) + .ThenByDescending(item => item.UrgencyTier == PatchUrgencyTier.AsSoonAsPossible) + .ThenByDescending(item => item.UrgencyTier == PatchUrgencyTier.NormalPatchWindow) + .ThenByDescending(item => item.CvssScore) + .ThenByDescending(item => item.AssessedAt); + + var totalAssessments = await rankedAssessments.CountAsync(ct); + var assessments = await rankedAssessments + .Take(MaxPatchAssessmentsForPrompt) + .ToListAsync(ct); + + if (assessments.Count == 0) + return Result.Failure("No patch assessments are available for open vulnerabilities in this remediation case."); + + var prompt = new StringBuilder(); + prompt.AppendLine($"Software product: {softwareName}"); + prompt.AppendLine(); + prompt.AppendLine("Patch assessments:"); + if (totalAssessments > assessments.Count) + prompt.AppendLine($"Only the top {assessments.Count} of {totalAssessments} assessments are included, ranked by urgency and CVSS."); + + foreach (var assessment in assessments) + { + prompt.AppendLine($"- {assessment.ExternalId} ({assessment.VendorSeverity}, CVSS {assessment.CvssScore?.ToString("F1") ?? "N/A"}): {assessment.Title}"); + prompt.AppendLine($" Urgency tier: {assessment.UrgencyTier}"); + prompt.AppendLine($" Target SLA: {assessment.UrgencyTargetSla}"); + prompt.AppendLine($" Urgency reason: {assessment.UrgencyReason}"); + prompt.AppendLine($" Recommendation: {assessment.Recommendation}"); + prompt.AppendLine($" Confidence: {assessment.Confidence}"); + if (!string.IsNullOrWhiteSpace(assessment.Summary)) + prompt.AppendLine($" Summary: {assessment.Summary}"); + } + + var generated = await aiTextGenerationService.GenerateAsync( + tenantId, + null, + new AiTextGenerationRequest( + SystemPrompt, + prompt.ToString(), + IncludeCitations: false, + MaxOutputTokens: 700), + ct); + if (!generated.IsSuccess) + return Result.Failure(generated.Error ?? "AI generation failed."); + + var parsed = ParseDraft(generated.Value.Content); + if (parsed is null) + return Result.Failure("AI recommendation response was not valid JSON."); + + if (!SupportedOutcomes.Contains(parsed.RecommendedOutcome)) + return Result.Failure("AI recommendation response used an unsupported remediation outcome."); + + if (!SupportedPriorities.Contains(parsed.PriorityOverride)) + return Result.Failure("AI recommendation response used an unsupported priority."); + + if (string.IsNullOrWhiteSpace(parsed.Rationale)) + return Result.Failure("AI recommendation response did not include a rationale."); + + return Result.Success(new AiRecommendationDraftDto( + NormalizeMatch(parsed.RecommendedOutcome, SupportedOutcomes), + NormalizeMatch(parsed.PriorityOverride, SupportedPriorities), + parsed.Rationale.Trim() + )); + } + + private static AiRecommendationDraftDto? ParseDraft(string content) + { + try + { + return JsonSerializer.Deserialize( + StripCodeFence(content), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + catch (JsonException) + { + return null; + } + } + + private static string StripCodeFence(string content) + { + var trimmed = content.Trim(); + if (!trimmed.StartsWith("```", StringComparison.Ordinal)) + return trimmed; + + var firstLineEnd = trimmed.IndexOf('\n'); + var lastFenceStart = trimmed.LastIndexOf("```", StringComparison.Ordinal); + if (firstLineEnd < 0 || lastFenceStart <= firstLineEnd) + return trimmed; + + return trimmed[(firstLineEnd + 1)..lastFenceStart].Trim(); + } + + private static string NormalizeMatch(string value, HashSet supported) => + supported.First(item => string.Equals(item, value, StringComparison.OrdinalIgnoreCase)); +} diff --git a/tests/PatchHound.Tests/Api/AiRecommendationDraftServiceTests.cs b/tests/PatchHound.Tests/Api/AiRecommendationDraftServiceTests.cs new file mode 100644 index 00000000..cb0ed50d --- /dev/null +++ b/tests/PatchHound.Tests/Api/AiRecommendationDraftServiceTests.cs @@ -0,0 +1,154 @@ +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using NSubstitute; +using PatchHound.Api.Services; +using PatchHound.Core.Common; +using PatchHound.Core.Constants; +using PatchHound.Core.Entities; +using PatchHound.Core.Enums; +using PatchHound.Core.Interfaces; +using PatchHound.Core.Models; +using PatchHound.Core.Services; +using PatchHound.Infrastructure.Data; +using PatchHound.Tests.TestData; + +namespace PatchHound.Tests.Api; + +public class AiRecommendationDraftServiceTests : IDisposable +{ + private readonly Guid _tenantId = Guid.NewGuid(); + private readonly ITenantContext _tenantContext; + private readonly PatchHoundDbContext _dbContext; + + public AiRecommendationDraftServiceTests() + { + _tenantContext = Substitute.For(); + _tenantContext.CurrentTenantId.Returns(_tenantId); + _tenantContext.AccessibleTenantIds.Returns([_tenantId]); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + _dbContext = new PatchHoundDbContext( + options, + TestServiceProviderFactory.Create(_tenantContext) + ); + } + + [Fact] + public async Task GenerateAsync_LimitsPromptToMostRelevantPatchAssessments() + { + var product = SoftwareProduct.Create("Contoso", "Contoso Agent", null); + var remediationCase = RemediationCase.Create(_tenantId, product.Id); + var device = CanonicalTestData.MakeDevice(_tenantId); + var installedSoftware = CanonicalTestData.MakeInstalledSoftware(_tenantId, device.Id, product.Id); + + await _dbContext.AddRangeAsync(product, remediationCase, device, installedSoftware); + + for (var i = 0; i < 26; i++) + { + var vulnerability = CreateVulnerability($"CVE-2026-HIGH-{i:00}", 9.9m - (i * 0.1m)); + var exposure = CreateExposure(device.Id, product.Id, installedSoftware.Id, vulnerability.Id); + var assessment = CreateAssessment( + vulnerability.Id, + PatchUrgencyTier.Emergency, + $"Emergency reason {i:00}"); + await _dbContext.AddRangeAsync(vulnerability, exposure, assessment); + } + + var lowPriorityVulnerability = CreateVulnerability("CVE-2026-LOW-OUT", 9.8m); + var lowPriorityExposure = CreateExposure(device.Id, product.Id, installedSoftware.Id, lowPriorityVulnerability.Id); + var lowPriorityAssessment = CreateAssessment( + lowPriorityVulnerability.Id, + PatchUrgencyTier.LowPriority, + "Low priority reason should not be sent."); + await _dbContext.AddRangeAsync(lowPriorityVulnerability, lowPriorityExposure, lowPriorityAssessment); + await _dbContext.SaveChangesAsync(); + + AiTextGenerationRequest? capturedRequest = null; + var provider = Substitute.For(); + provider.ProviderType.Returns(TenantAiProviderType.OpenAi); + provider + .GenerateTextAsync( + Arg.Do(request => capturedRequest = request), + Arg.Any(), + Arg.Any()) + .Returns(""" + { + "recommendedOutcome": "ApprovedForPatching", + "priorityOverride": "Critical", + "rationale": "Patch the highest urgency vulnerabilities first." + } + """); + + var profile = TenantAiProfileFactory.Create(_tenantId, name: "Recommendation profile"); + var aiResolver = Substitute.For(); + aiResolver.ResolveDefaultAsync(_tenantId, Arg.Any()) + .Returns(Result.Success(new TenantAiProfileResolved(profile, "secret"))); + + var service = new AiRecommendationDraftService( + _dbContext, + new TenantAiTextGenerationService([provider], aiResolver) + ); + + var result = await service.GenerateAsync(_tenantId, remediationCase.Id, CancellationToken.None); + + result.IsSuccess.Should().BeTrue(result.Error); + capturedRequest.Should().NotBeNull(); + capturedRequest!.UserPrompt.Should().Contain("Only the top 25 of 27 assessments are included"); + capturedRequest.UserPrompt.Should().Contain("CVE-2026-HIGH-00"); + capturedRequest.UserPrompt.Should().NotContain("CVE-2026-LOW-OUT"); + capturedRequest.UserPrompt.Should().NotContain("Low priority reason should not be sent."); + } + + private static Vulnerability CreateVulnerability(string externalId, decimal cvssScore) => + Vulnerability.Create( + "nvd", + externalId, + "Remote code execution", + "A remotely exploitable vulnerability.", + Severity.Critical, + cvssScore, + null, + DateTimeOffset.UtcNow.AddDays(-30) + ); + + private DeviceVulnerabilityExposure CreateExposure( + Guid deviceId, + Guid productId, + Guid installedSoftwareId, + Guid vulnerabilityId) => + DeviceVulnerabilityExposure.Observe( + _tenantId, + deviceId, + vulnerabilityId, + productId, + installedSoftwareId, + "1.2.3", + ExposureMatchSource.Product, + DateTimeOffset.UtcNow.AddDays(-2), + runId: Guid.NewGuid()); + + private static VulnerabilityPatchAssessment CreateAssessment( + Guid vulnerabilityId, + string urgencyTier, + string urgencyReason) => + VulnerabilityPatchAssessment.Create( + vulnerabilityId, + "Patch immediately.", + "High", + "Known exploitation is credible.", + urgencyTier, + "Within 24 hours", + urgencyReason, + "[]", + "[]", + "[]", + "Default AI", + null, + DateTimeOffset.UtcNow + ); + + public void Dispose() => _dbContext.Dispose(); +} diff --git a/tests/PatchHound.Tests/Api/RemediationDecisionsControllerTests.cs b/tests/PatchHound.Tests/Api/RemediationDecisionsControllerTests.cs index db9aa064..fbbcc6cd 100644 --- a/tests/PatchHound.Tests/Api/RemediationDecisionsControllerTests.cs +++ b/tests/PatchHound.Tests/Api/RemediationDecisionsControllerTests.cs @@ -7,6 +7,7 @@ using PatchHound.Api.Models.Decisions; using PatchHound.Api.Services; using PatchHound.Core.Common; +using PatchHound.Core.Constants; using PatchHound.Core.Entities; using PatchHound.Core.Enums; using PatchHound.Core.Interfaces; @@ -128,6 +129,7 @@ public async Task GenerateThreatIntel_ReturnsBadRequest_WhenNoTenantSelected() workflowAuthorizationService: null!, workflowService: null!, threatIntelService: null!, + aiRecommendationDraftService: null!, dbContext: _dbContext, tenantContext: noTenantContext ); @@ -159,6 +161,7 @@ public async Task GenerateThreatIntel_ReturnsBadRequest_WhenNoAiProfileConfigure workflowAuthorizationService: null!, workflowService: null!, threatIntelService: threatIntelService, + aiRecommendationDraftService: null!, dbContext: _dbContext, tenantContext: _tenantContext ); @@ -191,6 +194,7 @@ public async Task GenerateThreatIntel_ReturnsNotFound_WhenCaseDoesNotExist() workflowAuthorizationService: null!, workflowService: null!, threatIntelService: threatIntelService, + aiRecommendationDraftService: null!, dbContext: _dbContext, tenantContext: _tenantContext ); @@ -277,8 +281,126 @@ public async Task GenerateThreatIntel_PersistsSummaryForDecisionContextReload() reloaded.ThreatIntel.ProfileName.Should().Be("Threat profile"); } + [Fact] + public async Task GenerateAiRecommendationDraft_ReturnsBadRequest_WhenNoTenantSelected() + { + var noTenantContext = Substitute.For(); + noTenantContext.CurrentTenantId.Returns((Guid?)null); + + var controller = new RemediationDecisionsController( + queryService: null!, + decisionService: null!, + approvalTaskService: null!, + recommendationService: null!, + workflowAuthorizationService: null!, + workflowService: null!, + threatIntelService: null!, + aiRecommendationDraftService: null!, + dbContext: _dbContext, + tenantContext: noTenantContext + ); + + var result = await controller.GenerateAiRecommendationDraft(Guid.NewGuid(), CancellationToken.None); + + var badRequest = result.Result.Should().BeOfType().Subject; + badRequest.Value.Should().BeOfType().Which.Title.Should().Be("No active tenant is selected."); + } + + [Fact] + public async Task GenerateAiRecommendationDraft_ReturnsNotFound_WhenCaseDoesNotExist() + { + var service = CreateAiRecommendationDraftService(); + var controller = CreateController(aiRecommendationDraftService: service); + + var result = await controller.GenerateAiRecommendationDraft(Guid.NewGuid(), CancellationToken.None); + + var notFound = result.Result.Should().BeOfType().Subject; + notFound.Value.Should().BeOfType().Which.Title.Should() + .Be("Remediation case not found."); + } + + [Fact] + public async Task GenerateAiRecommendationDraft_ReturnsDraftFromPatchAssessments() + { + var product = SoftwareProduct.Create("Contoso", "Contoso Agent", null); + var remediationCase = RemediationCase.Create(_tenantId, product.Id); + var device = CanonicalTestData.MakeDevice(_tenantId); + var installedSoftware = CanonicalTestData.MakeInstalledSoftware(_tenantId, device.Id, product.Id); + var vulnerability = Vulnerability.Create( + "nvd", + "CVE-2026-4242", + "Remote code execution", + "A remotely exploitable vulnerability.", + Severity.Critical, + 9.8m, + null, + DateTimeOffset.UtcNow.AddDays(-30) + ); + var exposure = DeviceVulnerabilityExposure.Observe( + _tenantId, + device.Id, + vulnerability.Id, + product.Id, + installedSoftware.Id, + "1.2.3", + ExposureMatchSource.Product, + DateTimeOffset.UtcNow.AddDays(-2), + runId: Guid.NewGuid()); + var assessment = VulnerabilityPatchAssessment.Create( + vulnerability.Id, + "Patch immediately.", + "High", + "Known exploitation is credible.", + PatchUrgencyTier.Emergency, + "Within 24 hours", + "Public exploitation and high blast radius.", + "[]", + "[]", + "[]", + "Default AI", + null, + DateTimeOffset.UtcNow + ); + var profile = TenantAiProfileFactory.Create(_tenantId, name: "Recommendation profile"); + var provider = Substitute.For(); + provider.ProviderType.Returns(TenantAiProviderType.OpenAi); + provider + .GenerateTextAsync( + Arg.Is(request => + request.UserPrompt.Contains("CVE-2026-4242") + && request.UserPrompt.Contains("Public exploitation and high blast radius.") + && request.UserPrompt.Contains("Target SLA: Within 24 hours")), + Arg.Any(), + Arg.Any()) + .Returns(""" + { + "recommendedOutcome": "ApprovedForPatching", + "priorityOverride": "Critical", + "rationale": "Patch immediately because exploitation is likely and the target SLA is within 24 hours." + } + """); + var aiResolver = Substitute.For(); + aiResolver.ResolveDefaultAsync(_tenantId, Arg.Any()) + .Returns(Result.Success(new TenantAiProfileResolved(profile, "secret"))); + + await _dbContext.AddRangeAsync(product, remediationCase, device, installedSoftware, vulnerability, exposure, assessment); + await _dbContext.SaveChangesAsync(); + + var service = CreateAiRecommendationDraftService(provider, aiResolver); + var controller = CreateController(aiRecommendationDraftService: service); + + var result = await controller.GenerateAiRecommendationDraft(remediationCase.Id, CancellationToken.None); + + var ok = result.Result.Should().BeOfType().Subject; + var draft = ok.Value.Should().BeOfType().Subject; + draft.RecommendedOutcome.Should().Be("ApprovedForPatching"); + draft.PriorityOverride.Should().Be("Critical"); + draft.Rationale.Should().Contain("target SLA is within 24 hours"); + } + private RemediationDecisionsController CreateController( - RemediationWorkflowAuthorizationService? workflowAuthorizationService = null + RemediationWorkflowAuthorizationService? workflowAuthorizationService = null, + AiRecommendationDraftService? aiRecommendationDraftService = null ) => new( queryService: null!, @@ -288,10 +410,31 @@ private RemediationDecisionsController CreateController( workflowAuthorizationService: workflowAuthorizationService!, workflowService: null!, threatIntelService: null!, + aiRecommendationDraftService: aiRecommendationDraftService!, dbContext: _dbContext, tenantContext: _tenantContext ); + private AiRecommendationDraftService CreateAiRecommendationDraftService( + IAiReportProvider? provider = null, + ITenantAiConfigurationResolver? aiResolver = null) + { + aiResolver ??= Substitute.For(); + if (provider is null) + { + var profile = TenantAiProfileFactory.Create(_tenantId, name: "Recommendation profile"); + aiResolver.ResolveDefaultAsync(_tenantId, Arg.Any()) + .Returns(Result.Success(new TenantAiProfileResolved(profile, "secret"))); + provider = Substitute.For(); + provider.ProviderType.Returns(TenantAiProviderType.OpenAi); + } + + return new AiRecommendationDraftService( + _dbContext, + new TenantAiTextGenerationService([provider], aiResolver) + ); + } + private async Task SeedActiveDecisionWorkflowAsync() { var product = SoftwareProduct.Create("Contoso", "Contoso Agent", null);