Merge branch 'main' into feat/Desktop-app

This commit is contained in:
Sparsh 2026-05-21 16:52:47 +05:30 committed by GitHub
commit c2ee1b3cf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 569 additions and 111 deletions

View file

@ -48,7 +48,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
queries: security-and-quality
@ -69,6 +69,6 @@ jobs:
- '**/test/fixtures/**'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: '/language:${{ matrix.language }}'

View file

@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Dependency Review
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
comment-summary-in-pr: on-failure

View file

@ -108,7 +108,7 @@ jobs:
# Pinned to v7.2.0. Verify SHA via:
# gh api repos/release-drafter/release-drafter/git/refs/tags/v7.2.0
# v7 removed `disable-releaser`; use `dry-run: true` to only autolabel.
- uses: release-drafter/release-drafter@563bf132657a13ded0b01fcb723c5a58cdd824e2 # v7.2.1
- uses: release-drafter/release-drafter@c2e2804cc59f45f57076a99af580d0fedb697927 # v7.3.0
with:
config-name: release-drafter.yml
dry-run: true

View file

@ -53,6 +53,6 @@ jobs:
retention-days: 5
- name: Upload to Security tab
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: results.sarif

View file

@ -76,7 +76,7 @@ jobs:
exit-code: '0'
- name: Upload to Security tab
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: trivy-${{ matrix.image.name }}.sarif
category: trivy-${{ matrix.image.name }}

View file

@ -76,7 +76,7 @@ jobs:
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: zizmor.sarif
category: zizmor

View file

@ -15,10 +15,10 @@
"@langchain/ollama": "^1.2.6",
"@langchain/openai": "^1.4.5",
"@sigma/edge-curve": "^3.1.0",
"@tailwindcss/vite": "^4.2.4",
"@tailwindcss/vite": "^4.3.0",
"axios": "^1.16.0",
"d3": "^7.9.0",
"dompurify": "^3.4.2",
"dompurify": "^3.4.3",
"gitnexus-shared": "file:../gitnexus-shared",
"graphology": "^0.26.0",
"graphology-indices": "^0.17.0",
@ -35,7 +35,7 @@
"react": "^19.2.5",
"react-dom": "^19.2.6",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.0",
"react-syntax-highlighter": "^16.1.1",
"react-zoom-pan-pinch": "^4.0.3",
"remark-gfm": "^4.0.1",
"sigma": "^3.0.2",
@ -2011,47 +2011,47 @@
"license": "MIT"
},
"node_modules/@tailwindcss/node": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
"integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
"integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"enhanced-resolve": "^5.19.0",
"enhanced-resolve": "^5.21.0",
"jiti": "^2.6.1",
"lightningcss": "1.32.0",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
"tailwindcss": "4.2.4"
"tailwindcss": "4.3.0"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz",
"integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz",
"integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.2.4",
"@tailwindcss/oxide-darwin-arm64": "4.2.4",
"@tailwindcss/oxide-darwin-x64": "4.2.4",
"@tailwindcss/oxide-freebsd-x64": "4.2.4",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
"@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
"@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
"@tailwindcss/oxide-linux-x64-musl": "4.2.4",
"@tailwindcss/oxide-wasm32-wasi": "4.2.4",
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
"@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
"@tailwindcss/oxide-android-arm64": "4.3.0",
"@tailwindcss/oxide-darwin-arm64": "4.3.0",
"@tailwindcss/oxide-darwin-x64": "4.3.0",
"@tailwindcss/oxide-freebsd-x64": "4.3.0",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0",
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.0",
"@tailwindcss/oxide-linux-arm64-musl": "4.3.0",
"@tailwindcss/oxide-linux-x64-gnu": "4.3.0",
"@tailwindcss/oxide-linux-x64-musl": "4.3.0",
"@tailwindcss/oxide-wasm32-wasi": "4.3.0",
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.0",
"@tailwindcss/oxide-win32-x64-msvc": "4.3.0"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
"integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz",
"integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==",
"cpu": [
"arm64"
],
@ -2065,9 +2065,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
"integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz",
"integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==",
"cpu": [
"arm64"
],
@ -2081,9 +2081,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
"integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz",
"integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==",
"cpu": [
"x64"
],
@ -2097,9 +2097,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
"integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz",
"integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==",
"cpu": [
"x64"
],
@ -2113,9 +2113,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
"integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz",
"integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==",
"cpu": [
"arm"
],
@ -2129,9 +2129,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
"integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz",
"integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==",
"cpu": [
"arm64"
],
@ -2145,9 +2145,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
"integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz",
"integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==",
"cpu": [
"arm64"
],
@ -2161,9 +2161,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
"integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz",
"integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==",
"cpu": [
"x64"
],
@ -2177,9 +2177,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
"integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz",
"integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==",
"cpu": [
"x64"
],
@ -2193,9 +2193,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
"integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz",
"integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@ -2210,10 +2210,10 @@
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.8.1",
"@emnapi/runtime": "^1.8.1",
"@emnapi/wasi-threads": "^1.1.0",
"@napi-rs/wasm-runtime": "^1.1.1",
"@emnapi/core": "^1.10.0",
"@emnapi/runtime": "^1.10.0",
"@emnapi/wasi-threads": "^1.2.1",
"@napi-rs/wasm-runtime": "^1.1.4",
"@tybys/wasm-util": "^0.10.1",
"tslib": "^2.8.1"
},
@ -2221,10 +2221,70 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.10.0",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.10.0",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
"integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
"integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==",
"cpu": [
"arm64"
],
@ -2238,9 +2298,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz",
"integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz",
"integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==",
"cpu": [
"x64"
],
@ -2254,14 +2314,14 @@
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz",
"integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz",
"integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==",
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.2.4",
"@tailwindcss/oxide": "4.2.4",
"tailwindcss": "4.2.4"
"@tailwindcss/node": "4.3.0",
"@tailwindcss/oxide": "4.3.0",
"tailwindcss": "4.3.0"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7 || ^8"
@ -4407,9 +4467,9 @@
"peer": true
},
"node_modules/dompurify": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.3.tgz",
"integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@ -4478,9 +4538,9 @@
"license": "ISC"
},
"node_modules/enhanced-resolve": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
"version": "5.21.6",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@ -7672,9 +7732,9 @@
}
},
"node_modules/react-syntax-highlighter": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
"integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==",
"version": "16.1.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz",
"integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
@ -8102,9 +8162,9 @@
"license": "MIT"
},
"node_modules/tailwindcss": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
"integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
"integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
"license": "MIT"
},
"node_modules/tapable": {

View file

@ -26,10 +26,10 @@
"@langchain/ollama": "^1.2.6",
"@langchain/openai": "^1.4.5",
"@sigma/edge-curve": "^3.1.0",
"@tailwindcss/vite": "^4.2.4",
"@tailwindcss/vite": "^4.3.0",
"axios": "^1.16.0",
"d3": "^7.9.0",
"dompurify": "^3.4.2",
"dompurify": "^3.4.3",
"graphology": "^0.26.0",
"graphology-indices": "^0.17.0",
"graphology-layout-force": "^0.2.4",
@ -45,7 +45,7 @@
"react": "^19.2.5",
"react-dom": "^19.2.6",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.0",
"react-syntax-highlighter": "^16.1.1",
"react-zoom-pan-pinch": "^4.0.3",
"remark-gfm": "^4.0.1",
"sigma": "^3.0.2",

View file

@ -9,6 +9,7 @@
import { useState, useRef, useEffect, useId } from 'react';
import {
Github,
Gitlab,
FolderOpen,
Loader2,
Check,
@ -26,15 +27,20 @@ import { AnalyzeProgress } from './AnalyzeProgress';
// ── Helpers ──────────────────────────────────────────────────────────────────
type InputMode = 'github' | 'local';
type InputMode = 'github' | 'gitlab' | 'local';
const GITHUB_RE = /^https?:\/\/(www\.)?github\.com\/[^/\s]+\/[^/\s]+/i;
const GITLAB_RE = /^https?:\/\/[^/\s]+\/[^/\s]+\/[^/\s]+(\/.*)?$/i;
const IS_WINDOWS = navigator.userAgent.toLowerCase().includes('win');
function isValidGithubUrl(value: string): boolean {
return GITHUB_RE.test(value.trim());
}
function isValidGitlabUrl(value: string): boolean {
return GITLAB_RE.test(value.trim());
}
// ── Mode tabs ────────────────────────────────────────────────────────────────
function ModeTabs({ mode, onChange }: { mode: InputMode; onChange: (m: InputMode) => void }) {
@ -53,6 +59,19 @@ function ModeTabs({ mode, onChange }: { mode: InputMode; onChange: (m: InputMode
<Github className="h-3 w-3" />
GitHub URL
</button>
<button
role="tab"
aria-selected={mode === 'gitlab'}
onClick={() => onChange('gitlab')}
className={`flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
mode === 'gitlab'
? 'bg-accent text-white shadow-sm'
: 'text-text-muted hover:text-text-secondary'
} `}
>
<Gitlab className="h-3 w-3" />
GitLab URL
</button>
<button
role="tab"
aria-selected={mode === 'local'}
@ -138,6 +157,7 @@ export const RepoAnalyzer = ({ variant, onComplete, onCancel }: RepoAnalyzerProp
const folderInputRef = useRef<HTMLInputElement>(null);
const [mode, setMode] = useState<InputMode>('github');
const [githubUrl, setGithubUrl] = useState('');
const [gitlabUrl, setGitlabUrl] = useState('');
const [localPath, setLocalPath] = useState('');
const [phase, setPhase] = useState<InternalPhase>('input');
const [validationError, setValidationError] = useState<string | null>(null);
@ -162,6 +182,7 @@ export const RepoAnalyzer = ({ variant, onComplete, onCancel }: RepoAnalyzerProp
const handleModeChange = (m: InputMode) => {
setMode(m);
setGithubUrl('');
setGitlabUrl('');
setLocalPath('');
setValidationError(null);
};
@ -175,13 +196,19 @@ export const RepoAnalyzer = ({ variant, onComplete, onCancel }: RepoAnalyzerProp
const canSubmit =
mode === 'github'
? isValidGithubUrl(githubUrl) && (phase === 'input' || phase === 'error')
: localPath.trim().length > 1 && (phase === 'input' || phase === 'error');
: mode === 'gitlab'
? isValidGitlabUrl(gitlabUrl) && (phase === 'input' || phase === 'error')
: localPath.trim().length > 1 && (phase === 'input' || phase === 'error');
const handleAnalyze = async () => {
if (mode === 'github' && !isValidGithubUrl(githubUrl)) {
setValidationError('Please enter a valid GitHub repository URL.');
return;
}
if (mode === 'gitlab' && !isValidGitlabUrl(gitlabUrl)) {
setValidationError('Please enter a valid GitLab repository URL.');
return;
}
if (mode === 'local' && localPath.trim().length < 2) {
setValidationError('Please enter a folder path.');
return;
@ -191,12 +218,22 @@ export const RepoAnalyzer = ({ variant, onComplete, onCancel }: RepoAnalyzerProp
setPhase('starting');
try {
const request = mode === 'github' ? { url: githubUrl.trim() } : { path: localPath.trim() };
const request =
mode === 'github'
? { url: githubUrl.trim() }
: mode === 'gitlab'
? { url: gitlabUrl.trim() }
: { path: localPath.trim() };
const { jobId } = await startAnalyze(request);
jobIdRef.current = jobId;
setPhase('analyzing');
const nameSource = mode === 'github' ? githubUrl.trim() : localPath.trim();
const nameSource =
mode === 'github'
? githubUrl.trim()
: mode === 'gitlab'
? gitlabUrl.trim()
: localPath.trim();
const controller = streamAnalyzeProgress(
jobId,
(p) => setProgress(p),
@ -297,6 +334,61 @@ export const RepoAnalyzer = ({ variant, onComplete, onCancel }: RepoAnalyzerProp
</div>
)}
{/* GitLab URL input */}
{showInput && mode === 'gitlab' && (
<div className="space-y-2">
<label
htmlFor={inputId}
className="block text-xs font-medium tracking-wider text-text-secondary uppercase"
>
GitLab Repository URL
</label>
<div
className={`flex items-center gap-3 rounded-xl border bg-void px-4 py-3.5 transition-all duration-200 ${
validationError && phase === 'error'
? 'border-red-500/50'
: isValidGitlabUrl(gitlabUrl)
? 'border-accent/50 shadow-[0_0_0_3px_rgba(124,58,237,0.08)]'
: 'border-border-default focus-within:border-accent/40'
} `}
>
<Gitlab className="h-4 w-4 shrink-0 text-text-muted" />
<input
id={inputId}
type="url"
value={gitlabUrl}
onChange={(e) => {
setGitlabUrl(e.target.value);
if (validationError) setValidationError(null);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && canSubmit && !isLoading) {
e.preventDefault();
handleAnalyze();
}
}}
disabled={isLoading}
placeholder="https://gitlab.com/owner/repo"
autoComplete="url"
spellCheck={false}
className="flex-1 border-none bg-transparent font-mono text-sm text-text-primary outline-none placeholder:text-text-muted disabled:opacity-50"
/>
{gitlabUrl.length > 10 && (
<div className="shrink-0">
{isValidGitlabUrl(gitlabUrl) ? (
<Check className="h-3.5 w-3.5 text-emerald-400" />
) : (
<AlertCircle className="h-3.5 w-3.5 text-text-muted" />
)}
</div>
)}
</div>
<p className="text-xs text-text-muted">
Supports GitLab.com and self-hosted GitLab instances.
</p>
</div>
)}
{/* Local folder input */}
{showInput && mode === 'local' && (
<div className="space-y-2">

View file

@ -123,6 +123,67 @@ export {
* defaults to `currentColor`, so Tailwind `text-*` utilities work the same as
* with any other icon in this module.
*/
/**
* GitLab tanuki mark SVG path data from simple-icons (CC0-1.0).
*
* GitLab's logo (the tanuki/fox-head) is a registered trademark of GitLab Inc.
* We use it here only to indicate GitLab source-repo integration.
*
* API-compatible with `lucide-react` icons (`LucideProps`).
*/
export const Gitlab = forwardRef<SVGSVGElement, LucideProps>(function Gitlab(
{
size = 24,
color = 'currentColor',
className,
strokeWidth: _strokeWidth,
absoluteStrokeWidth: _absoluteStrokeWidth,
...rest
},
ref,
) {
const numericSize = typeof size === 'string' ? Number.parseFloat(size) : size;
const useSmallVariant = Number.isFinite(numericSize) && (numericSize as number) <= 16;
if (useSmallVariant) {
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 16 16"
fill={color}
className={className}
{...rest}
>
<path d="M8 15.282l1.855-5.717H6.145L8 15.282z" />
<path d="M8 15.282L6.145 9.565H2.333L8 15.282z" />
<path d="M2.333 9.565l-.944-2.942c-.09-.267.067-.553.333-.553h3.153L2.333 9.565z" />
<path d="M4.875 6.07L6.145 9.565H2.333l2.542-3.495z" />
<path d="M13.667 9.565l.944-2.942c.09-.267-.067-.553-.333-.553h-3.153l2.542 3.495z" />
<path d="M11.125 6.07L9.855 9.565h3.812l-2.542-3.495z" />
<path d="M8 15.282l1.855-5.717H6.145L8 15.282z" />
</svg>
);
}
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill={color}
className={className}
{...rest}
>
<path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z" />
</svg>
);
});
export const Github = forwardRef<SVGSVGElement, LucideProps>(function Github(
{
size = 24,

View file

@ -695,6 +695,14 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
// local-bound default).
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
// Chromium Private Network Access (required since Chrome 130+). Must run before
// cors: the cors middleware ends OPTIONS preflight responses, so this header
// has to be set on res before cors writes the preflight reply.
app.use((_req, res, next) => {
res.setHeader('Access-Control-Allow-Private-Network', 'true');
next();
});
// CORS: allow localhost, private/LAN networks, and the deployed site.
// Non-browser requests (curl, server-to-server) have no origin and are allowed.
// Disallowed origins get the response without Access-Control-Allow-Origin,
@ -709,22 +717,6 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
);
app.use(express.json({ limit: '10mb' }));
// Support Chromium Private Network Access (required since Chrome 130+).
// Without this header, Chrome/Edge/Brave/Arc block public->loopback requests
// which breaks bridge mode entirely.
app.use((_req, res, next) => {
res.setHeader('Access-Control-Allow-Private-Network', 'true');
next();
});
// Handle PNA preflight: Chromium sends Access-Control-Request-Private-Network
// on OPTIONS requests and expects the allow header in the response.
// Note: the actual Allow-Private-Network header is already set by the global
// middleware above, so we just need to call next() here.
app.options('*', (_req, res, next) => {
next();
});
// Initialize MCP backend (multi-repo, shared across all MCP sessions)
const backend = new LocalBackend();
await backend.init();

View file

@ -0,0 +1,141 @@
/**
* HTTP serve startup proves createServer() boots under Express 5.
*
* Spawns the built CLI (`gitnexus serve`) and probes GET /api/health.
* Catches regressions like invalid route patterns that throw at registration
* time before LadybugDB or MCP initialize.
*/
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
import fs from 'node:fs';
import http from 'node:http';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, describe, expect, it } from 'vitest';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..', '..');
const DIST_CLI = path.join(REPO_ROOT, 'dist', 'cli', 'index.js');
const STARTUP_BUDGET_MS = process.env.CI ? 30_000 : 15_000;
const allocateFreePort = (): Promise<number> =>
new Promise((resolve, reject) => {
const probe = http.createServer();
probe.once('error', reject);
probe.listen(0, '127.0.0.1', () => {
const addr = probe.address();
if (typeof addr !== 'object' || !addr) {
probe.close();
reject(new Error('could not allocate ephemeral port'));
return;
}
const port = addr.port;
probe.close((err) => (err ? reject(err) : resolve(port)));
});
});
const probeHealth = (port: number): Promise<{ status: number; body: string }> =>
new Promise((resolve, reject) => {
const req = http.get(`http://127.0.0.1:${port}/api/health`, (res) => {
const chunks: Buffer[] = [];
res.on('data', (c) => chunks.push(c));
res.on('end', () => {
resolve({
status: res.statusCode ?? 0,
body: Buffer.concat(chunks).toString('utf8'),
});
});
});
req.on('error', reject);
req.setTimeout(5_000, () => {
req.destroy();
reject(new Error('health probe timed out'));
});
});
// Child-process serve + health probe is reliable on Linux CI (where e2e failed).
// On Windows, spawned `serve` can print "running" before the listen socket is
// reachable from the parent; unit tests in server-cors-stack.test.ts cover the
// Express 5 registration path on all platforms.
const describeServeStartup = process.platform === 'win32' ? describe.skip : describe;
describeServeStartup('gitnexus serve HTTP startup (Express 5)', () => {
let proc: ChildProcessWithoutNullStreams | undefined;
let homeDir: string | undefined;
afterEach(async () => {
if (proc && !proc.killed) {
proc.kill('SIGTERM');
await new Promise<void>((resolve) => {
const timer = setTimeout(() => {
proc?.kill('SIGKILL');
resolve();
}, 3_000);
proc?.on('exit', () => {
clearTimeout(timer);
resolve();
});
});
}
proc = undefined;
if (homeDir) {
fs.rmSync(homeDir, { recursive: true, force: true });
homeDir = undefined;
}
});
it('serve boots and GET /api/health returns ok', async () => {
if (!fs.existsSync(DIST_CLI)) {
throw new Error(`Missing ${DIST_CLI} — run npm run build before integration tests`);
}
const port = await allocateFreePort();
homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-serve-home-'));
proc = spawn(
process.execPath,
[DIST_CLI, 'serve', '--port', String(port), '--host', '127.0.0.1'],
{
cwd: REPO_ROOT,
env: { ...process.env, GITNEXUS_HOME: homeDir, NODE_OPTIONS: '' },
stdio: ['ignore', 'pipe', 'pipe'],
},
);
let stdout = '';
let stderr = '';
proc.stdout.on('data', (buf) => {
stdout += buf.toString();
});
proc.stderr.on('data', (buf) => {
stderr += buf.toString();
});
const startedAt = Date.now();
let status = 0;
let body = '';
while (Date.now() - startedAt < STARTUP_BUDGET_MS) {
if (proc.exitCode !== null) {
throw new Error(
`serve exited ${proc.exitCode} before ready.\nstdout:\n${stdout}\nstderr:\n${stderr}`,
);
}
try {
({ status, body } = await probeHealth(port));
if (status === 200) {
break;
}
} catch {
// Server still starting — retry until budget expires.
}
await new Promise((r) => setTimeout(r, 100));
}
expect(status).toBe(200);
expect(body).toContain('"status":"ok"');
}, 60_000);
});

View file

@ -262,6 +262,17 @@ describe('production routes — rate-limit middleware wiring', () => {
);
});
it('does not register Express-4-only app.options("*") (Express 5 path-to-regexp)', () => {
expect(apiSource).not.toMatch(/app\.options\(\s*'\*'/);
expect(apiSource).not.toMatch(/app\.options\(\s*'\/\*'/);
});
it('sets PNA header middleware before cors (preflight must include Allow-Private-Network)', () => {
expect(apiSource).toMatch(
/Access-Control-Allow-Private-Network[\s\S]*?app\.use\(\s*\n?\s*cors\(/,
);
});
it('embed route flushes WAL via flushWAL, not inline executeQuery (#1376)', () => {
// The embed handler must call the consolidated helper, not hand-roll
// its own try/catch around executeQuery('CHECKPOINT').

View file

@ -0,0 +1,101 @@
/**
* Regression tests for createServer() CORS + PNA middleware (Express 5).
*
* Express 5 / path-to-regexp v8 rejects bare `app.options('*')`, which broke
* `gitnexus serve` in CI after #872. These tests mirror the registration order
* in createServer() without booting LadybugDB or MCP.
*/
import express from 'express';
import cors from 'cors';
import http from 'node:http';
import { afterEach, describe, expect, it } from 'vitest';
import { isAllowedOrigin } from '../../src/server/api.js';
/** Mirrors createServer() trust proxy + PNA + cors + json stack. */
const buildCreateServerCorsStack = (): express.Express => {
const app = express();
app.disable('x-powered-by');
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
app.use((_req, res, next) => {
res.setHeader('Access-Control-Allow-Private-Network', 'true');
next();
});
app.use(
cors({
origin: (origin, callback) => {
callback(null, isAllowedOrigin(origin));
},
}),
);
app.use(express.json({ limit: '10mb' }));
return app;
};
describe('createServer CORS/PNA stack — Express 5 registration', () => {
it('registers without path-to-regexp wildcard errors', () => {
expect(() => buildCreateServerCorsStack()).not.toThrow();
});
});
describe('createServer CORS/PNA stack — OPTIONS preflight', () => {
let server: http.Server | undefined;
let baseUrl = '';
afterEach(
() =>
new Promise<void>((resolve, reject) => {
if (!server) {
resolve();
return;
}
server.close((err) => (err ? reject(err) : resolve()));
}),
);
const start = (app: express.Express): Promise<void> =>
new Promise((resolve) => {
server = app.listen(0, '127.0.0.1', () => {
const addr = server!.address();
if (typeof addr === 'object' && addr) {
baseUrl = `http://127.0.0.1:${addr.port}`;
}
resolve();
});
});
it('OPTIONS /api/repos includes PNA and ACAO for allowed origin', async () => {
const app = buildCreateServerCorsStack();
await start(app);
const res = await fetch(`${baseUrl}/api/repos`, {
method: 'OPTIONS',
headers: {
Origin: 'https://gitnexus.vercel.app',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Private-Network': 'true',
},
});
expect(res.status).toBe(204);
expect(res.headers.get('access-control-allow-origin')).toBe('https://gitnexus.vercel.app');
expect(res.headers.get('access-control-allow-private-network')).toBe('true');
});
it('OPTIONS / includes PNA header for localhost bridge', async () => {
const app = buildCreateServerCorsStack();
await start(app);
const res = await fetch(`${baseUrl}/`, {
method: 'OPTIONS',
headers: {
Origin: 'http://localhost:5173',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Private-Network': 'true',
},
});
expect(res.status).toBe(204);
expect(res.headers.get('access-control-allow-origin')).toBe('http://localhost:5173');
expect(res.headers.get('access-control-allow-private-network')).toBe('true');
});
});