diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7f0de95a..c7e15210 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,39 +2,74 @@ name: CI
on:
pull_request:
- branches: [ main ]
+ branches: [main]
push:
- branches: [ main ]
+ branches: [main]
jobs:
- ci:
+ # Build native module on Linux (fast sanity check)
+ build-native:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Setup pnpm
- uses: pnpm/action-setup@v4
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache Rust
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: osgrep-core
+
+ - name: Install napi-rs CLI
+ run: npm install -g @napi-rs/cli
+
+ - name: Build native module
+ working-directory: osgrep-core
+ run: napi build --platform --release
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
with:
- version: 9
- run_install: false
+ name: native-linux
+ path: osgrep-core/*.node
+
+ # TypeScript checks and tests
+ ci:
+ runs-on: ubuntu-latest
+ needs: build-native
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup bun
+ uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- cache: 'pnpm'
- cache-dependency-path: 'pnpm-lock.yaml'
+
+ - name: Download native module
+ uses: actions/download-artifact@v4
+ with:
+ name: native-linux
+ path: osgrep-core
- name: Install dependencies
- run: pnpm install --frozen-lockfile
+ run: bun install
- name: Check types
- run: pnpm run typecheck
+ run: bun run typecheck
- name: Run tests
- run: pnpm run test
+ run: bun run test
- name: Build
- run: pnpm run build
-
+ run: bun run build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 66336a7c..7343b46c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,34 +6,106 @@ on:
- 'v*.*.*'
workflow_dispatch:
+env:
+ CARGO_INCREMENTAL: 0
+
jobs:
- publish-npm:
+ # =============================================================================
+ # Build native binaries for each platform
+ # =============================================================================
+ build-native:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: aarch64-apple-darwin
+ os: macos-latest
+ - target: x86_64-apple-darwin
+ os: macos-latest
+ - target: x86_64-unknown-linux-gnu
+ os: ubuntu-latest
+ - target: x86_64-unknown-linux-musl
+ os: ubuntu-latest
+ use-cross: true
+ - target: x86_64-pc-windows-msvc
+ os: windows-latest
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.target }}
+
+ - name: Install dependencies (Linux musl)
+ if: matrix.use-cross
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y musl-tools
+
+ - name: Install napi-rs CLI
+ run: npm install -g @napi-rs/cli
+
+ - name: Build native module
+ working-directory: osgrep-core
+ run: napi build --platform --release --target ${{ matrix.target }}
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: bindings-${{ matrix.target }}
+ path: osgrep-core/*.node
+ if-no-files-found: error
+
+ # =============================================================================
+ # Publish platform packages + meta package + osgrep
+ # =============================================================================
+ publish:
runs-on: ubuntu-latest
+ needs: build-native
permissions:
contents: write
id-token: write
+
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- - name: Setup pnpm
- uses: pnpm/action-setup@v4
- with:
- version: 9
- run_install: false
-
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- cache: 'pnpm'
- cache-dependency-path: 'pnpm-lock.yaml'
+
+ - name: Install napi-rs CLI
+ run: npm install -g @napi-rs/cli
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: osgrep-core/artifacts
+
+ - name: Move artifacts to osgrep-core
+ run: |
+ cd osgrep-core
+ for dir in artifacts/bindings-*/; do
+ mv "$dir"*.node . 2>/dev/null || true
+ done
+ rm -rf artifacts
+ ls -la *.node
- name: Verify tag commit is on main
- working-directory: .
run: |
git fetch origin main --depth=1
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
@@ -41,12 +113,6 @@ jobs:
exit 1
fi
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Check types
- run: pnpm run typecheck
-
- name: Verify tag matches package.json version
run: |
TAG="${GITHUB_REF##*/}"
@@ -56,15 +122,36 @@ jobs:
exit 1
fi
- - name: Build
- run: pnpm build
+ - name: Publish osgrep-core platform packages
+ working-directory: osgrep-core
+ run: napi prepublish -t npm
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+ - name: Publish osgrep-core meta package
+ working-directory: osgrep-core
+ run: npm publish --access public --provenance
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+ - name: Install bun
+ uses: oven-sh/setup-bun@v2
+
+ - name: Install osgrep dependencies
+ run: bun install
+
+ - name: Check types
+ run: bun run typecheck
+
+ - name: Build osgrep
+ run: bun run build
- - name: Publish to npm
- run: pnpm publish --access public --provenance --no-git-checks
+ - name: Publish osgrep to npm
+ run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- - name: Create GitHub Release (tag only, no assets)
+ - name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
diff --git a/.gitignore b/.gitignore
index 9b85d444..e08a9c8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,28 +1,53 @@
dist/
node_modules/
+**/node_modules/
*.tsbuildinfo
*.tgz
+.DS_Store
+
+# Local osgrep data
+.osgrep/
+
+# Development files
+CLAUDE.md
+TODO.md
+AGENTS.md
ADVANCED.md
-test-build.sh
-TEST_SUITE_PROMPT.md
-WATCH_MODE_PROMPT.md
ENGINEERING_DIARY.md
RELEASE_GUIDE.md
-osgrep/run-benchmark.sh
+TEST_SUITE_PROMPT.md
+WATCH_MODE_PROMPT.md
+AGENT_BENCHMARK.md
+latest_review.md
+plan.md
+
+# Benchmark & experiments (dev-only)
+benchmark/
+experiments/
+scripts/
+tools/
+public/
+test_skeleton.py
+test-build.sh
run-agent-benchmark.sh
setup-benchmark-repos.sh
-AGENT_BENCHMARK.md
benchmark-results.json
-src/bench/benchmark-agent.ts
-src/bench/generate-benchmark-chart.ts
-src/.env.local
-CLAUDE.md
-.DS_Store
-latest_review.md
benchmarks/*.log
-.osgrep/server.json
-.osgrep/cache/meta.lmdb-lock
+src/bench/
+
+# Env files
+src/.env.local
+.env*
+# Lockfiles (using bun)
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+
+bun.lockb
+# Native core (Rust / N-API) build outputs
+osgrep-core/target/
+**/target/
+osgrep-core/*.node
+osgrep-core/bench/
.osgrep
-TODO.md
-AGENTS.md
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6d69c9c6..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2025 Mixedbread AI (original mgrep work)
- Copyright 2025 Ryan D'Onofrio (osgrep modifications and enhancements)
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 62b2f374..00000000
--- a/NOTICE
+++ /dev/null
@@ -1,33 +0,0 @@
-osgrep
-Copyright 2025 Ryan Donofrio
-
-This product includes software originally developed by MixedBread as mgrep.
-Original mgrep Copyright 2025 MixedBread
-https://github.com/mixedbread-ai/mgrep
-
-================================================================================
-
-MODIFICATIONS AND ENHANCEMENTS
-
-osgrep is a substantial modification and enhancement of the original mgrep
-codebase. The following major changes and additions have been made by
-Ryan Donofrio:
-
-- Complete rewrite to use local-only embeddings (removing remote API dependencies)
-- Integration with @huggingface/transformers for local model inference
-- Implementation of LanceDB for local vector storage
-- New local storage architecture and indexing system
-- Enhanced chunking and context management
-- Addition of watch mode for real-time index updates
-- New CLI commands and improved user experience
-- Comprehensive benchmarking and evaluation tools
-- Extensive test suite additions
-- Documentation and setup improvements
-
-
-================================================================================
-
-THIRD-PARTY DEPENDENCIES
-
-This software depends on various open-source libraries. See package.json for
-a complete list of dependencies and their respective licenses.
diff --git a/README.md b/README.md
index e88861c5..47008e29 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,10 @@
Natural-language search that works like `grep`. Fast, local, and built for coding agents.
- **Semantic:** Finds concepts ("where do transactions get created?"), not just strings.
-- **Call Graph Tracing:** Map dependencies with `trace` to see who calls what.
- **Role Detection:** Distinguishes `ORCHESTRATION` (high-level logic) from `DEFINITION` (types/classes).
-- **Local & Private:** 100% local embeddings via `onnxruntime-node`.
+- **Local & Private:** 100% local embeddings via `onnxruntime`.
- **Auto-Isolated:** Each repository gets its own index automatically.
-- **Agent-Ready:** Native output with symbols, roles, and call graphs.
+- **Agent-Ready:** Native output with symbols and roles.
## Quick Start
@@ -50,26 +49,6 @@ Natural-language search that works like `grep`. Fast, local, and built for codin
**Your first search will automatically index the repository.** Each repository is automatically isolated with its own index. Switching between repos "just works" ā no manual configuration needed. If the background server is running (`osgrep serve`), search goes through the hot daemon; otherwise it falls back to on-demand indexing.
-4. **Trace** (Call Graph)
-
- ```bash
- osgrep trace "function_name"
- ```
-See who calls a function (upstream dependencies) and what it calls (downstream dependencies). Perfect for impact analysis and understanding code flow.
-
-To find the symbols in your code base:
- ```bash
- osgrep symbols
- ```
-
-In our public benchmarks, `osgrep` can save about 20% of your LLM tokens and deliver a 30% speedup.
-
-
-

-
-
-
-
### Claude Code Plugin
1. Run `osgrep install-claude-code`
@@ -104,15 +83,20 @@ osgrep "how is the database connection pooled?"
| `--scores` | Show relevance scores (0-1) for each result. | `false` |
| `--min-score ` | Filter out results below this score threshold. | `0` |
| `--compact` | Show file paths only (like `grep -l`). | `false` |
+| `--deep` | Include related code (callers, definitions) for architectural context. | `false` |
| `-s`, `--sync` | Force re-index changed files before searching. | `false` |
| `-r`, `--reset` | Reset the index and re-index from scratch. | `false` |
+
**Examples:**
```bash
# General concept search
osgrep "API rate limiting logic"
-# Deep dive (show more matches per file)
+# Deep dive with architectural context (shows callers and definitions)
+osgrep "how does authentication work" --deep
+
+# Show more matches per file
osgrep "error handling" --per-file 5
# Just give me the files
@@ -158,7 +142,7 @@ Runs a lightweight HTTP server with live file watching so searches stay hot in R
- Keeps LanceDB and the embedding worker resident for <50ms responses.
- Watches the repo (via chokidar) and incrementally re-indexes on change.
- Health endpoint: `GET /health`
-- Search endpoint: `POST /search` with `{ query, limit, path, rerank }`
+- Search endpoint: `POST /search` with `{ query, limit, path, deep }`
- Writes lock: `.osgrep/server.json` with `port`/`pid`
**Options:**
@@ -205,41 +189,68 @@ osgrep serve stop --all
Claude Code hooks start/stop this automatically; you rarely need to run it manually.
-### `osgrep list`
+### `osgrep trace`
-Lists all indexed repositories (stores) and their metadata.
+Shows callers and callees for a symbol in one shot ā replaces multiple search/read cycles when tracing code flow.
```bash
-osgrep list
+osgrep trace handleRequest
```
-Shows store names, sizes, and last modified times. Useful for seeing what's indexed and cleaning up old stores.
+**Output (default plain format):**
+```
+handleRequest
+ def: src/server/handler.ts:45
+ calls: validateAuth routeRequest sendResponse
+ called_by: src/index.ts:12 src/api/router.ts:87
+```
-### `osgrep skeleton`
+**Options:**
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| `--depth ` | Traversal depth for nested calls | `1` |
+| `--callers` | Show only callers (who calls this) | `false` |
+| `--callees` | Show only callees (what this calls) | `false` |
+| `--path ` | Filter results to path prefix | - |
+| `--pretty` | Tree view output for humans | `false` |
+| `--json` | JSON output for programmatic use | `false` |
-Generates a compressed "skeleton" of a file, showing only signatures, types, and class structures while eliding function bodies.
+**Examples:**
```bash
-osgrep skeleton src/lib/auth.ts
+# Basic trace
+osgrep trace Searcher
+
+# Only show what calls this function
+osgrep trace handleAuth --callers
+
+# Only show what this function calls
+osgrep trace handleAuth --callees
+
+# Filter to specific directory
+osgrep trace validateToken --path src/auth
+
+# Pretty tree output
+osgrep trace processRequest --pretty
```
-**Output:**
-```typescript
-class AuthService {
- validate(token: string): boolean {
- // ā jwt.verify, checkScope, .. | C:5 | ORCH
- }
-}
+**Server endpoint:**
+```bash
+curl -X POST http://localhost:4444/trace \
+ -H "Content-Type: application/json" \
+ -d '{"symbol": "Searcher", "depth": 1, "callers": true}'
```
-**Modes:**
-- `osgrep skeleton `: Skeletonize specific file.
-- `osgrep skeleton `: Find symbol in index and skeletonize its file.
-- `osgrep skeleton "query"`: Search for query and skeletonize top matches.
+### `osgrep list`
-**Supported Languages:**
-TypeScript, JavaScript, Python, Go, Rust, Java, C#, C++, C, Ruby, PHP.
+Lists all indexed repositories (stores) and their metadata.
+
+```bash
+osgrep list
+```
+Shows store names, sizes, and last modified times. Useful for seeing what's indexed and cleaning up old stores.
### `osgrep doctor`
@@ -303,9 +314,9 @@ osgrep respects both `.gitignore` and `.osgrepignore` files when indexing. Creat
## Development
```bash
-pnpm install
-pnpm build # or pnpm dev
-pnpm format # biome check
+bun install
+bun run build
+bun run format # biome check
```
## Troubleshooting
@@ -316,15 +327,7 @@ pnpm format # biome check
- **Want faster indexing?** Keep fallback disabled (default) to skip files without TreeSitter support.
- **Need a fresh start?** Delete `~/.osgrep/data` and `~/.osgrep/meta.json` and run `osgrep index`.
-## Attribution
-
-osgrep is built upon the foundation of [mgrep](https://github.com/mixedbread-ai/mgrep) by MixedBread. We acknowledge and appreciate the original architectural concepts and design decisions that informed this work.
-
-
-See the [NOTICE](NOTICE) file for detailed attribution information.
-
## License
-Licensed under the Apache License, Version 2.0.
-See [LICENSE](LICENSE) and [Apache-2.0](https://opensource.org/licenses/Apache-2.0) for details.
+Licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0).
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 00000000..169254e1
Binary files /dev/null and b/bun.lockb differ
diff --git a/experiments/mrr-sweep.ts b/experiments/mrr-sweep.ts
deleted file mode 100644
index 063b601e..00000000
--- a/experiments/mrr-sweep.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { performance } from "node:perf_hooks";
-import { cases, evaluateCase } from "../src/eval";
-import { Searcher } from "../src/lib/search/searcher";
-import type { SearchFilter } from "../src/lib/store/types";
-import { VectorDB } from "../src/lib/store/vector-db";
-import {
- ensureProjectPaths,
- findProjectRoot,
-} from "../src/lib/utils/project-root";
-import { destroyWorkerPool } from "../src/lib/workers/pool";
-
-type Scenario = {
- name: string;
- env: Record;
- searchOptions?: { rerank?: boolean };
-};
-
-const enableDiagnostics =
- process.env.OSGREP_SWEEP_DEBUG === "1" ||
- process.env.OSGREP_SWEEP_DEBUG === "true";
-
-type Ranked = { path: string; score?: number };
-
-function topPaths(response: { data: any[] }, k = 3): Ranked[] {
- return (response.data || []).slice(0, k).map((chunk) => ({
- path: chunk?.metadata?.path || "",
- score: chunk?.score ?? chunk?.generated_metadata?._score,
- }));
-}
-
-function targetRank(response: { data: any[] }, expectedPath: string): number {
- const expectedPaths = expectedPath
- .split("|")
- .map((p) => p.trim().toLowerCase())
- .filter(Boolean);
- return response.data.findIndex((chunk: any) => {
- const path = chunk?.metadata?.path?.toLowerCase?.() || "";
- return expectedPaths.some((expected) => path.includes(expected));
- });
-}
-
-// Keep runs consistent and serial.
-process.env.OSGREP_WORKER_THREADS ??= "1";
-process.env.OSGREP_WORKER_COUNT ??= "1";
-
-const scenarios: Scenario[] = [
- {
- name: "baseline-rerank-on",
- env: {},
- searchOptions: { rerank: true },
- },
- {
- name: "oldish-no-rerank-strong-boosts",
- env: {
- OSGREP_CODE_BOOST: "1.25",
- OSGREP_TEST_PENALTY: "0.85",
- OSGREP_DOC_PENALTY: "0.5",
- OSGREP_PRE_K: "300",
- OSGREP_STAGE1_K: "400",
- OSGREP_STAGE2_K: "0", // skip pooled filter
- OSGREP_MAX_PER_FILE: "50",
- },
- searchOptions: { rerank: false },
- },
- {
- name: "small-rerank-strong-boosts",
- env: {
- OSGREP_CODE_BOOST: "1.25",
- OSGREP_TEST_PENALTY: "0.85",
- OSGREP_DOC_PENALTY: "0.5",
- OSGREP_PRE_K: "300",
- OSGREP_STAGE1_K: "400",
- OSGREP_STAGE2_K: "80",
- OSGREP_MAX_PER_FILE: "50",
- },
- searchOptions: { rerank: true },
- },
- {
- name: "no-diversify-rerank",
- env: {
- OSGREP_CODE_BOOST: "1.2",
- OSGREP_TEST_PENALTY: "0.85",
- OSGREP_DOC_PENALTY: "0.7",
- OSGREP_PRE_K: "400",
- OSGREP_STAGE1_K: "500",
- OSGREP_STAGE2_K: "120",
- OSGREP_MAX_PER_FILE: "1000",
- },
- searchOptions: { rerank: true },
- },
-];
-
-async function runScenario(scenario: Scenario) {
- const touched: Record = {};
- for (const [key, value] of Object.entries(scenario.env)) {
- touched[key] = process.env[key];
- process.env[key] = value;
- }
-
- const searchRoot = process.cwd();
- const projectRoot = findProjectRoot(searchRoot) ?? searchRoot;
- const paths = ensureProjectPaths(projectRoot);
- const vectorDb = new VectorDB(paths.lancedbDir);
- const searcher = new Searcher(vectorDb);
-
- const results: ReturnType[] = [];
- let drops = 0;
- let lifts = 0;
- let total = 0;
- let better = 0;
- let worse = 0;
- let unchanged = 0;
-
- for (const c of cases) {
- const queryStart = performance.now();
- let fusedTop: Ranked[] = [];
- let rerankTop: Ranked[] = [];
- let fusedRank = -1;
-
- if (enableDiagnostics && scenario.searchOptions?.rerank !== false) {
- const fusedOnly = await searcher.search(
- c.query,
- 20,
- { rerank: false },
- undefined as SearchFilter | undefined,
- );
- fusedTop = topPaths(fusedOnly);
- fusedRank = targetRank(fusedOnly, c.expectedPath);
- }
-
- const res = await searcher.search(c.query, 20, scenario.searchOptions);
- rerankTop = topPaths(res);
- const rerankRank = enableDiagnostics ? targetRank(res, c.expectedPath) : -1;
-
- if (enableDiagnostics && fusedTop.length && rerankTop.length) {
- total += 1;
- const fusedBest = fusedTop[0]?.path?.toLowerCase?.() || "";
- const rerankFirstThree = rerankTop
- .slice(0, 3)
- .map((r) => r.path?.toLowerCase?.() || "");
- if (fusedBest && !rerankFirstThree.some((p) => p.includes(fusedBest))) {
- drops += 1;
- console.log(
- `[debug] ${c.query}\n fused#1: ${fusedTop
- .map((r) => r.path)
- .join(", ")}\n rerank#3: ${rerankTop
- .map((r) => r.path)
- .join(", ")}`,
- );
- }
-
- const rerankBest = rerankTop[0]?.path?.toLowerCase?.() || "";
- const fusedFirstThree = fusedTop
- .slice(0, 3)
- .map((r) => r.path?.toLowerCase?.() || "");
- if (
- rerankBest &&
- !fusedFirstThree.some((p) => p.includes(rerankBest)) &&
- fusedTop.some((r) =>
- (r.path?.toLowerCase?.() || "").includes(rerankBest),
- )
- ) {
- lifts += 1;
- console.log(
- `[lift] ${c.query}\n fused#3: ${fusedTop
- .map((r) => r.path)
- .join(", ")}\n rerank#1: ${rerankTop
- .map((r) => r.path)
- .join(", ")}`,
- );
- }
-
- if (fusedRank >= 0 && rerankRank >= 0) {
- if (rerankRank < fusedRank) {
- better += 1;
- } else if (rerankRank > fusedRank) {
- worse += 1;
- console.log(
- `[worse] ${c.query} fusedRank=${fusedRank + 1} rerankRank=${
- rerankRank + 1
- } path=${c.expectedPath}`,
- );
- } else {
- unchanged += 1;
- }
- }
- }
-
- const timeMs = performance.now() - queryStart;
- results.push(evaluateCase(res, c, timeMs));
- }
-
- const mrr = results.reduce((sum, r) => sum + r.rr, 0) / results.length;
- const recallAt10 =
- results.reduce((sum, r) => sum + r.recall, 0) / results.length;
- const avgTime =
- results.reduce((sum, r) => sum + r.timeMs, 0) / results.length;
- const found = results.filter((r) => r.found).length;
-
- console.log(`\n=== ${scenario.name} ===`);
- console.log(
- `MRR: ${mrr.toFixed(3)} | Recall@10: ${recallAt10.toFixed(3)} | Avg ms: ${avgTime.toFixed(0)} | Found: ${found}/${results.length}`,
- );
- if (enableDiagnostics && total > 0) {
- console.log(
- `Rerank dropped fused#1 out of top3 for ${drops}/${total} queries; promoted new #1 outside fused top3 for ${lifts}/${total} queries`,
- );
- if (better + worse + unchanged > 0) {
- console.log(
- `Target rank delta: better ${better}, worse ${worse}, unchanged ${unchanged} (out of ${better + worse + unchanged})`,
- );
- }
- }
-
- // Restore env (best-effort) so each scenario is clean.
- for (const [key, prev] of Object.entries(touched)) {
- if (prev === undefined) delete process.env[key];
- else process.env[key] = prev;
- }
-}
-
-async function main() {
- const filter = process.argv[2];
- const runList = filter
- ? scenarios.filter((s) => s.name === filter)
- : scenarios;
-
- for (const scenario of runList) {
- try {
- await runScenario(scenario);
- } catch (err) {
- console.error(`Scenario ${scenario.name} failed:`, err);
- }
- }
-
- try {
- await new Promise((resolve) => setTimeout(resolve, 200));
- await destroyWorkerPool();
- } catch {
- // Swallow cleanup errors for experiments.
- }
-}
-
-main().catch((err) => {
- console.error(err);
- process.exitCode = 1;
-});
diff --git a/experiments/quick_check.ts b/experiments/quick_check.ts
deleted file mode 100644
index 5274985e..00000000
--- a/experiments/quick_check.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { VectorDB } from "../src/lib/store/vector-db";
-import {
- ensureProjectPaths,
- findProjectRoot,
-} from "../src/lib/utils/project-root";
-
-async function main() {
- const root = process.cwd();
- const paths = ensureProjectPaths(findProjectRoot(root) ?? root);
- const db = new VectorDB(paths.lancedbDir);
- const table = await db.ensureTable();
- const rows = (await table.query().limit(5).toArray()) as any[];
- rows.forEach((r, i) => {
- const col = r.colbert;
- let len = 0;
- if (Buffer.isBuffer(col)) len = col.length;
- else if (Array.isArray(col)) len = col.length;
- else if (
- col &&
- typeof col === "object" &&
- "length" in (col as Record)
- )
- len = Number((col as { length: number }).length) || 0;
- else if (col && col.type === "Buffer" && Array.isArray(col.data))
- len = col.data.length;
- console.log(
- `#${i} path=${r.path}, colbertLen=${len}, scale=${r.colbert_scale}`,
- );
- });
-}
-main().catch(console.error);
diff --git a/experiments/ranking-test.ts b/experiments/ranking-test.ts
deleted file mode 100644
index 6c4f7b48..00000000
--- a/experiments/ranking-test.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-// experiments/ranking-test.ts
-
-import { Searcher } from "../src/lib/search/searcher";
-import { VectorDB } from "../src/lib/store/vector-db";
-import { ensureProjectPaths } from "../src/lib/utils/project-root";
-
-async function run() {
- const root = process.cwd();
- const paths = ensureProjectPaths(root);
- const db = new VectorDB(paths.lancedbDir);
- const searcher = new Searcher(db);
-
- const cases = [
- { query: "Request Validation", expected: "request_body_to_args" },
- { query: "Dependency Injection", expected: "solve_dependencies" },
- ];
-
- for (const c of cases) {
- console.log(`\nš Query: "${c.query}"`);
- const results = await searcher.search(c.query, 10, { rerank: true });
-
- // Filter out this test file
- const filtered = results.data.filter(
- (r) => !r.metadata?.path.includes("ranking-test.ts"),
- );
-
- const found = filtered.findIndex(
- (r) =>
- r.metadata?.path.includes(c.expected) || r.text?.includes(c.expected),
- );
-
- if (found === 0) console.log(`ā
PASS: Found '${c.expected}' at rank #1`);
- else if (found > 0)
- console.log(`ā ļø WARN: Found '${c.expected}' at rank #${found + 1}`);
- else console.log(`ā FAIL: Did not find '${c.expected}' in top results`);
-
- // Debug: Show top 3 roles/scores
- filtered.slice(0, 3).forEach((r, i) => {
- console.log(
- ` ${i + 1}. [${r.role ?? "UNK"}] ${r.metadata?.path} (Score: ${r.score.toFixed(3)})`,
- );
- });
- }
-}
-
-run();
diff --git a/experiments/verify-fix.ts b/experiments/verify-fix.ts
deleted file mode 100644
index c4773ac9..00000000
--- a/experiments/verify-fix.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { performance } from "node:perf_hooks";
-import { Searcher } from "../src/lib/search/searcher";
-import { VectorDB } from "../src/lib/store/vector-db";
-import {
- ensureProjectPaths,
- findProjectRoot,
-} from "../src/lib/utils/project-root";
-
-// Force old-engine style boosts for this check.
-process.env.OSGREP_CODE_BOOST = "1.25";
-process.env.OSGREP_TEST_PENALTY = "0.85";
-process.env.OSGREP_DOC_PENALTY = "0.5";
-
-async function main() {
- const root = process.cwd();
- const projectRoot = findProjectRoot(root) ?? root;
- const paths = ensureProjectPaths(projectRoot);
- const db = new VectorDB(paths.lancedbDir);
- const searcher = new Searcher(db);
-
- const warmStart = performance.now();
- await searcher.search("test query", 1, { rerank: true });
- console.log(
- `Warmup Query Time: ${(performance.now() - warmStart).toFixed(0)}ms`,
- );
-
- const res = await searcher.search("TreeSitterParser", 5, { rerank: true });
- console.log("\nContext Visibility Check:");
- res.data.forEach((r, i) => {
- const path = r.metadata?.path ?? "";
- const score = typeof r.score === "number" ? r.score.toFixed(3) : "n/a";
- console.log(`${i + 1}. ${path} (Score: ${score})`);
- if (!r.text?.includes("TreeSitterParser")) {
- console.log(" (Matched via vectorized context, not literal text)");
- }
- });
-}
-
-main().catch((err) => {
- console.error(err);
- process.exitCode = 1;
-});
diff --git a/osgrep-core/.gitignore b/osgrep-core/.gitignore
new file mode 100644
index 00000000..0b03a24c
--- /dev/null
+++ b/osgrep-core/.gitignore
@@ -0,0 +1,5 @@
+target/
+node_modules/
+*.node
+.DS_Store
+bench/opencode/
diff --git a/osgrep-core/Cargo.lock b/osgrep-core/Cargo.lock
new file mode 100644
index 00000000..cda7c17d
--- /dev/null
+++ b/osgrep-core/Cargo.lock
@@ -0,0 +1,2754 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.4",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "serde",
+ "static_assertions",
+]
+
+[[package]]
+name = "console"
+version = "0.15.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "unicode-width",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dary_heap"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "esaxx-rs"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "filetime"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "libredox",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
+
+[[package]]
+name = "flate2"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "hf-hub"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97"
+dependencies = [
+ "dirs",
+ "futures",
+ "http",
+ "indicatif",
+ "libc",
+ "log",
+ "native-tls",
+ "num_cpus",
+ "rand",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "ureq 2.12.1",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec 1.15.1",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec 1.15.1",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec 1.15.1",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.17.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
+dependencies = [
+ "console",
+ "number_prefix",
+ "portable-atomic",
+ "unicode-width",
+ "web-time",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.178"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "libredox"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "macro_rules_attribute"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "paste",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
+dependencies = [
+ "autocfg",
+ "rawpointer",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "monostate"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67"
+dependencies = [
+ "monostate-impl",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "monostate-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "napi"
+version = "2.16.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
+dependencies = [
+ "bitflags 2.10.0",
+ "ctor",
+ "napi-derive",
+ "napi-sys",
+ "once_cell",
+]
+
+[[package]]
+name = "napi-build"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1"
+
+[[package]]
+name = "napi-derive"
+version = "2.16.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
+dependencies = [
+ "cfg-if",
+ "convert_case",
+ "napi-derive-backend",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "napi-derive-backend"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
+dependencies = [
+ "convert_case",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "semver",
+ "syn",
+]
+
+[[package]]
+name = "napi-sys"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ndarray"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
+dependencies = [
+ "matrixmultiply",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "portable-atomic",
+ "portable-atomic-util",
+ "rawpointer",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "onig"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "openssl"
+version = "0.10.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
+dependencies = [
+ "bitflags 2.10.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ort"
+version = "2.0.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721"
+dependencies = [
+ "ndarray",
+ "ort-sys",
+ "smallvec 2.0.0-alpha.10",
+ "tracing",
+]
+
+[[package]]
+name = "ort-sys"
+version = "2.0.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890"
+dependencies = [
+ "flate2",
+ "pkg-config",
+ "sha2",
+ "tar",
+ "ureq 3.1.4",
+]
+
+[[package]]
+name = "osgrep-core"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "hf-hub",
+ "napi",
+ "napi-build",
+ "napi-derive",
+ "once_cell",
+ "ort",
+ "serde",
+ "serde_json",
+ "tokenizers",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "portable-atomic"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-cond"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
+dependencies = [
+ "either",
+ "itertools",
+ "rayon",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "reqwest"
+version = "0.12.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+dependencies = [
+ "bitflags 2.10.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "smallvec"
+version = "2.0.0-alpha.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b"
+
+[[package]]
+name = "socket2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "socks"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
+dependencies = [
+ "byteorder",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spm_precompiled"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
+dependencies = [
+ "base64 0.13.1",
+ "nom",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tokenizers"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476"
+dependencies = [
+ "ahash",
+ "aho-corasick",
+ "compact_str",
+ "dary_heap",
+ "derive_builder",
+ "esaxx-rs",
+ "getrandom 0.3.4",
+ "indicatif",
+ "itertools",
+ "log",
+ "macro_rules_attribute",
+ "monostate",
+ "onig",
+ "paste",
+ "rand",
+ "rayon",
+ "rayon-cond",
+ "regex",
+ "regex-syntax",
+ "serde",
+ "serde_json",
+ "spm_precompiled",
+ "thiserror",
+ "unicode-normalization-alignments",
+ "unicode-segmentation",
+ "unicode_categories",
+]
+
+[[package]]
+name = "tokio"
+version = "1.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-normalization-alignments"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
+dependencies = [
+ "smallvec 1.15.1",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
+dependencies = [
+ "base64 0.22.1",
+ "flate2",
+ "log",
+ "native-tls",
+ "once_cell",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "socks",
+ "url",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
+name = "ureq"
+version = "3.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
+dependencies = [
+ "base64 0.22.1",
+ "der",
+ "log",
+ "native-tls",
+ "percent-encoding",
+ "rustls-pki-types",
+ "socks",
+ "ureq-proto",
+ "utf-8",
+ "webpki-root-certs",
+]
+
+[[package]]
+name = "ureq-proto"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
+dependencies = [
+ "base64 0.22.1",
+ "http",
+ "httparse",
+ "log",
+]
+
+[[package]]
+name = "url"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.4",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/osgrep-core/Cargo.toml b/osgrep-core/Cargo.toml
new file mode 100644
index 00000000..228e24e7
--- /dev/null
+++ b/osgrep-core/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "osgrep-core"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+# NAPI-RS bindings
+napi = { version = "2", default-features = false, features = ["napi4"] }
+napi-derive = "2"
+
+# Utilities
+once_cell = "1"
+anyhow = "1"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+
+# HuggingFace tokenizers + model hub
+tokenizers = "0.21"
+hf-hub = "0.4"
+
+# ONNX Runtime (the only ML backend we need)
+ort = { version = "2.0.0-rc.10", default-features = true, features = ["download-binaries"] }
+
+[build-dependencies]
+napi-build = "2"
+
+[profile.release]
+lto = true
+opt-level = 3
diff --git a/osgrep-core/build.rs b/osgrep-core/build.rs
new file mode 100644
index 00000000..9fc23678
--- /dev/null
+++ b/osgrep-core/build.rs
@@ -0,0 +1,5 @@
+extern crate napi_build;
+
+fn main() {
+ napi_build::setup();
+}
diff --git a/osgrep-core/index.d.ts b/osgrep-core/index.d.ts
new file mode 100644
index 00000000..7b588f84
--- /dev/null
+++ b/osgrep-core/index.d.ts
@@ -0,0 +1,60 @@
+// TypeScript declarations for the `osgrep-core` native module (N-API).
+
+export interface DenseResult {
+ /** Flat array of embeddings [batch_size * 384] */
+ embeddings: number[];
+ /** Number of texts encoded */
+ count: number;
+}
+
+export interface ColbertPackedResult {
+ /** Packed embeddings as flat i8 array (all docs concatenated) */
+ embeddings: Int8Array | number[];
+ /** Token IDs for skiplist filtering */
+ tokenIds: Uint32Array | number[];
+ /** Number of tokens per document */
+ lengths: Uint32Array | number[];
+ /** Byte offsets into embeddings for each doc */
+ offsets: Uint32Array | number[];
+}
+
+export interface RerankResult {
+ /** Original indices of top-k documents */
+ indices: number[];
+ /** MaxSim scores for top-k documents */
+ scores: number[];
+}
+
+export interface EmbedResult {
+ /** Dense embeddings [batch_size * 384] */
+ dense: number[];
+ /** Packed ColBERT embeddings (i8) */
+ colbertEmbeddings: Int8Array | number[];
+ /** Token IDs for skiplist filtering (all docs concatenated) */
+ colbertTokenIds: Uint32Array | number[];
+ /** Token counts per document */
+ colbertLengths: Uint32Array | number[];
+ /** Byte offsets per document */
+ colbertOffsets: Uint32Array | number[];
+}
+
+export function initModels(denseRepo: string, colbertRepo: string): void;
+export function isInitialized(): boolean;
+
+export function embedDense(texts: string[]): DenseResult;
+export function embedColbertPacked(texts: string[]): ColbertPackedResult;
+
+/** Returns query embeddings as a flat array [seq_len * 48]. */
+export function encodeQueryColbert(query: string): Float64Array | number[];
+
+export function rerankColbert(
+ queryEmbeddings: Float64Array | number[],
+ docEmbeddings: Int8Array | number[],
+ docTokenIds: Uint32Array | number[],
+ docLengths: number[] | Uint32Array,
+ docOffsets: number[] | Uint32Array,
+ candidateIndices: number[] | Uint32Array,
+ topK: number,
+): RerankResult;
+
+export function embedBatch(texts: string[]): EmbedResult;
diff --git a/osgrep-core/index.js b/osgrep-core/index.js
new file mode 100644
index 00000000..39323283
--- /dev/null
+++ b/osgrep-core/index.js
@@ -0,0 +1,124 @@
+// osgrep-core: Native embedding and reranking
+// Auto-loads the correct binary for the current platform
+
+import { createRequire } from 'module';
+import { existsSync } from 'fs';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { execSync } from 'child_process';
+
+const require = createRequire(import.meta.url);
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const { platform, arch } = process;
+
+let nativeBinding = null;
+let loadError = null;
+
+function isMusl() {
+ if (!process.report || typeof process.report.getReport !== 'function') {
+ try {
+ const lddPath = execSync('which ldd').toString().trim();
+ return require('fs').readFileSync(lddPath, 'utf8').includes('musl');
+ } catch (e) {
+ return true;
+ }
+ } else {
+ const { glibcVersionRuntime } = process.report.getReport().header;
+ return !glibcVersionRuntime;
+ }
+}
+
+function tryLoad(localPath, packageName) {
+ if (existsSync(join(__dirname, localPath))) {
+ return require(`./${localPath}`);
+ }
+ return require(packageName);
+}
+
+switch (platform) {
+ case 'darwin':
+ switch (arch) {
+ case 'arm64':
+ try {
+ nativeBinding = tryLoad('osgrep-core.darwin-arm64.node', '@osgrep-core/darwin-arm64');
+ } catch (e) {
+ loadError = e;
+ }
+ break;
+ case 'x64':
+ try {
+ nativeBinding = tryLoad('osgrep-core.darwin-x64.node', '@osgrep-core/darwin-x64');
+ } catch (e) {
+ loadError = e;
+ }
+ break;
+ default:
+ throw new Error(`Unsupported architecture on macOS: ${arch}`);
+ }
+ break;
+ case 'linux':
+ switch (arch) {
+ case 'x64':
+ if (isMusl()) {
+ try {
+ nativeBinding = tryLoad('osgrep-core.linux-x64-musl.node', '@osgrep-core/linux-x64-musl');
+ } catch (e) {
+ loadError = e;
+ }
+ } else {
+ try {
+ nativeBinding = tryLoad('osgrep-core.linux-x64-gnu.node', '@osgrep-core/linux-x64-gnu');
+ } catch (e) {
+ loadError = e;
+ }
+ }
+ break;
+ default:
+ throw new Error(`Unsupported architecture on Linux: ${arch}`);
+ }
+ break;
+ case 'win32':
+ switch (arch) {
+ case 'x64':
+ try {
+ nativeBinding = tryLoad('osgrep-core.win32-x64-msvc.node', '@osgrep-core/win32-x64-msvc');
+ } catch (e) {
+ loadError = e;
+ }
+ break;
+ default:
+ throw new Error(`Unsupported architecture on Windows: ${arch}`);
+ }
+ break;
+ default:
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
+}
+
+if (!nativeBinding) {
+ if (loadError) {
+ throw loadError;
+ }
+ throw new Error(`Failed to load native binding`);
+}
+
+// Clean API exports
+export const {
+ // Initialization
+ initModels,
+ isInitialized,
+
+ // Dense embeddings (384-dim)
+ embedDense,
+
+ // ColBERT embeddings (48-dim per token, packed)
+ embedColbertPacked,
+
+ // ColBERT reranking
+ encodeQueryColbert,
+ rerankColbert,
+
+ // Convenience: both embeddings in one call
+ embedBatch,
+} = nativeBinding;
diff --git a/osgrep-core/package.json b/osgrep-core/package.json
new file mode 100644
index 00000000..69da5677
--- /dev/null
+++ b/osgrep-core/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "osgrep-core",
+ "version": "0.1.0",
+ "description": "Native Rust core for osgrep (embeddings + reranking via ONNX)",
+ "type": "module",
+ "main": "index.js",
+ "types": "index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Ryandonofrio3/osgrep.git",
+ "directory": "osgrep-core"
+ },
+ "license": "Apache-2.0",
+ "files": [
+ "index.js",
+ "index.d.ts"
+ ],
+ "napi": {
+ "name": "osgrep-core",
+ "triples": {
+ "defaults": false,
+ "additional": [
+ "aarch64-apple-darwin",
+ "x86_64-apple-darwin",
+ "x86_64-unknown-linux-gnu",
+ "x86_64-unknown-linux-musl",
+ "x86_64-pc-windows-msvc"
+ ]
+ }
+ },
+ "scripts": {
+ "build": "napi build --platform",
+ "build:release": "napi build --platform --release",
+ "prepublishOnly": "napi prepublish -t npm"
+ },
+ "devDependencies": {
+ "@napi-rs/cli": "^3.0.0-alpha.63"
+ },
+ "optionalDependencies": {
+ "@osgrep-core/darwin-arm64": "0.1.0",
+ "@osgrep-core/darwin-x64": "0.1.0",
+ "@osgrep-core/linux-x64-gnu": "0.1.0",
+ "@osgrep-core/linux-x64-musl": "0.1.0",
+ "@osgrep-core/win32-x64-msvc": "0.1.0"
+ }
+}
diff --git a/osgrep-core/src/colbert_ort.rs b/osgrep-core/src/colbert_ort.rs
new file mode 100644
index 00000000..1038c7bd
--- /dev/null
+++ b/osgrep-core/src/colbert_ort.rs
@@ -0,0 +1,592 @@
+use ort::session::{Session, builder::GraphOptimizationLevel};
+use ort::value::Value;
+use tokenizers::Tokenizer;
+use hf_hub::{api::sync::Api, Repo, RepoType};
+use std::collections::HashSet;
+
+fn log_native(msg: impl AsRef) {
+ // Intentionally no-op: native logging was polluting CLI output.
+ // If you need debugging, add structured logging at the JS layer instead.
+ let _ = msg.as_ref();
+}
+
+// ColBERT special tokens (these get added during fine-tuning)
+const QUERY_MARKER: &str = "[Q]";
+const DOC_MARKER: &str = "[D]";
+const QUERY_MAXLEN: usize = 32;
+// This directly caps how much of each chunk the reranker can "see".
+// Keep this in sync with chunk sizing; very large values quickly blow up MaxSim cost.
+const DOC_MAXLEN: usize = 96;
+
+pub struct ColbertEncoderOrt {
+ session: Session,
+ tokenizer: Tokenizer,
+ hidden_size: usize,
+ // Special token IDs
+ cls_id: u32,
+ sep_id: u32,
+ mask_id: u32,
+ pad_id: u32,
+ query_marker_id: Option,
+ doc_marker_id: Option,
+ // Skip list for MaxSim (punctuation, special tokens to ignore)
+ skip_ids: HashSet,
+}
+
+impl ColbertEncoderOrt {
+ pub fn load_from_hf(repo_id: &str, hidden_size: usize) -> anyhow::Result {
+ log_native(format!("[ColBERT-ORT] Downloading model from HF hub: {}", repo_id));
+
+ let api = Api::new()?;
+ let repo = api.repo(Repo::new(repo_id.to_string(), RepoType::Model));
+
+ // Download model and tokenizer files
+ // Try int8 quantized model first for speed, fall back to fp32
+ let model_path = repo.get("onnx/model_int8.onnx")
+ .or_else(|_| repo.get("onnx/model.onnx"))?;
+ let tokenizer_path = repo.get("tokenizer.json")?;
+
+ // Try to load skiplist
+ let skip_ids = match repo.get("skiplist.json") {
+ Ok(skiplist_path) => {
+ let content = std::fs::read_to_string(&skiplist_path)?;
+ let ids: Vec = serde_json::from_str(&content)?;
+ log_native(format!("[ColBERT-ORT] Loaded skiplist with {} token IDs", ids.len()));
+ ids.into_iter().collect()
+ }
+ Err(_) => {
+ log_native("[ColBERT-ORT] No skiplist found, using empty");
+ HashSet::new()
+ }
+ };
+
+ log_native(format!("[ColBERT-ORT] Loading model from {:?}", model_path));
+
+ let session = Session::builder()?
+ .with_optimization_level(GraphOptimizationLevel::Level3)?
+ .with_intra_threads(4)? // Reduced from 8 to avoid thread contention
+ .commit_from_file(&model_path)?;
+
+ let tokenizer = Tokenizer::from_file(&tokenizer_path)
+ .map_err(|e| anyhow::anyhow!("Failed to load tokenizer: {}", e))?;
+
+ // Get special token IDs
+ let vocab = tokenizer.get_vocab(true);
+
+ let cls_id = *vocab.get("[CLS]").unwrap_or(&0);
+ let sep_id = *vocab.get("[SEP]").unwrap_or(&0);
+ let mask_id = *vocab.get("[MASK]").unwrap_or(&0);
+ let pad_id = *vocab.get("[PAD]").unwrap_or(&mask_id); // Use MASK as PAD if no PAD
+
+ // ColBERT marker tokens (may not exist in all tokenizers)
+ let query_marker_id = vocab.get(QUERY_MARKER).copied();
+ let doc_marker_id = vocab.get(DOC_MARKER).copied();
+
+ log_native(format!("[ColBERT-ORT] Token IDs: CLS={}, SEP={}, MASK={}, PAD={}",
+ cls_id, sep_id, mask_id, pad_id));
+ log_native(format!("[ColBERT-ORT] Marker IDs: [Q]={:?}, [D]={:?}",
+ query_marker_id, doc_marker_id));
+ log_native("[ColBERT-ORT] Model loaded successfully");
+
+ Ok(Self {
+ session,
+ tokenizer,
+ hidden_size,
+ cls_id,
+ sep_id,
+ mask_id,
+ pad_id,
+ query_marker_id,
+ doc_marker_id,
+ skip_ids,
+ })
+ }
+
+ /// Encode a query with ColBERT format: [CLS] [Q] tokens... [SEP] [MASK]...
+ /// Pads with [MASK] tokens to QUERY_MAXLEN for query expansion
+ pub fn encode_query(&mut self, text: &str) -> anyhow::Result {
+ // If the tokenizer doesn't have a dedicated [Q] token, mimic the Python
+ // harness behavior by prefixing the literal string "[Q] ".
+ let text_for_tokenizer;
+ let text = if self.query_marker_id.is_none() && !text.starts_with("[Q]") {
+ text_for_tokenizer = format!("[Q] {}", text);
+ text_for_tokenizer.as_str()
+ } else {
+ text
+ };
+
+ // Tokenize without special tokens
+ let encoding = self.tokenizer
+ .encode(text, false)
+ .map_err(|e| anyhow::anyhow!("Tokenization failed: {}", e))?;
+
+ let token_ids = encoding.get_ids();
+
+ // Build sequence: [CLS] [Q]? tokens... [SEP] [MASK]...
+ let mut final_ids: Vec = Vec::with_capacity(QUERY_MAXLEN);
+ final_ids.push(self.cls_id);
+
+ if let Some(q_id) = self.query_marker_id {
+ final_ids.push(q_id);
+ }
+
+ // Add tokens (truncate if needed, leaving room for SEP)
+ let max_tokens = QUERY_MAXLEN - final_ids.len() - 1; // -1 for SEP
+ for &id in token_ids.iter().take(max_tokens) {
+ final_ids.push(id);
+ }
+
+ final_ids.push(self.sep_id);
+
+ // Pad with [MASK] for query expansion
+ while final_ids.len() < QUERY_MAXLEN {
+ final_ids.push(self.mask_id);
+ }
+
+ // Create attention mask (all 1s, MASK tokens are attended)
+ let attention_mask: Vec = vec![1i64; final_ids.len()];
+ let input_ids: Vec = final_ids.iter().map(|&id| id as i64).collect();
+
+ let seq_len = input_ids.len();
+
+ // Create tensors
+ let input_ids_tensor = Value::from_array(([1usize, seq_len], input_ids))?;
+ let attention_mask_tensor = Value::from_array(([1usize, seq_len], attention_mask))?;
+
+ // Run inference
+ let outputs = self.session.run(ort::inputs![
+ "input_ids" => input_ids_tensor,
+ "attention_mask" => attention_mask_tensor
+ ])?;
+
+ // Get embeddings [1, seq_len, hidden_size]
+ let embeddings_tensor = outputs[0].try_extract_tensor::()?;
+ let embeddings_data: &[f32] = embeddings_tensor.1;
+
+ // Copy to owned vec and L2 normalize each token
+ let mut embeddings = vec![0.0f32; seq_len * self.hidden_size];
+ for s in 0..seq_len {
+ let src_offset = s * self.hidden_size;
+ let dst_offset = s * self.hidden_size;
+
+ // L2 normalize
+ let mut sum_sq = 0.0f32;
+ for d in 0..self.hidden_size {
+ let val = embeddings_data[src_offset + d];
+ sum_sq += val * val;
+ }
+ let norm = sum_sq.sqrt().max(1e-12);
+
+ for d in 0..self.hidden_size {
+ embeddings[dst_offset + d] = embeddings_data[src_offset + d] / norm;
+ }
+ }
+
+ Ok(QueryEmbedding {
+ embeddings,
+ seq_len,
+ hidden_size: self.hidden_size,
+ })
+ }
+
+ /// Encode documents in a batch: [CLS] [D]? tokens... [SEP]
+ pub fn encode_docs(&mut self, texts: &[String]) -> anyhow::Result> {
+ if texts.is_empty() {
+ return Ok(vec![]);
+ }
+
+ let batch_size = texts.len();
+
+ // Tokenize all texts
+ let mut all_token_ids: Vec> = Vec::with_capacity(batch_size);
+ let mut max_len = 0usize;
+
+ for text in texts {
+ // If the tokenizer doesn't have a dedicated [D] token, mimic the Python
+ // harness behavior by prefixing the literal string "[D] ".
+ let text_for_tokenizer;
+ let text = if self.doc_marker_id.is_none() && !text.starts_with("[D]") {
+ text_for_tokenizer = format!("[D] {}", text);
+ text_for_tokenizer.as_str()
+ } else {
+ text.as_str()
+ };
+
+ let encoding = self.tokenizer
+ .encode(text, false)
+ .map_err(|e| anyhow::anyhow!("Tokenization failed: {}", e))?;
+
+ let token_ids = encoding.get_ids();
+
+ // Build sequence: [CLS] [D]? tokens... [SEP]
+ let mut final_ids: Vec = Vec::with_capacity(DOC_MAXLEN);
+ final_ids.push(self.cls_id);
+
+ if let Some(d_id) = self.doc_marker_id {
+ final_ids.push(d_id);
+ }
+
+ // Add tokens (truncate if needed)
+ let max_tokens = DOC_MAXLEN - final_ids.len() - 1;
+ for &id in token_ids.iter().take(max_tokens) {
+ final_ids.push(id);
+ }
+
+ final_ids.push(self.sep_id);
+
+ if final_ids.len() > max_len {
+ max_len = final_ids.len();
+ }
+
+ all_token_ids.push(final_ids);
+ }
+
+ // Pad to max_len and create batched tensors
+ let mut input_ids_vec = vec![0i64; batch_size * max_len];
+ let mut attention_mask_vec = vec![0i64; batch_size * max_len];
+ let mut real_lengths: Vec = Vec::with_capacity(batch_size);
+
+ for (i, ids) in all_token_ids.iter().enumerate() {
+ let real_len = ids.len();
+ real_lengths.push(real_len);
+
+ for (j, &id) in ids.iter().enumerate() {
+ input_ids_vec[i * max_len + j] = id as i64;
+ attention_mask_vec[i * max_len + j] = 1;
+ }
+ // Remaining positions stay 0 (padded)
+ }
+
+ // Create tensors
+ let input_ids = Value::from_array(([batch_size, max_len], input_ids_vec))?;
+ let attention_mask = Value::from_array(([batch_size, max_len], attention_mask_vec))?;
+
+ // Run inference
+ let outputs = self.session.run(ort::inputs![
+ "input_ids" => input_ids,
+ "attention_mask" => attention_mask
+ ])?;
+
+ // Get embeddings [batch, max_len, hidden_size]
+ let embeddings_tensor = outputs[0].try_extract_tensor::()?;
+ let embeddings_data: &[f32] = embeddings_tensor.1;
+
+ // Extract per-document embeddings with L2 normalization
+ let mut results: Vec = Vec::with_capacity(batch_size);
+
+ for b in 0..batch_size {
+ let real_len = real_lengths[b];
+ let token_ids = &all_token_ids[b];
+ let mut embeddings = vec![0.0f32; real_len * self.hidden_size];
+
+ for s in 0..real_len {
+ let src_offset = b * max_len * self.hidden_size + s * self.hidden_size;
+ let dst_offset = s * self.hidden_size;
+
+ // L2 normalize each token embedding
+ let mut sum_sq = 0.0f32;
+ for d in 0..self.hidden_size {
+ let val = embeddings_data[src_offset + d];
+ sum_sq += val * val;
+ }
+ let norm = sum_sq.sqrt().max(1e-12);
+
+ for d in 0..self.hidden_size {
+ embeddings[dst_offset + d] = embeddings_data[src_offset + d] / norm;
+ }
+ }
+
+ results.push(DocEmbedding {
+ embeddings,
+ token_ids: token_ids.clone(),
+ seq_len: real_len,
+ hidden_size: self.hidden_size,
+ });
+ }
+
+ Ok(results)
+ }
+
+ /// MaxSim scoring: for each query token, find max similarity with doc tokens, sum
+ pub fn max_sim(&self, query: &QueryEmbedding, doc: &DocEmbedding) -> f32 {
+ let mut total_score = 0.0f32;
+
+ for q in 0..query.seq_len {
+ let q_offset = q * query.hidden_size;
+ let mut max_dot = f32::NEG_INFINITY;
+
+ for d in 0..doc.seq_len {
+ // Skip tokens in skiplist (punctuation, special tokens)
+ if self.skip_ids.contains(&doc.token_ids[d]) {
+ continue;
+ }
+
+ let d_offset = d * doc.hidden_size;
+
+ // Dot product (vectors are already L2 normalized)
+ let mut dot = 0.0f32;
+ for k in 0..query.hidden_size {
+ dot += query.embeddings[q_offset + k] * doc.embeddings[d_offset + k];
+ }
+
+ if dot > max_dot {
+ max_dot = dot;
+ }
+ }
+
+ if max_dot > f32::NEG_INFINITY {
+ total_score += max_dot;
+ }
+ }
+
+ total_score
+ }
+
+ /// Rerank documents against a query, return sorted indices and scores
+ pub fn rerank(&mut self, query: &str, docs: &[String], top_k: usize) -> anyhow::Result {
+ use std::time::Instant;
+
+ // Encode query
+ let t0 = Instant::now();
+ let query_emb = self.encode_query(query)?;
+ let query_time = t0.elapsed();
+
+ // Encode docs in batches (larger batch = better throughput)
+ let t1 = Instant::now();
+ let batch_size = 64;
+ let mut all_doc_embs: Vec = Vec::with_capacity(docs.len());
+
+ for chunk in docs.chunks(batch_size) {
+ let chunk_vec: Vec = chunk.to_vec();
+ let embs = self.encode_docs(&chunk_vec)?;
+ all_doc_embs.extend(embs);
+ }
+ let doc_time = t1.elapsed();
+
+ // Score all docs
+ let t2 = Instant::now();
+ let mut scores: Vec<(usize, f32)> = all_doc_embs
+ .iter()
+ .enumerate()
+ .map(|(i, doc_emb)| (i, self.max_sim(&query_emb, doc_emb)))
+ .collect();
+ let score_time = t2.elapsed();
+
+ // Log timing once
+ static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
+ if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
+ log_native(format!(
+ "[ColBERT-ORT] Timing: query={:?} docs={:?} maxsim={:?}",
+ query_time, doc_time, score_time
+ ));
+ }
+
+ // Sort by score descending
+ scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
+
+ // Take top_k
+ let k = std::cmp::min(top_k, scores.len());
+ let top_indices: Vec = scores[..k].iter().map(|(i, _)| *i as u32).collect();
+ let top_scores: Vec = scores[..k].iter().map(|(_, s)| *s as f64).collect();
+ let checksum: f64 = scores.iter().map(|(_, s)| *s as f64).sum();
+
+ Ok(RerankResultOrt {
+ indices: top_indices,
+ scores: top_scores,
+ checksum,
+ })
+ }
+}
+
+#[derive(Clone)]
+pub struct QueryEmbedding {
+ pub embeddings: Vec, // [seq_len * hidden_size] flattened
+ pub seq_len: usize,
+ pub hidden_size: usize,
+}
+
+#[derive(Clone)]
+pub struct DocEmbedding {
+ pub embeddings: Vec, // [seq_len * hidden_size] flattened
+ pub token_ids: Vec, // For skiplist filtering
+ pub seq_len: usize,
+ pub hidden_size: usize,
+}
+
+pub struct RerankResultOrt {
+ pub indices: Vec,
+ pub scores: Vec,
+ pub checksum: f64,
+}
+
+/// Packed document embeddings for storage/retrieval
+/// All embeddings are flattened into a single buffer with offsets
+#[derive(Clone)]
+pub struct PackedDocEmbeddings {
+ /// Flattened embeddings: all docs concatenated [sum(lengths) * hidden_size]
+ pub embeddings: Vec,
+ /// Token IDs for skiplist: all docs concatenated [sum(lengths)]
+ pub token_ids: Vec,
+ /// Number of tokens per document
+ pub lengths: Vec,
+ /// Byte offsets into embeddings buffer for each doc (for fast lookup)
+ pub offsets: Vec,
+ /// Hidden dimension
+ pub hidden_size: usize,
+}
+
+impl ColbertEncoderOrt {
+ /// Encode documents and return packed embeddings for storage
+ /// This is for INDEX TIME - encode once, store, reuse at query time
+ pub fn encode_docs_packed(&mut self, texts: &[String]) -> anyhow::Result {
+ if texts.is_empty() {
+ return Ok(PackedDocEmbeddings {
+ embeddings: vec![],
+ token_ids: vec![],
+ lengths: vec![],
+ offsets: vec![],
+ hidden_size: self.hidden_size,
+ });
+ }
+
+ // Encode in batches
+ let batch_size = 64;
+ let mut all_embeddings: Vec = Vec::new();
+ let mut all_token_ids: Vec = Vec::new();
+ let mut lengths: Vec = Vec::with_capacity(texts.len());
+ let mut offsets: Vec = Vec::with_capacity(texts.len());
+
+ for chunk in texts.chunks(batch_size) {
+ let chunk_vec: Vec = chunk.to_vec();
+ let doc_embs = self.encode_docs(&chunk_vec)?;
+
+ for doc in doc_embs {
+ offsets.push(all_embeddings.len() as u32);
+ lengths.push(doc.seq_len as u32);
+ all_embeddings.extend(doc.embeddings);
+ all_token_ids.extend(doc.token_ids);
+ }
+ }
+
+ Ok(PackedDocEmbeddings {
+ embeddings: all_embeddings,
+ token_ids: all_token_ids,
+ lengths,
+ offsets,
+ hidden_size: self.hidden_size,
+ })
+ }
+
+ /// Score a query against pre-computed packed embeddings
+ /// This is for QUERY TIME - no doc encoding needed
+ pub fn score_packed(
+ &self,
+ query_emb: &QueryEmbedding,
+ packed: &PackedDocEmbeddings,
+ doc_indices: &[usize], // Which docs from packed to score
+ ) -> Vec {
+ let mut scores = Vec::with_capacity(doc_indices.len());
+
+ for &doc_idx in doc_indices {
+ if doc_idx >= packed.lengths.len() {
+ scores.push(0.0);
+ continue;
+ }
+
+ let doc_len = packed.lengths[doc_idx] as usize;
+ let emb_offset = packed.offsets[doc_idx] as usize;
+ let token_offset: usize = packed.offsets[..doc_idx]
+ .iter()
+ .zip(&packed.lengths[..doc_idx])
+ .map(|(&off, &len)| len as usize)
+ .sum();
+
+ // MaxSim scoring
+ let mut total_score = 0.0f32;
+
+ for q in 0..query_emb.seq_len {
+ let q_offset = q * query_emb.hidden_size;
+ let mut max_dot = f32::NEG_INFINITY;
+
+ for d in 0..doc_len {
+ // Check skiplist
+ let token_id = packed.token_ids[token_offset + d];
+ if self.skip_ids.contains(&token_id) {
+ continue;
+ }
+
+ let d_offset = emb_offset + d * packed.hidden_size;
+
+ // Dot product
+ let mut dot = 0.0f32;
+ for k in 0..query_emb.hidden_size {
+ dot += query_emb.embeddings[q_offset + k]
+ * packed.embeddings[d_offset + k];
+ }
+
+ if dot > max_dot {
+ max_dot = dot;
+ }
+ }
+
+ if max_dot > f32::NEG_INFINITY {
+ total_score += max_dot;
+ }
+ }
+
+ scores.push(total_score);
+ }
+
+ scores
+ }
+
+ /// Rerank using pre-computed packed embeddings (FAST query-time path)
+ pub fn rerank_from_packed(
+ &mut self,
+ query: &str,
+ packed: &PackedDocEmbeddings,
+ doc_indices: &[usize],
+ top_k: usize,
+ ) -> anyhow::Result {
+ use std::time::Instant;
+
+ // Encode query only
+ let t0 = Instant::now();
+ let query_emb = self.encode_query(query)?;
+ let query_time = t0.elapsed();
+
+ // Score against packed embeddings
+ let t1 = Instant::now();
+ let raw_scores = self.score_packed(&query_emb, packed, doc_indices);
+ let score_time = t1.elapsed();
+
+ // Log timing
+ static LOGGED_PACKED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
+ if !LOGGED_PACKED.swap(true, std::sync::atomic::Ordering::Relaxed) {
+ log_native(format!(
+ "[ColBERT-ORT] Packed timing: query={:?} maxsim={:?}",
+ query_time, score_time
+ ));
+ }
+
+ // Sort by score descending
+ let mut indexed_scores: Vec<(usize, f32)> = raw_scores
+ .iter()
+ .enumerate()
+ .map(|(i, &s)| (doc_indices[i], s))
+ .collect();
+ indexed_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
+
+ // Take top_k
+ let k = std::cmp::min(top_k, indexed_scores.len());
+ let top_indices: Vec = indexed_scores[..k].iter().map(|(i, _)| *i as u32).collect();
+ let top_scores: Vec = indexed_scores[..k].iter().map(|(_, s)| *s as f64).collect();
+ let checksum: f64 = raw_scores.iter().map(|&s| s as f64).sum();
+
+ Ok(RerankResultOrt {
+ indices: top_indices,
+ scores: top_scores,
+ checksum,
+ })
+ }
+}
diff --git a/osgrep-core/src/dense_ort.rs b/osgrep-core/src/dense_ort.rs
new file mode 100644
index 00000000..85b89467
--- /dev/null
+++ b/osgrep-core/src/dense_ort.rs
@@ -0,0 +1,208 @@
+use ort::session::{Session, builder::GraphOptimizationLevel};
+use ort::value::Value;
+use tokenizers::Tokenizer;
+use hf_hub::{api::sync::Api, Repo, RepoType};
+
+fn log_native(msg: impl AsRef) {
+ // Intentionally no-op: native logging was polluting CLI output.
+ // If you need debugging, add structured logging at the JS layer instead.
+ let _ = msg.as_ref();
+}
+
+pub struct DenseEncoderOrt {
+ session: Session,
+ tokenizer: Tokenizer,
+ hidden_size: usize,
+}
+
+impl DenseEncoderOrt {
+ /// Load ONNX model and tokenizer from HuggingFace Hub
+ /// repo_id: HF repo like "onnx-community/granite-embedding-30m-english-ONNX"
+ pub fn load_from_hf(repo_id: &str, hidden_size: usize) -> anyhow::Result {
+ log_native(format!("[ORT] Downloading model from HF hub: {}", repo_id));
+
+ let api = Api::new()?;
+ let repo = api.repo(Repo::new(repo_id.to_string(), RepoType::Model));
+
+ // Download model and tokenizer files
+ // ONNX model is in onnx/ subdirectory for onnx-community repos
+ // Also download the external data file if it exists
+ let model_path = repo.get("onnx/model.onnx")?;
+ let _ = repo.get("onnx/model.onnx_data"); // External data, ignore if not found
+ let tokenizer_path = repo.get("tokenizer.json")?;
+
+ log_native(format!("[ORT] Loading model from {:?}", model_path));
+
+ // Initialize ONNX Runtime session with CPU provider
+ let session = Session::builder()?
+ .with_optimization_level(GraphOptimizationLevel::Level3)?
+ .with_intra_threads(4)?
+ .commit_from_file(&model_path)?;
+
+ // Load tokenizer
+ let mut tokenizer = Tokenizer::from_file(&tokenizer_path)
+ .map_err(|e| anyhow::anyhow!("Failed to load tokenizer: {}", e))?;
+
+ // Configure truncation/padding (same as Candle)
+ let max_len = 256usize;
+ tokenizer.with_truncation(Some(tokenizers::TruncationParams {
+ max_length: max_len,
+ ..Default::default()
+ })).map_err(|e| anyhow::anyhow!("Failed to set truncation: {}", e))?;
+
+ tokenizer.with_padding(Some(tokenizers::PaddingParams {
+ strategy: tokenizers::PaddingStrategy::BatchLongest,
+ ..Default::default()
+ }));
+
+ log_native(format!("[ORT] Tokenizer configured with max_seq_len={}", max_len));
+ log_native("[ORT] Model loaded successfully");
+
+ Ok(Self {
+ session,
+ tokenizer,
+ hidden_size,
+ })
+ }
+
+ /// Load ONNX model and tokenizer from local paths
+ pub fn load(model_path: &str, tokenizer_path: &str, hidden_size: usize) -> anyhow::Result {
+ log_native(format!("[ORT] Loading model from {}", model_path));
+
+ // Initialize ONNX Runtime session with CPU provider
+ let session = Session::builder()?
+ .with_optimization_level(GraphOptimizationLevel::Level3)?
+ .with_intra_threads(4)?
+ .commit_from_file(model_path)?;
+
+ // Load tokenizer
+ let mut tokenizer = Tokenizer::from_file(tokenizer_path)
+ .map_err(|e| anyhow::anyhow!("Failed to load tokenizer: {}", e))?;
+
+ let max_len = 256usize;
+ tokenizer.with_truncation(Some(tokenizers::TruncationParams {
+ max_length: max_len,
+ ..Default::default()
+ })).map_err(|e| anyhow::anyhow!("Failed to set truncation: {}", e))?;
+
+ tokenizer.with_padding(Some(tokenizers::PaddingParams {
+ strategy: tokenizers::PaddingStrategy::BatchLongest,
+ ..Default::default()
+ }));
+
+ log_native(format!("[ORT] Tokenizer configured with max_seq_len={}", max_len));
+ log_native("[ORT] Model loaded successfully");
+
+ Ok(Self {
+ session,
+ tokenizer,
+ hidden_size,
+ })
+ }
+
+ pub fn encode_batch(&mut self, texts: Vec, normalize: bool) -> anyhow::Result> {
+ if texts.is_empty() {
+ return Err(anyhow::anyhow!("Empty input texts"));
+ }
+
+ // Tokenize
+ let encodings = self.tokenizer
+ .encode_batch(texts.clone(), true)
+ .map_err(|e| anyhow::anyhow!("Tokenization failed: {}", e))?;
+
+ let max_len = encodings.iter().map(|e| e.get_ids().len()).max().unwrap_or(0);
+ let batch_size = encodings.len();
+
+ // Log once
+ static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
+ if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
+ log_native(format!("[ORT] First batch: batch_size={} max_seq_len={}", batch_size, max_len));
+ }
+
+ // Prepare input tensors
+ let mut input_ids_vec = vec![0i64; batch_size * max_len];
+ let mut attention_mask_vec = vec![0i64; batch_size * max_len];
+ let mut token_type_ids_vec = vec![0i64; batch_size * max_len];
+
+ for (i, encoding) in encodings.iter().enumerate() {
+ let ids = encoding.get_ids();
+ let mask = encoding.get_attention_mask();
+ let type_ids = encoding.get_type_ids();
+
+ for (j, &id) in ids.iter().enumerate() {
+ input_ids_vec[i * max_len + j] = id as i64;
+ }
+ for (j, &m) in mask.iter().enumerate() {
+ attention_mask_vec[i * max_len + j] = m as i64;
+ }
+ for (j, &t) in type_ids.iter().enumerate() {
+ token_type_ids_vec[i * max_len + j] = t as i64;
+ }
+ }
+
+ // Create ORT tensors - pass Vec directly, not slice
+ let input_ids = Value::from_array(([batch_size, max_len], input_ids_vec))?;
+ let attention_mask_tensor = Value::from_array(([batch_size, max_len], attention_mask_vec.clone()))?;
+ let token_type_ids = Value::from_array(([batch_size, max_len], token_type_ids_vec))?;
+
+ // Run inference
+ let outputs = self.session.run(ort::inputs![
+ "input_ids" => input_ids,
+ "attention_mask" => attention_mask_tensor,
+ "token_type_ids" => token_type_ids
+ ])?;
+
+ // Get the last_hidden_state output (typically first output)
+ let embeddings_tensor = outputs[0].try_extract_tensor::()?;
+ let embeddings_data: &[f32] = embeddings_tensor.1;
+
+ // Mean pooling
+ let mut pooled = vec![0.0f32; batch_size * self.hidden_size];
+ for i in 0..batch_size {
+ let mut sum_hidden = vec![0.0f32; self.hidden_size];
+ let mut sum_mask = 0.0f32;
+
+ for j in 0..max_len {
+ let mask_val = attention_mask_vec[i * max_len + j] as f32;
+ sum_mask += mask_val;
+
+ for k in 0..self.hidden_size {
+ let emb_val = embeddings_data[i * max_len * self.hidden_size + j * self.hidden_size + k];
+ sum_hidden[k] += emb_val * mask_val;
+ }
+ }
+
+ let denom = sum_mask.max(1e-9);
+ for k in 0..self.hidden_size {
+ pooled[i * self.hidden_size + k] = sum_hidden[k] / denom;
+ }
+ }
+
+ // L2 normalize if requested
+ if normalize {
+ for i in 0..batch_size {
+ let start = i * self.hidden_size;
+ let end = start + self.hidden_size;
+ let slice = &mut pooled[start..end];
+
+ let norm = slice.iter().map(|x| x * x).sum::().sqrt().max(1e-12);
+ for val in slice.iter_mut() {
+ *val /= norm;
+ }
+ }
+ }
+
+ Ok(pooled)
+ }
+
+ pub fn compute_checksum(&mut self, texts: Vec, normalize: bool) -> anyhow::Result {
+ let embeddings = self.encode_batch(texts, normalize)?;
+ let checksum: f64 = embeddings.iter().map(|&x| x as f64).sum();
+ Ok(checksum)
+ }
+
+ /// Get the hidden size (embedding dimension)
+ pub fn hidden_size(&self) -> usize {
+ self.hidden_size
+ }
+}
diff --git a/osgrep-core/src/lib.rs b/osgrep-core/src/lib.rs
new file mode 100644
index 00000000..db8fae8a
--- /dev/null
+++ b/osgrep-core/src/lib.rs
@@ -0,0 +1,265 @@
+//! osgrep-core: Fast embedding and reranking via ONNX Runtime
+//!
+//! This is the native performance core of osgrep. It provides:
+//! - Dense embeddings (384-dim, granite-30m)
+//! - ColBERT token embeddings (48-dim, mxbai-edge-colbert-17m)
+//! - MaxSim reranking with pre-indexed documents
+
+#[macro_use]
+extern crate napi_derive;
+
+use napi::bindgen_prelude::*;
+use once_cell::sync::OnceCell;
+use std::sync::Mutex;
+
+mod dense_ort;
+mod colbert_ort;
+
+use dense_ort::DenseEncoderOrt;
+use colbert_ort::{ColbertEncoderOrt, PackedDocEmbeddings};
+
+// =============================================================================
+// Global Model Storage (initialized once, reused)
+// =============================================================================
+
+static DENSE_MODEL: OnceCell> = OnceCell::new();
+static COLBERT_MODEL: OnceCell> = OnceCell::new();
+
+// =============================================================================
+// Initialization
+// =============================================================================
+
+/// Initialize both models. Call once at startup.
+///
+/// dense_repo: HF repo like "onnx-community/granite-embedding-30m-english-ONNX"
+/// colbert_repo: HF repo like "ryandono/mxbai-edge-colbert-v0-17m-onnx-int8"
+#[napi]
+pub fn init_models(dense_repo: String, colbert_repo: String) -> Result<()> {
+ // Initialize dense model
+ if DENSE_MODEL.get().is_none() {
+ let encoder = DenseEncoderOrt::load_from_hf(&dense_repo, 384)
+ .map_err(|e| Error::from_reason(format!("Failed to load dense model: {:?}", e)))?;
+ DENSE_MODEL.set(Mutex::new(encoder))
+ .map_err(|_| Error::from_reason("Dense model already initialized"))?;
+ }
+
+ // Initialize ColBERT model
+ if COLBERT_MODEL.get().is_none() {
+ let encoder = ColbertEncoderOrt::load_from_hf(&colbert_repo, 48)
+ .map_err(|e| Error::from_reason(format!("Failed to load ColBERT model: {:?}", e)))?;
+ COLBERT_MODEL.set(Mutex::new(encoder))
+ .map_err(|_| Error::from_reason("ColBERT model already initialized"))?;
+ }
+
+ Ok(())
+}
+
+/// Check if models are initialized
+#[napi]
+pub fn is_initialized() -> bool {
+ DENSE_MODEL.get().is_some() && COLBERT_MODEL.get().is_some()
+}
+
+// =============================================================================
+// Dense Embeddings
+// =============================================================================
+
+#[napi(object)]
+pub struct DenseResult {
+ /// Flat array of embeddings [batch_size * 384]
+ pub embeddings: Vec,
+ /// Number of texts encoded
+ pub count: u32,
+}
+
+/// Encode texts to dense vectors (384-dim, L2-normalized)
+#[napi]
+pub fn embed_dense(texts: Vec) -> Result {
+ let model = DENSE_MODEL.get()
+ .ok_or_else(|| Error::from_reason("Models not initialized. Call init_models() first."))?;
+
+ let mut encoder = model.lock()
+ .map_err(|e| Error::from_reason(format!("Failed to lock dense model: {:?}", e)))?;
+
+ let embeddings_f32 = encoder.encode_batch(texts.clone(), true)
+ .map_err(|e| Error::from_reason(format!("Dense encoding failed: {:?}", e)))?;
+
+ Ok(DenseResult {
+ embeddings: embeddings_f32.iter().map(|&x| x as f64).collect(),
+ count: texts.len() as u32,
+ })
+}
+
+// =============================================================================
+// ColBERT Embeddings (for indexing)
+// =============================================================================
+
+#[napi(object)]
+pub struct ColbertPackedResult {
+ /// Packed embeddings as flat i8 array (all docs concatenated)
+ pub embeddings: Vec,
+ /// Token IDs for skiplist filtering
+ pub token_ids: Vec,
+ /// Number of tokens per document
+ pub lengths: Vec,
+ /// Byte offsets into embeddings for each doc
+ pub offsets: Vec,
+}
+
+/// Encode documents to ColBERT embeddings (48-dim per token, packed for storage)
+/// Call this at INDEX TIME to pre-compute embeddings
+#[napi]
+pub fn embed_colbert_packed(texts: Vec) -> Result {
+ let model = COLBERT_MODEL.get()
+ .ok_or_else(|| Error::from_reason("Models not initialized. Call init_models() first."))?;
+
+ let mut encoder = model.lock()
+ .map_err(|e| Error::from_reason(format!("Failed to lock ColBERT model: {:?}", e)))?;
+
+ let packed = encoder.encode_docs_packed(&texts)
+ .map_err(|e| Error::from_reason(format!("ColBERT encoding failed: {:?}", e)))?;
+
+ // Convert f32 embeddings to i8 (quantized)
+ let embeddings_i8: Vec = packed.embeddings.iter()
+ .map(|&x| (x * 127.0).clamp(-128.0, 127.0) as i8)
+ .collect();
+
+ Ok(ColbertPackedResult {
+ embeddings: embeddings_i8,
+ token_ids: packed.token_ids,
+ lengths: packed.lengths,
+ offsets: packed.offsets,
+ })
+}
+
+// =============================================================================
+// ColBERT Reranking (for search)
+// =============================================================================
+
+#[napi(object)]
+pub struct RerankResult {
+ /// Original indices of top-k documents
+ pub indices: Vec,
+ /// MaxSim scores for top-k documents
+ pub scores: Vec,
+}
+
+/// Encode a query for ColBERT reranking
+/// Returns the query matrix as flat f64 array [seq_len * 48]
+#[napi]
+pub fn encode_query_colbert(query: String) -> Result> {
+ let model = COLBERT_MODEL.get()
+ .ok_or_else(|| Error::from_reason("Models not initialized. Call init_models() first."))?;
+
+ let mut encoder = model.lock()
+ .map_err(|e| Error::from_reason(format!("Failed to lock ColBERT model: {:?}", e)))?;
+
+ let query_emb = encoder.encode_query(&query)
+ .map_err(|e| Error::from_reason(format!("Query encoding failed: {:?}", e)))?;
+
+ Ok(query_emb.embeddings.iter().map(|&x| x as f64).collect())
+}
+
+/// Rerank documents using pre-indexed ColBERT embeddings
+///
+/// query_embeddings: flat f64 array from encode_query_colbert [seq_len * 48]
+/// doc_embeddings: flat i8 array (packed ColBERT embeddings)
+/// doc_lengths: number of tokens per document
+/// doc_offsets: byte offset for each document in doc_embeddings
+/// candidate_indices: which docs to rerank (e.g., top-100 from dense retrieval)
+/// top_k: how many to return
+#[napi]
+pub fn rerank_colbert(
+ query_embeddings: Float64Array,
+ doc_embeddings: Int8Array,
+ doc_token_ids: Uint32Array,
+ doc_lengths: Vec,
+ doc_offsets: Vec,
+ candidate_indices: Vec,
+ top_k: u32,
+) -> Result {
+ let model = COLBERT_MODEL.get()
+ .ok_or_else(|| Error::from_reason("Models not initialized. Call init_models() first."))?;
+
+ let encoder = model.lock()
+ .map_err(|e| Error::from_reason(format!("Failed to lock ColBERT model: {:?}", e)))?;
+
+ let query_embeddings = query_embeddings.to_vec();
+ let doc_embeddings = doc_embeddings.to_vec();
+ let doc_token_ids = doc_token_ids.to_vec();
+
+ let hidden_size = 48usize;
+ let query_seq_len = query_embeddings.len() / hidden_size;
+
+ // Reconstruct query embedding struct
+ let query_emb = colbert_ort::QueryEmbedding {
+ embeddings: query_embeddings.iter().map(|&x| x as f32).collect(),
+ seq_len: query_seq_len,
+ hidden_size,
+ };
+
+ // Reconstruct packed doc embeddings (convert i8 back to f32)
+ let doc_embeddings_f32: Vec = doc_embeddings.iter()
+ .map(|&x| (x as f32) / 127.0)
+ .collect();
+
+ let packed = PackedDocEmbeddings {
+ embeddings: doc_embeddings_f32,
+ token_ids: doc_token_ids,
+ lengths: doc_lengths,
+ offsets: doc_offsets,
+ hidden_size,
+ };
+
+ // Score candidates
+ let indices: Vec = candidate_indices.iter().map(|&i| i as usize).collect();
+ let scores = encoder.score_packed(&query_emb, &packed, &indices);
+
+ // Sort by score descending
+ let mut indexed_scores: Vec<(usize, f32)> = indices.iter()
+ .zip(scores.iter())
+ .map(|(&i, &s)| (i, s))
+ .collect();
+ indexed_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
+
+ // Take top-k
+ let k = std::cmp::min(top_k as usize, indexed_scores.len());
+
+ Ok(RerankResult {
+ indices: indexed_scores[..k].iter().map(|(i, _)| *i as u32).collect(),
+ scores: indexed_scores[..k].iter().map(|(_, s)| *s as f64).collect(),
+ })
+}
+
+// =============================================================================
+// Convenience: Combined embed for indexing
+// =============================================================================
+
+#[napi(object)]
+pub struct EmbedResult {
+ /// Dense embeddings [batch_size * 384]
+ pub dense: Vec,
+ /// Packed ColBERT embeddings (i8)
+ pub colbert_embeddings: Vec,
+ /// Token IDs for skiplist filtering (all docs concatenated)
+ pub colbert_token_ids: Vec,
+ /// Token counts per document
+ pub colbert_lengths: Vec,
+ /// Byte offsets per document
+ pub colbert_offsets: Vec,
+}
+
+/// Embed texts for indexing (both dense and ColBERT in one call)
+#[napi]
+pub fn embed_batch(texts: Vec) -> Result {
+ let dense = embed_dense(texts.clone())?;
+ let colbert = embed_colbert_packed(texts)?;
+
+ Ok(EmbedResult {
+ dense: dense.embeddings,
+ colbert_embeddings: colbert.embeddings,
+ colbert_token_ids: colbert.token_ids,
+ colbert_lengths: colbert.lengths,
+ colbert_offsets: colbert.offsets,
+ })
+}
diff --git a/package.json b/package.json
index fabd64ef..f95f0207 100644
--- a/package.json
+++ b/package.json
@@ -21,15 +21,11 @@
"dev": "npx tsc && node dist/index.js",
"test": "vitest run",
"test:watch": "vitest",
- "benchmark": "./run-benchmark.sh",
- "benchmark:index": "./run-benchmark.sh $HOME/osgrep-benchmarks --index",
- "benchmark:agent": "npx tsx src/bench/benchmark-agent.ts",
- "benchmark:chart": "npx tsx src/bench/generate-benchmark-chart.ts",
"format": "biome check --write .",
"format:check": "biome check .",
"lint": "biome lint .",
"typecheck": "tsc --noEmit",
- "prepublishOnly": "pnpm build"
+ "prepublishOnly": "bun run build"
},
"keywords": [
"osgrep",
@@ -40,15 +36,12 @@
"files": [
"dist",
"plugins",
- "README.md",
- "LICENSE",
- "NOTICE"
+ "README.md"
],
"license": "Apache-2.0",
"description": "Local grep-like search tool for your codebase.",
"dependencies": {
- "@clack/prompts": "^0.11.0",
- "@huggingface/transformers": "^3.8.0",
+ "osgrep-core": "file:./osgrep-core",
"@lancedb/lancedb": "^0.22.3",
"@modelcontextprotocol/sdk": "^1.24.3",
"apache-arrow": "^18.1.0",
@@ -56,25 +49,15 @@
"chokidar": "^4.0.3",
"cli-highlight": "^2.1.11",
"commander": "^14.0.2",
- "dotenv": "^17.2.3",
"fast-glob": "^3.3.3",
"ignore": "^5.0.0",
"lmdb": "^3.4.4",
- "onnxruntime-node": "1.21.0",
"ora": "^5.4.1",
- "piscina": "^5.1.4",
- "simsimd": "^6.5.5",
- "uuid": "^9.0.1",
- "web-tree-sitter": "^0.25.10",
- "zod": "^4.1.12"
+ "web-tree-sitter": "^0.25.10"
},
"devDependencies": {
- "@anthropic-ai/claude-agent-sdk": "^0.1.50",
"@biomejs/biome": "2.3.4",
"@types/node": "^24.10.1",
- "@types/uuid": "^9.0.8",
- "node-gyp": "^12.1.0",
- "ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vitest": "^1.6.0"
}
diff --git a/plugins/osgrep/hooks/start.js b/plugins/osgrep/hooks/start.js
index 41066c87..9c8fa941 100644
--- a/plugins/osgrep/hooks/start.js
+++ b/plugins/osgrep/hooks/start.js
@@ -11,24 +11,72 @@ function readPayload() {
}
}
+function isProcessRunning(pid) {
+ if (!Number.isFinite(pid) || pid <= 0) return false;
+ try {
+ process.kill(pid, 0);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+function findProjectRoot(startDir) {
+ const start = _path.resolve(startDir || process.cwd());
+ const osgrepDir = _path.join(start, ".osgrep");
+ const gitDir = _path.join(start, ".git");
+ if (fs.existsSync(osgrepDir) || fs.existsSync(gitDir)) return start;
+ return start;
+}
+
+function getServerForProject(projectRoot) {
+ try {
+ const regPath = _path.join(require("node:os").homedir(), ".osgrep", "servers.json");
+ if (!fs.existsSync(regPath)) return null;
+ const data = JSON.parse(fs.readFileSync(regPath, "utf-8"));
+ if (!Array.isArray(data)) return null;
+ return data.find((s) => s && s.projectRoot === projectRoot && isProcessRunning(s.pid)) || null;
+ } catch {
+ return null;
+ }
+}
+
function main() {
const payload = readPayload();
const cwd = payload.cwd || process.cwd();
+ if (process.env.OSGREP_DISABLE_AUTO_SERVE === "1") {
+ const response = {
+ hookSpecificOutput: {
+ hookEventName: "SessionStart",
+ additionalContext:
+ "osgrep serve auto-start disabled (OSGREP_DISABLE_AUTO_SERVE=1).",
+ },
+ };
+ process.stdout.write(JSON.stringify(response));
+ return;
+ }
+
+ const projectRoot = findProjectRoot(cwd);
+ const existing = getServerForProject(projectRoot);
const logPath = "/tmp/osgrep.log";
const out = fs.openSync(logPath, "a");
- const child = spawn("osgrep", ["serve"], {
- cwd,
- detached: true,
- stdio: ["ignore", out, out],
- });
- child.unref();
+ if (!existing) {
+ const child = spawn("osgrep", ["serve", "--background"], {
+ cwd,
+ detached: true,
+ stdio: ["ignore", out, out],
+ });
+ child.unref();
+ }
const response = {
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext:
- 'osgrep serve started; prefer `osgrep ""` over grep (plain output is agent-friendly).',
+ existing
+ ? `osgrep serve running (PID: ${existing.pid}, Port: ${existing.port}).`
+ : 'osgrep serve starting (indexing in background). Searches work immediately but may show partial results until indexing completes.',
},
};
process.stdout.write(JSON.stringify(response));
diff --git a/plugins/osgrep/skills/osgrep/SKILL.md b/plugins/osgrep/skills/osgrep/SKILL.md
index ab2549a1..34f71786 100644
--- a/plugins/osgrep/skills/osgrep/SKILL.md
+++ b/plugins/osgrep/skills/osgrep/SKILL.md
@@ -1,11 +1,12 @@
---
name: osgrep
-description: Semantic code search. Use alongside grep - grep for exact strings, osgrep for concepts.
+description: Semantic code search and call tracing. Use alongside grep - grep for exact strings, osgrep for concepts and call flow.
allowed-tools: "Bash(osgrep:*), Read"
---
## What osgrep does
+
Finds code by meaning. When you'd ask a colleague "where do we handle auth?", use osgrep.
- grep/ripgrep: exact string match, fast
@@ -46,16 +47,45 @@ Read src/auth/handler.ts:45-120
Read the specific line range, not the whole file.
-## Other commands
+## Trace command
+
+When you need to understand call flow (who calls what, what calls who):
+
```bash
-# Trace call graph (who calls X, what X calls)
-osgrep trace handleAuth
+osgrep trace handleRequest
+```
-# Skeleton of a huge file (to find which ranges to read)
-osgrep skeleton src/giant-2000-line-file.ts
+**Output:**
+```
+handleRequest
+ def: src/server/handler.ts:45
+ calls: validateAuth routeRequest sendResponse
+ called_by: src/index.ts:12 src/api/router.ts:87
+```
+Use trace when:
+- You found a function and need to know what calls it
+- You need to understand what a function depends on
+- You're tracing request/data flow through the codebase
+
+```bash
+# Only callers (who calls this?)
+osgrep trace handleAuth --callers
+
+# Only callees (what does this call?)
+osgrep trace handleAuth --callees
+
+# Filter to specific path
+osgrep trace validateToken --path src/auth
+```
+
+## Other options
+```bash
# Just file paths when you only need locations
osgrep "authentication" --compact
+
+# Show more results
+osgrep "error handling" -m 20
```
## Workflow: architecture questions
@@ -66,9 +96,6 @@ osgrep "where do requests enter the server"
# 2. If you need deeper context on a specific function
Read src/server/handler.ts:45-120
-
-# 3. Trace to understand call flow
-osgrep trace handleRequest
```
## Tips
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
deleted file mode 100644
index 3f75cda9..00000000
--- a/pnpm-lock.yaml
+++ /dev/null
@@ -1,4112 +0,0 @@
-lockfileVersion: '6.0'
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
-dependencies:
- '@clack/prompts':
- specifier: ^0.11.0
- version: 0.11.0
- '@huggingface/transformers':
- specifier: ^3.8.0
- version: 3.8.1
- '@lancedb/lancedb':
- specifier: ^0.22.3
- version: 0.22.3(apache-arrow@18.1.0)
- '@modelcontextprotocol/sdk':
- specifier: ^1.24.3
- version: 1.24.3(zod@4.1.13)
- apache-arrow:
- specifier: ^18.1.0
- version: 18.1.0
- chalk:
- specifier: ^5.6.2
- version: 5.6.2
- chokidar:
- specifier: ^4.0.3
- version: 4.0.3
- cli-highlight:
- specifier: ^2.1.11
- version: 2.1.11
- commander:
- specifier: ^14.0.2
- version: 14.0.2
- dotenv:
- specifier: ^17.2.3
- version: 17.2.3
- fast-glob:
- specifier: ^3.3.3
- version: 3.3.3
- ignore:
- specifier: ^5.0.0
- version: 5.3.2
- lmdb:
- specifier: ^3.4.4
- version: 3.4.4
- onnxruntime-node:
- specifier: 1.21.0
- version: 1.21.0
- ora:
- specifier: ^5.4.1
- version: 5.4.1
- piscina:
- specifier: ^5.1.4
- version: 5.1.4
- simsimd:
- specifier: ^6.5.5
- version: 6.5.5
- uuid:
- specifier: ^9.0.1
- version: 9.0.1
- web-tree-sitter:
- specifier: ^0.25.10
- version: 0.25.10
- zod:
- specifier: ^4.1.12
- version: 4.1.13
-
-devDependencies:
- '@anthropic-ai/claude-agent-sdk':
- specifier: ^0.1.50
- version: 0.1.56(zod@4.1.13)
- '@biomejs/biome':
- specifier: 2.3.4
- version: 2.3.4
- '@types/node':
- specifier: ^24.10.1
- version: 24.10.1
- '@types/uuid':
- specifier: ^9.0.8
- version: 9.0.8
- node-gyp:
- specifier: ^12.1.0
- version: 12.1.0
- ts-node:
- specifier: ^10.9.2
- version: 10.9.2(@types/node@24.10.1)(typescript@5.9.3)
- typescript:
- specifier: ^5.9.3
- version: 5.9.3
- vitest:
- specifier: ^1.6.0
- version: 1.6.1(@types/node@24.10.1)
-
-packages:
-
- /@anthropic-ai/claude-agent-sdk@0.1.56(zod@4.1.13):
- resolution: {integrity: sha512-r4IAJ3Wht2iTyS3qoDYbCEbkTNXM2W33DEt9q5tkDDD9UR0dU5U7qLPwA8TTTX4wyHkZic5a0jLDuD7xU3ZrZg==}
- engines: {node: '>=18.0.0'}
- peerDependencies:
- zod: ^3.24.1
- dependencies:
- zod: 4.1.13
- optionalDependencies:
- '@img/sharp-darwin-arm64': 0.33.5
- '@img/sharp-darwin-x64': 0.33.5
- '@img/sharp-linux-arm': 0.33.5
- '@img/sharp-linux-arm64': 0.33.5
- '@img/sharp-linux-x64': 0.33.5
- '@img/sharp-linuxmusl-arm64': 0.33.5
- '@img/sharp-linuxmusl-x64': 0.33.5
- '@img/sharp-win32-x64': 0.33.5
- dev: true
-
- /@babel/runtime@7.28.4:
- resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
- engines: {node: '>=6.9.0'}
- requiresBuild: true
- dev: false
- optional: true
-
- /@biomejs/biome@2.3.4:
- resolution: {integrity: sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w==}
- engines: {node: '>=14.21.3'}
- hasBin: true
- optionalDependencies:
- '@biomejs/cli-darwin-arm64': 2.3.4
- '@biomejs/cli-darwin-x64': 2.3.4
- '@biomejs/cli-linux-arm64': 2.3.4
- '@biomejs/cli-linux-arm64-musl': 2.3.4
- '@biomejs/cli-linux-x64': 2.3.4
- '@biomejs/cli-linux-x64-musl': 2.3.4
- '@biomejs/cli-win32-arm64': 2.3.4
- '@biomejs/cli-win32-x64': 2.3.4
- dev: true
-
- /@biomejs/cli-darwin-arm64@2.3.4:
- resolution: {integrity: sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-darwin-x64@2.3.4:
- resolution: {integrity: sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-linux-arm64-musl@2.3.4:
- resolution: {integrity: sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-linux-arm64@2.3.4:
- resolution: {integrity: sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-linux-x64-musl@2.3.4:
- resolution: {integrity: sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-linux-x64@2.3.4:
- resolution: {integrity: sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-win32-arm64@2.3.4:
- resolution: {integrity: sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@biomejs/cli-win32-x64@2.3.4:
- resolution: {integrity: sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@clack/core@0.5.0:
- resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
- dependencies:
- picocolors: 1.1.1
- sisteransi: 1.0.5
- dev: false
-
- /@clack/prompts@0.11.0:
- resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==}
- dependencies:
- '@clack/core': 0.5.0
- picocolors: 1.1.1
- sisteransi: 1.0.5
- dev: false
-
- /@cspotcode/source-map-support@0.8.1:
- resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
- engines: {node: '>=12'}
- dependencies:
- '@jridgewell/trace-mapping': 0.3.9
- dev: true
-
- /@emnapi/runtime@1.7.1:
- resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
- requiresBuild: true
- dependencies:
- tslib: 2.8.1
- dev: false
- optional: true
-
- /@esbuild/aix-ppc64@0.21.5:
- resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [aix]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/android-arm64@0.21.5:
- resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/android-arm@0.21.5:
- resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/android-x64@0.21.5:
- resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/darwin-arm64@0.21.5:
- resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/darwin-x64@0.21.5:
- resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/freebsd-arm64@0.21.5:
- resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/freebsd-x64@0.21.5:
- resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-arm64@0.21.5:
- resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-arm@0.21.5:
- resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-ia32@0.21.5:
- resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-loong64@0.21.5:
- resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-mips64el@0.21.5:
- resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-ppc64@0.21.5:
- resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-riscv64@0.21.5:
- resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-s390x@0.21.5:
- resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-x64@0.21.5:
- resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/netbsd-x64@0.21.5:
- resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/openbsd-x64@0.21.5:
- resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/sunos-x64@0.21.5:
- resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-arm64@0.21.5:
- resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-ia32@0.21.5:
- resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-x64@0.21.5:
- resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@huggingface/jinja@0.5.3:
- resolution: {integrity: sha512-asqfZ4GQS0hD876Uw4qiUb7Tr/V5Q+JZuo2L+BtdrD4U40QU58nIRq3ZSgAzJgT874VLjhGVacaYfrdpXtEvtA==}
- engines: {node: '>=18'}
- dev: false
-
- /@huggingface/transformers@3.8.1:
- resolution: {integrity: sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==}
- dependencies:
- '@huggingface/jinja': 0.5.3
- onnxruntime-node: 1.21.0
- onnxruntime-web: 1.22.0-dev.20250409-89f8206ba4
- sharp: 0.34.5
- dev: false
-
- /@img/colour@1.0.0:
- resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
- engines: {node: '>=18'}
- dev: false
-
- /@img/sharp-darwin-arm64@0.33.5:
- resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-darwin-arm64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-darwin-arm64@0.34.5:
- resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-darwin-arm64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-darwin-x64@0.33.5:
- resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-darwin-x64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-darwin-x64@0.34.5:
- resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-darwin-x64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-libvips-darwin-arm64@1.0.4:
- resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-darwin-arm64@1.2.4:
- resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-darwin-x64@1.0.4:
- resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-darwin-x64@1.2.4:
- resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-arm64@1.0.4:
- resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-linux-arm64@1.2.4:
- resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-arm@1.0.5:
- resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-linux-arm@1.2.4:
- resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-ppc64@1.2.4:
- resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-riscv64@1.2.4:
- resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-s390x@1.2.4:
- resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linux-x64@1.0.4:
- resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-linux-x64@1.2.4:
- resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linuxmusl-arm64@1.0.4:
- resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-linuxmusl-arm64@1.2.4:
- resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-libvips-linuxmusl-x64@1.0.4:
- resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-libvips-linuxmusl-x64@1.2.4:
- resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-linux-arm64@0.33.5:
- resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-arm64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-linux-arm64@0.34.5:
- resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-arm64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linux-arm@0.33.5:
- resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-arm': 1.0.5
- dev: true
- optional: true
-
- /@img/sharp-linux-arm@0.34.5:
- resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-arm': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linux-ppc64@0.34.5:
- resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-ppc64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linux-riscv64@0.34.5:
- resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-riscv64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linux-s390x@0.34.5:
- resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-s390x': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linux-x64@0.33.5:
- resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-x64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-linux-x64@0.34.5:
- resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linux-x64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linuxmusl-arm64@0.33.5:
- resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-linuxmusl-arm64@0.34.5:
- resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-linuxmusl-x64@0.33.5:
- resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-x64': 1.0.4
- dev: true
- optional: true
-
- /@img/sharp-linuxmusl-x64@0.34.5:
- resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-x64': 1.2.4
- dev: false
- optional: true
-
- /@img/sharp-wasm32@0.34.5:
- resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [wasm32]
- requiresBuild: true
- dependencies:
- '@emnapi/runtime': 1.7.1
- dev: false
- optional: true
-
- /@img/sharp-win32-arm64@0.34.5:
- resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-win32-ia32@0.34.5:
- resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@img/sharp-win32-x64@0.33.5:
- resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@img/sharp-win32-x64@0.34.5:
- resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@isaacs/balanced-match@4.0.1:
- resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
- engines: {node: 20 || >=22}
- dev: true
-
- /@isaacs/brace-expansion@5.0.0:
- resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
- engines: {node: 20 || >=22}
- dependencies:
- '@isaacs/balanced-match': 4.0.1
- dev: true
-
- /@isaacs/fs-minipass@4.0.1:
- resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
- engines: {node: '>=18.0.0'}
- dependencies:
- minipass: 7.1.2
-
- /@jest/schemas@29.6.3:
- resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@sinclair/typebox': 0.27.8
- dev: true
-
- /@jridgewell/resolve-uri@3.1.2:
- resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
- engines: {node: '>=6.0.0'}
- dev: true
-
- /@jridgewell/sourcemap-codec@1.5.5:
- resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
- dev: true
-
- /@jridgewell/trace-mapping@0.3.9:
- resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
- dev: true
-
- /@lancedb/lancedb-darwin-arm64@0.22.3:
- resolution: {integrity: sha512-oP2Kic51nLqs27Xo+AzSVlcMgmmfZbU/PseQ3KBtU92rczO5DYU2St1Y7qDUWcjw+RF3H2v/DKzYed16h1wCBQ==}
- engines: {node: '>= 18'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-darwin-x64@0.22.3:
- resolution: {integrity: sha512-wOwgZkvBgQM8asjolz4NeyPa8W/AjZv4fwyQxJhTqKTGlB3ntrpdn1m84K5qncTmFFDcDfGgZ4DkNVkVK+ydoQ==}
- engines: {node: '>= 18'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-linux-arm64-gnu@0.22.3:
- resolution: {integrity: sha512-YUbFuBKQniTZOR9h2/es1f7lDzdHNt8qXs5GaqFmLQv2GNWpnvKXVA/vVffhCNpFB5nV132o1VhXW3KoMubPsw==}
- engines: {node: '>= 18'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-linux-arm64-musl@0.22.3:
- resolution: {integrity: sha512-jVRMtXxxYaDlZSaclCIHB2N+NJvQ1Fj9EaPeBx+HxG2VqUg0vXKef+yiaD2aGo9sAH6mMmkKJsrPhwABpUC4rQ==}
- engines: {node: '>= 18'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-linux-x64-gnu@0.22.3:
- resolution: {integrity: sha512-co7idTwvNAtbFoFHojhHlTpKsydOm5sZfbtAsQRdoa7g6a61yIrqrMm8D7Ngh756JfzZLFQBMkDUZEW3X4vP/g==}
- engines: {node: '>= 18'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-linux-x64-musl@0.22.3:
- resolution: {integrity: sha512-+ipFsn5PCODK7mOMq1gZ5OAZWks5YlgmjAlnYMmU8XxvaK0b6lZdA3s1hTmBaBO9+wv+31ulO55oBN4/U8Yldg==}
- engines: {node: '>= 18'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-win32-arm64-msvc@0.22.3:
- resolution: {integrity: sha512-E0XywJYnelIe4pzOlvog+aMHKt5ChW27tgmT2V80Z6PXcX6eN9I69Fj0Q6DK6z1YCTPIPu6Na1Hd6d4GqUNKPw==}
- engines: {node: '>= 18'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb-win32-x64-msvc@0.22.3:
- resolution: {integrity: sha512-/1feFnjz5MIhzXOEU4+1OeGwpAFYczGfefuOGZRsmGWDdt4V6/fza7Hkkxyb2OnTzqpBfy6BdW2+iBguE1JMyQ==}
- engines: {node: '>= 18'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lancedb/lancedb@0.22.3(apache-arrow@18.1.0):
- resolution: {integrity: sha512-nRC0fkg+d7dzCtudKHT+VH7znk6KUXRZyuS6HJYNnIrbvXBxaT6wAPjEbf70KTuqvP2znj48Zg+kiwRqkRnAJw==}
- engines: {node: '>= 18'}
- os: [darwin, linux, win32]
- peerDependencies:
- apache-arrow: '>=15.0.0 <=18.1.0'
- dependencies:
- apache-arrow: 18.1.0
- reflect-metadata: 0.2.2
- optionalDependencies:
- '@lancedb/lancedb-darwin-arm64': 0.22.3
- '@lancedb/lancedb-darwin-x64': 0.22.3
- '@lancedb/lancedb-linux-arm64-gnu': 0.22.3
- '@lancedb/lancedb-linux-arm64-musl': 0.22.3
- '@lancedb/lancedb-linux-x64-gnu': 0.22.3
- '@lancedb/lancedb-linux-x64-musl': 0.22.3
- '@lancedb/lancedb-win32-arm64-msvc': 0.22.3
- '@lancedb/lancedb-win32-x64-msvc': 0.22.3
- dev: false
-
- /@lmdb/lmdb-darwin-arm64@3.4.4:
- resolution: {integrity: sha512-XaKL705gDWd6XVls3ATDj13ZdML/LqSIxwgnYpG8xTzH2ifArx8fMMDdvqGE/Emd+W6R90W2fveZcJ0AyS8Y0w==}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-darwin-x64@3.4.4:
- resolution: {integrity: sha512-GPHGEVcwJlkD01GmIr7B4kvbIcUDS2+kBadVEd7lU4can1RZaZQLDDBJRrrNfS2Kavvl0VLI/cMv7UASAXGrww==}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-linux-arm64@3.4.4:
- resolution: {integrity: sha512-mALqr7DE42HsiwVTKpQWxacjHoJk+e9p00RWIJqTACh/hpucxp/0lK/XMh5XzWnU/TDCZLukq1+vNqnNumTP/Q==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-linux-arm@3.4.4:
- resolution: {integrity: sha512-cmev5/dZr5ACKri9f6GU6lZCXTjMhV72xujlbOhFCgFXrt4W0TxGsmY8kA1BITvH60JBKE50cSxsiulybAbrrw==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-linux-x64@3.4.4:
- resolution: {integrity: sha512-QjLs8OcmCNcraAcLoZyFlo0atzBJniQLLwhtR+ymQqS5kLYpV5RqwriL87BW+ZiR9ZiGgZx3evrz5vnWPtJ1fQ==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-win32-arm64@3.4.4:
- resolution: {integrity: sha512-tr/pwHDlZ33forLGAr0tI04cRmP4SgF93yHbb+2zvZiDEyln5yMHhbKDySxY66aUOkhvBvTuHq9q/3YmTj6ZHQ==}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@lmdb/lmdb-win32-x64@3.4.4:
- resolution: {integrity: sha512-KRzfocJzB/mgoTCqnMawuLSKheHRVTqWfSmouIgYpFs6Hx4zvZSvsZKSCEb5gHmICy7qsx9l06jk3MFTtiFVAQ==}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@modelcontextprotocol/sdk@1.24.3(zod@4.1.13):
- resolution: {integrity: sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==}
- engines: {node: '>=18'}
- peerDependencies:
- '@cfworker/json-schema': ^4.1.1
- zod: ^3.25 || ^4.0
- peerDependenciesMeta:
- '@cfworker/json-schema':
- optional: true
- dependencies:
- ajv: 8.17.1
- ajv-formats: 3.0.1(ajv@8.17.1)
- content-type: 1.0.5
- cors: 2.8.5
- cross-spawn: 7.0.6
- eventsource: 3.0.7
- eventsource-parser: 3.0.6
- express: 5.2.1
- express-rate-limit: 7.5.1(express@5.2.1)
- jose: 6.1.3
- pkce-challenge: 5.0.1
- raw-body: 3.0.2
- zod: 4.1.13
- zod-to-json-schema: 3.25.0(zod@4.1.13)
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3:
- resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3:
- resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3:
- resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3:
- resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3:
- resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3:
- resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-android-arm-eabi@1.1.1:
- resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==}
- engines: {node: '>= 10'}
- cpu: [arm]
- os: [android]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-android-arm64@1.1.1:
- resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-darwin-arm64@1.1.1:
- resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-darwin-x64@1.1.1:
- resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-freebsd-x64@1.1.1:
- resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-arm-gnueabihf@1.1.1:
- resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==}
- engines: {node: '>= 10'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-arm64-gnu@1.1.1:
- resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-arm64-musl@1.1.1:
- resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-ppc64-gnu@1.1.1:
- resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==}
- engines: {node: '>= 10'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-riscv64-gnu@1.1.1:
- resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==}
- engines: {node: '>= 10'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-s390x-gnu@1.1.1:
- resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==}
- engines: {node: '>= 10'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-x64-gnu@1.1.1:
- resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-linux-x64-musl@1.1.1:
- resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-openharmony-arm64@1.1.1:
- resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [openharmony]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-win32-arm64-msvc@1.1.1:
- resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-win32-ia32-msvc@1.1.1:
- resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==}
- engines: {node: '>= 10'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice-win32-x64-msvc@1.1.1:
- resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: false
- optional: true
-
- /@napi-rs/nice@1.1.1:
- resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==}
- engines: {node: '>= 10'}
- requiresBuild: true
- optionalDependencies:
- '@napi-rs/nice-android-arm-eabi': 1.1.1
- '@napi-rs/nice-android-arm64': 1.1.1
- '@napi-rs/nice-darwin-arm64': 1.1.1
- '@napi-rs/nice-darwin-x64': 1.1.1
- '@napi-rs/nice-freebsd-x64': 1.1.1
- '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1
- '@napi-rs/nice-linux-arm64-gnu': 1.1.1
- '@napi-rs/nice-linux-arm64-musl': 1.1.1
- '@napi-rs/nice-linux-ppc64-gnu': 1.1.1
- '@napi-rs/nice-linux-riscv64-gnu': 1.1.1
- '@napi-rs/nice-linux-s390x-gnu': 1.1.1
- '@napi-rs/nice-linux-x64-gnu': 1.1.1
- '@napi-rs/nice-linux-x64-musl': 1.1.1
- '@napi-rs/nice-openharmony-arm64': 1.1.1
- '@napi-rs/nice-win32-arm64-msvc': 1.1.1
- '@napi-rs/nice-win32-ia32-msvc': 1.1.1
- '@napi-rs/nice-win32-x64-msvc': 1.1.1
- dev: false
- optional: true
-
- /@nodelib/fs.scandir@2.1.5:
- resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
- engines: {node: '>= 8'}
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
- dev: false
-
- /@nodelib/fs.stat@2.0.5:
- resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
- engines: {node: '>= 8'}
- dev: false
-
- /@nodelib/fs.walk@1.2.8:
- resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
- engines: {node: '>= 8'}
- dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.19.1
- dev: false
-
- /@npmcli/agent@4.0.0:
- resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- agent-base: 7.1.4
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6
- lru-cache: 11.2.4
- socks-proxy-agent: 8.0.5
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /@npmcli/fs@5.0.0:
- resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- semver: 7.7.3
- dev: true
-
- /@protobufjs/aspromise@1.1.2:
- resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
- dev: false
-
- /@protobufjs/base64@1.1.2:
- resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
- dev: false
-
- /@protobufjs/codegen@2.0.4:
- resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
- dev: false
-
- /@protobufjs/eventemitter@1.1.0:
- resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
- dev: false
-
- /@protobufjs/fetch@1.1.0:
- resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
- dependencies:
- '@protobufjs/aspromise': 1.1.2
- '@protobufjs/inquire': 1.1.0
- dev: false
-
- /@protobufjs/float@1.0.2:
- resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
- dev: false
-
- /@protobufjs/inquire@1.1.0:
- resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
- dev: false
-
- /@protobufjs/path@1.1.2:
- resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
- dev: false
-
- /@protobufjs/pool@1.1.0:
- resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
- dev: false
-
- /@protobufjs/utf8@1.1.0:
- resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
- dev: false
-
- /@rollup/rollup-android-arm-eabi@4.53.3:
- resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
- cpu: [arm]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-android-arm64@4.53.3:
- resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-darwin-arm64@4.53.3:
- resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-darwin-x64@4.53.3:
- resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-freebsd-arm64@4.53.3:
- resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-freebsd-x64@4.53.3:
- resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-arm-gnueabihf@4.53.3:
- resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-arm-musleabihf@4.53.3:
- resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-arm64-gnu@4.53.3:
- resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-arm64-musl@4.53.3:
- resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-loong64-gnu@4.53.3:
- resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
- cpu: [loong64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-ppc64-gnu@4.53.3:
- resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-riscv64-gnu@4.53.3:
- resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-riscv64-musl@4.53.3:
- resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-s390x-gnu@4.53.3:
- resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-x64-gnu@4.53.3:
- resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-linux-x64-musl@4.53.3:
- resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-openharmony-arm64@4.53.3:
- resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==}
- cpu: [arm64]
- os: [openharmony]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-win32-arm64-msvc@4.53.3:
- resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-win32-ia32-msvc@4.53.3:
- resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-win32-x64-gnu@4.53.3:
- resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rollup/rollup-win32-x64-msvc@4.53.3:
- resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@sinclair/typebox@0.27.8:
- resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
- dev: true
-
- /@swc/helpers@0.5.17:
- resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
- dependencies:
- tslib: 2.8.1
- dev: false
-
- /@tsconfig/node10@1.0.12:
- resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
- dev: true
-
- /@tsconfig/node12@1.0.11:
- resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
- dev: true
-
- /@tsconfig/node14@1.0.3:
- resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
- dev: true
-
- /@tsconfig/node16@1.0.4:
- resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
- dev: true
-
- /@types/command-line-args@5.2.3:
- resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==}
- dev: false
-
- /@types/command-line-usage@5.0.4:
- resolution: {integrity: sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==}
- dev: false
-
- /@types/estree@1.0.8:
- resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
- dev: true
-
- /@types/node@20.19.25:
- resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
- dependencies:
- undici-types: 6.21.0
- dev: false
-
- /@types/node@24.10.1:
- resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
- dependencies:
- undici-types: 7.16.0
-
- /@types/uuid@9.0.8:
- resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
- dev: true
-
- /@vitest/expect@1.6.1:
- resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
- dependencies:
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- chai: 4.5.0
- dev: true
-
- /@vitest/runner@1.6.1:
- resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
- dependencies:
- '@vitest/utils': 1.6.1
- p-limit: 5.0.0
- pathe: 1.1.2
- dev: true
-
- /@vitest/snapshot@1.6.1:
- resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
- dependencies:
- magic-string: 0.30.21
- pathe: 1.1.2
- pretty-format: 29.7.0
- dev: true
-
- /@vitest/spy@1.6.1:
- resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
- dependencies:
- tinyspy: 2.2.1
- dev: true
-
- /@vitest/utils@1.6.1:
- resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
- dependencies:
- diff-sequences: 29.6.3
- estree-walker: 3.0.3
- loupe: 2.3.7
- pretty-format: 29.7.0
- dev: true
-
- /abbrev@4.0.0:
- resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dev: true
-
- /accepts@2.0.0:
- resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
- engines: {node: '>= 0.6'}
- dependencies:
- mime-types: 3.0.2
- negotiator: 1.0.0
- dev: false
-
- /acorn-walk@8.3.4:
- resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
- engines: {node: '>=0.4.0'}
- dependencies:
- acorn: 8.15.0
- dev: true
-
- /acorn@8.15.0:
- resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
- engines: {node: '>=0.4.0'}
- hasBin: true
- dev: true
-
- /agent-base@7.1.4:
- resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
- engines: {node: '>= 14'}
- dev: true
-
- /ajv-formats@3.0.1(ajv@8.17.1):
- resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
- peerDependencies:
- ajv: ^8.0.0
- peerDependenciesMeta:
- ajv:
- optional: true
- dependencies:
- ajv: 8.17.1
- dev: false
-
- /ajv@8.17.1:
- resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
- dependencies:
- fast-deep-equal: 3.1.3
- fast-uri: 3.1.0
- json-schema-traverse: 1.0.0
- require-from-string: 2.0.2
- dev: false
-
- /ansi-regex@5.0.1:
- resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
- engines: {node: '>=8'}
- dev: false
-
- /ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
- engines: {node: '>=8'}
- dependencies:
- color-convert: 2.0.1
- dev: false
-
- /ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
- dev: true
-
- /any-promise@1.3.0:
- resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
- dev: false
-
- /apache-arrow@18.1.0:
- resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==}
- hasBin: true
- dependencies:
- '@swc/helpers': 0.5.17
- '@types/command-line-args': 5.2.3
- '@types/command-line-usage': 5.0.4
- '@types/node': 20.19.25
- command-line-args: 5.2.1
- command-line-usage: 7.0.3
- flatbuffers: 24.12.23
- json-bignum: 0.0.3
- tslib: 2.8.1
- dev: false
-
- /arg@4.1.3:
- resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
- dev: true
-
- /array-back@3.1.0:
- resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==}
- engines: {node: '>=6'}
- dev: false
-
- /array-back@6.2.2:
- resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==}
- engines: {node: '>=12.17'}
- dev: false
-
- /assertion-error@1.1.0:
- resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
- dev: true
-
- /base64-js@1.5.1:
- resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- dev: false
-
- /benchmark@2.1.4:
- resolution: {integrity: sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==}
- requiresBuild: true
- dependencies:
- lodash: 4.17.21
- platform: 1.3.6
- dev: false
- optional: true
-
- /bindings@1.5.0:
- resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
- dependencies:
- file-uri-to-path: 1.0.0
- dev: false
-
- /bl@4.1.0:
- resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
- dependencies:
- buffer: 5.7.1
- inherits: 2.0.4
- readable-stream: 3.6.2
- dev: false
-
- /body-parser@2.2.1:
- resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==}
- engines: {node: '>=18'}
- dependencies:
- bytes: 3.1.2
- content-type: 1.0.5
- debug: 4.4.3
- http-errors: 2.0.1
- iconv-lite: 0.7.0
- on-finished: 2.4.1
- qs: 6.14.0
- raw-body: 3.0.2
- type-is: 2.0.1
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /boolean@3.2.0:
- resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- dev: false
-
- /braces@3.0.3:
- resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
- engines: {node: '>=8'}
- dependencies:
- fill-range: 7.1.1
- dev: false
-
- /buffer@5.7.1:
- resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
- dependencies:
- base64-js: 1.5.1
- ieee754: 1.2.1
- dev: false
-
- /bytes@3.1.2:
- resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /cac@6.7.14:
- resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
- engines: {node: '>=8'}
- dev: true
-
- /cacache@20.0.3:
- resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- '@npmcli/fs': 5.0.0
- fs-minipass: 3.0.3
- glob: 13.0.0
- lru-cache: 11.2.4
- minipass: 7.1.2
- minipass-collect: 2.0.1
- minipass-flush: 1.0.5
- minipass-pipeline: 1.2.4
- p-map: 7.0.4
- ssri: 13.0.0
- unique-filename: 5.0.0
- dev: true
-
- /call-bind-apply-helpers@1.0.2:
- resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
- engines: {node: '>= 0.4'}
- dependencies:
- es-errors: 1.3.0
- function-bind: 1.1.2
- dev: false
-
- /call-bound@1.0.4:
- resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bind-apply-helpers: 1.0.2
- get-intrinsic: 1.3.0
- dev: false
-
- /chai@4.5.0:
- resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
- engines: {node: '>=4'}
- dependencies:
- assertion-error: 1.1.0
- check-error: 1.0.3
- deep-eql: 4.1.4
- get-func-name: 2.0.2
- loupe: 2.3.7
- pathval: 1.1.1
- type-detect: 4.1.0
- dev: true
-
- /chalk-template@0.4.0:
- resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
- engines: {node: '>=12'}
- dependencies:
- chalk: 4.1.2
- dev: false
-
- /chalk@4.1.2:
- resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
- engines: {node: '>=10'}
- dependencies:
- ansi-styles: 4.3.0
- supports-color: 7.2.0
- dev: false
-
- /chalk@5.6.2:
- resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
- engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
- dev: false
-
- /check-error@1.0.3:
- resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
- dependencies:
- get-func-name: 2.0.2
- dev: true
-
- /chokidar@4.0.3:
- resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
- engines: {node: '>= 14.16.0'}
- dependencies:
- readdirp: 4.1.2
- dev: false
-
- /chownr@3.0.0:
- resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
- engines: {node: '>=18'}
-
- /cli-cursor@3.1.0:
- resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
- engines: {node: '>=8'}
- dependencies:
- restore-cursor: 3.1.0
- dev: false
-
- /cli-highlight@2.1.11:
- resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==}
- engines: {node: '>=8.0.0', npm: '>=5.0.0'}
- hasBin: true
- dependencies:
- chalk: 4.1.2
- highlight.js: 10.7.3
- mz: 2.7.0
- parse5: 5.1.1
- parse5-htmlparser2-tree-adapter: 6.0.1
- yargs: 16.2.0
- dev: false
-
- /cli-spinners@2.9.2:
- resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
- engines: {node: '>=6'}
- dev: false
-
- /cliui@7.0.4:
- resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
- dependencies:
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
- dev: false
-
- /clone@1.0.4:
- resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
- engines: {node: '>=0.8'}
- dev: false
-
- /color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
- dependencies:
- color-name: 1.1.4
- dev: false
-
- /color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
- dev: false
-
- /command-line-args@5.2.1:
- resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
- engines: {node: '>=4.0.0'}
- dependencies:
- array-back: 3.1.0
- find-replace: 3.0.0
- lodash.camelcase: 4.3.0
- typical: 4.0.0
- dev: false
-
- /command-line-usage@7.0.3:
- resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==}
- engines: {node: '>=12.20.0'}
- dependencies:
- array-back: 6.2.2
- chalk-template: 0.4.0
- table-layout: 4.1.1
- typical: 7.3.0
- dev: false
-
- /commander@14.0.2:
- resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
- engines: {node: '>=20'}
- dev: false
-
- /complex.js@2.4.3:
- resolution: {integrity: sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==}
- requiresBuild: true
- dev: false
- optional: true
-
- /confbox@0.1.8:
- resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
- dev: true
-
- /content-disposition@1.0.1:
- resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
- engines: {node: '>=18'}
- dev: false
-
- /content-type@1.0.5:
- resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /cookie-signature@1.2.2:
- resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
- engines: {node: '>=6.6.0'}
- dev: false
-
- /cookie@0.7.2:
- resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /cors@2.8.5:
- resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
- engines: {node: '>= 0.10'}
- dependencies:
- object-assign: 4.1.1
- vary: 1.1.2
- dev: false
-
- /create-require@1.1.1:
- resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
- dev: true
-
- /cross-spawn@7.0.6:
- resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
- engines: {node: '>= 8'}
- dependencies:
- path-key: 3.1.1
- shebang-command: 2.0.0
- which: 2.0.2
-
- /debug@4.4.3:
- resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
- dependencies:
- ms: 2.1.3
-
- /decimal.js@10.6.0:
- resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
- requiresBuild: true
- dev: false
- optional: true
-
- /deep-eql@4.1.4:
- resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
- engines: {node: '>=6'}
- dependencies:
- type-detect: 4.1.0
- dev: true
-
- /defaults@1.0.4:
- resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
- dependencies:
- clone: 1.0.4
- dev: false
-
- /define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
- engines: {node: '>= 0.4'}
- dependencies:
- es-define-property: 1.0.1
- es-errors: 1.3.0
- gopd: 1.2.0
- dev: false
-
- /define-properties@1.2.1:
- resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
- engines: {node: '>= 0.4'}
- dependencies:
- define-data-property: 1.1.4
- has-property-descriptors: 1.0.2
- object-keys: 1.1.1
- dev: false
-
- /depd@2.0.0:
- resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /detect-libc@2.1.2:
- resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
- engines: {node: '>=8'}
- dev: false
-
- /detect-node@2.1.0:
- resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
- dev: false
-
- /diff-sequences@29.6.3:
- resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dev: true
-
- /diff@4.0.2:
- resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
- engines: {node: '>=0.3.1'}
- dev: true
-
- /dotenv@17.2.3:
- resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
- engines: {node: '>=12'}
- dev: false
-
- /dunder-proto@1.0.1:
- resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-errors: 1.3.0
- gopd: 1.2.0
- dev: false
-
- /ee-first@1.1.1:
- resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
- dev: false
-
- /emoji-regex@8.0.0:
- resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
- dev: false
-
- /encodeurl@2.0.0:
- resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /encoding@0.1.13:
- resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
- requiresBuild: true
- dependencies:
- iconv-lite: 0.6.3
- dev: true
- optional: true
-
- /env-paths@2.2.1:
- resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
- engines: {node: '>=6'}
- dev: true
-
- /err-code@2.0.3:
- resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
- dev: true
-
- /es-define-property@1.0.1:
- resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /es-errors@1.3.0:
- resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /es-object-atoms@1.1.1:
- resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
- engines: {node: '>= 0.4'}
- dependencies:
- es-errors: 1.3.0
- dev: false
-
- /es6-error@4.1.1:
- resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
- dev: false
-
- /esbuild@0.21.5:
- resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.21.5
- '@esbuild/android-arm': 0.21.5
- '@esbuild/android-arm64': 0.21.5
- '@esbuild/android-x64': 0.21.5
- '@esbuild/darwin-arm64': 0.21.5
- '@esbuild/darwin-x64': 0.21.5
- '@esbuild/freebsd-arm64': 0.21.5
- '@esbuild/freebsd-x64': 0.21.5
- '@esbuild/linux-arm': 0.21.5
- '@esbuild/linux-arm64': 0.21.5
- '@esbuild/linux-ia32': 0.21.5
- '@esbuild/linux-loong64': 0.21.5
- '@esbuild/linux-mips64el': 0.21.5
- '@esbuild/linux-ppc64': 0.21.5
- '@esbuild/linux-riscv64': 0.21.5
- '@esbuild/linux-s390x': 0.21.5
- '@esbuild/linux-x64': 0.21.5
- '@esbuild/netbsd-x64': 0.21.5
- '@esbuild/openbsd-x64': 0.21.5
- '@esbuild/sunos-x64': 0.21.5
- '@esbuild/win32-arm64': 0.21.5
- '@esbuild/win32-ia32': 0.21.5
- '@esbuild/win32-x64': 0.21.5
- dev: true
-
- /escalade@3.2.0:
- resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
- engines: {node: '>=6'}
- dev: false
-
- /escape-html@1.0.3:
- resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
- dev: false
-
- /escape-latex@1.2.0:
- resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
- requiresBuild: true
- dev: false
- optional: true
-
- /escape-string-regexp@4.0.0:
- resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
- engines: {node: '>=10'}
- dev: false
-
- /estree-walker@3.0.3:
- resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
- dependencies:
- '@types/estree': 1.0.8
- dev: true
-
- /etag@1.8.1:
- resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /eventsource-parser@3.0.6:
- resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
- engines: {node: '>=18.0.0'}
- dev: false
-
- /eventsource@3.0.7:
- resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
- engines: {node: '>=18.0.0'}
- dependencies:
- eventsource-parser: 3.0.6
- dev: false
-
- /execa@8.0.1:
- resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
- engines: {node: '>=16.17'}
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 8.0.1
- human-signals: 5.0.0
- is-stream: 3.0.0
- merge-stream: 2.0.0
- npm-run-path: 5.3.0
- onetime: 6.0.0
- signal-exit: 4.1.0
- strip-final-newline: 3.0.0
- dev: true
-
- /exponential-backoff@3.1.3:
- resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
- dev: true
-
- /express-rate-limit@7.5.1(express@5.2.1):
- resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==}
- engines: {node: '>= 16'}
- peerDependencies:
- express: '>= 4.11'
- dependencies:
- express: 5.2.1
- dev: false
-
- /express@5.2.1:
- resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
- engines: {node: '>= 18'}
- dependencies:
- accepts: 2.0.0
- body-parser: 2.2.1
- content-disposition: 1.0.1
- content-type: 1.0.5
- cookie: 0.7.2
- cookie-signature: 1.2.2
- debug: 4.4.3
- depd: 2.0.0
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- finalhandler: 2.1.1
- fresh: 2.0.0
- http-errors: 2.0.1
- merge-descriptors: 2.0.0
- mime-types: 3.0.2
- on-finished: 2.4.1
- once: 1.4.0
- parseurl: 1.3.3
- proxy-addr: 2.0.7
- qs: 6.14.0
- range-parser: 1.2.1
- router: 2.2.0
- send: 1.2.0
- serve-static: 2.2.0
- statuses: 2.0.2
- type-is: 2.0.1
- vary: 1.1.2
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
- dev: false
-
- /fast-glob@3.3.3:
- resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
- engines: {node: '>=8.6.0'}
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- '@nodelib/fs.walk': 1.2.8
- glob-parent: 5.1.2
- merge2: 1.4.1
- micromatch: 4.0.8
- dev: false
-
- /fast-uri@3.1.0:
- resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
- dev: false
-
- /fastq@1.19.1:
- resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
- dependencies:
- reusify: 1.1.0
- dev: false
-
- /fdir@6.5.0(picomatch@4.0.3):
- resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
- engines: {node: '>=12.0.0'}
- peerDependencies:
- picomatch: ^3 || ^4
- peerDependenciesMeta:
- picomatch:
- optional: true
- dependencies:
- picomatch: 4.0.3
- dev: true
-
- /file-uri-to-path@1.0.0:
- resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
- dev: false
-
- /fill-range@7.1.1:
- resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
- engines: {node: '>=8'}
- dependencies:
- to-regex-range: 5.0.1
- dev: false
-
- /finalhandler@2.1.1:
- resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
- engines: {node: '>= 18.0.0'}
- dependencies:
- debug: 4.4.3
- encodeurl: 2.0.0
- escape-html: 1.0.3
- on-finished: 2.4.1
- parseurl: 1.3.3
- statuses: 2.0.2
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /find-replace@3.0.0:
- resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==}
- engines: {node: '>=4.0.0'}
- dependencies:
- array-back: 3.1.0
- dev: false
-
- /flatbuffers@24.12.23:
- resolution: {integrity: sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==}
- dev: false
-
- /flatbuffers@25.9.23:
- resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==}
- dev: false
-
- /forwarded@0.2.0:
- resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /fraction.js@5.3.4:
- resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
- requiresBuild: true
- dev: false
- optional: true
-
- /fresh@2.0.0:
- resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /fs-minipass@3.0.3:
- resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==}
- engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- dependencies:
- minipass: 7.1.2
- dev: true
-
- /fsevents@2.3.3:
- resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
- engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /function-bind@1.1.2:
- resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- dev: false
-
- /get-caller-file@2.0.5:
- resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
- engines: {node: 6.* || 8.* || >= 10.*}
- dev: false
-
- /get-func-name@2.0.2:
- resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
- dev: true
-
- /get-intrinsic@1.3.0:
- resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-define-property: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.1
- function-bind: 1.1.2
- get-proto: 1.0.1
- gopd: 1.2.0
- has-symbols: 1.1.0
- hasown: 2.0.2
- math-intrinsics: 1.1.0
- dev: false
-
- /get-proto@1.0.1:
- resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
- engines: {node: '>= 0.4'}
- dependencies:
- dunder-proto: 1.0.1
- es-object-atoms: 1.1.1
- dev: false
-
- /get-stream@8.0.1:
- resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
- engines: {node: '>=16'}
- dev: true
-
- /glob-parent@5.1.2:
- resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
- engines: {node: '>= 6'}
- dependencies:
- is-glob: 4.0.3
- dev: false
-
- /glob@13.0.0:
- resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
- engines: {node: 20 || >=22}
- dependencies:
- minimatch: 10.1.1
- minipass: 7.1.2
- path-scurry: 2.0.1
- dev: true
-
- /global-agent@3.0.0:
- resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
- engines: {node: '>=10.0'}
- dependencies:
- boolean: 3.2.0
- es6-error: 4.1.1
- matcher: 3.0.0
- roarr: 2.15.4
- semver: 7.7.3
- serialize-error: 7.0.1
- dev: false
-
- /globalthis@1.0.4:
- resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
- engines: {node: '>= 0.4'}
- dependencies:
- define-properties: 1.2.1
- gopd: 1.2.0
- dev: false
-
- /gopd@1.2.0:
- resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /graceful-fs@4.2.11:
- resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- dev: true
-
- /guid-typescript@1.0.9:
- resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
- dev: false
-
- /has-flag@4.0.0:
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
- engines: {node: '>=8'}
- dev: false
-
- /has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
- dependencies:
- es-define-property: 1.0.1
- dev: false
-
- /has-symbols@1.1.0:
- resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
- engines: {node: '>= 0.4'}
- dependencies:
- function-bind: 1.1.2
- dev: false
-
- /highlight.js@10.7.3:
- resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
- dev: false
-
- /http-cache-semantics@4.2.0:
- resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
- dev: true
-
- /http-errors@2.0.1:
- resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
- engines: {node: '>= 0.8'}
- dependencies:
- depd: 2.0.0
- inherits: 2.0.4
- setprototypeof: 1.2.0
- statuses: 2.0.2
- toidentifier: 1.0.1
- dev: false
-
- /http-proxy-agent@7.0.2:
- resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
- engines: {node: '>= 14'}
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /https-proxy-agent@7.0.6:
- resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
- engines: {node: '>= 14'}
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /human-signals@5.0.0:
- resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
- engines: {node: '>=16.17.0'}
- dev: true
-
- /iconv-lite@0.6.3:
- resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
- engines: {node: '>=0.10.0'}
- requiresBuild: true
- dependencies:
- safer-buffer: 2.1.2
- dev: true
- optional: true
-
- /iconv-lite@0.7.0:
- resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
- engines: {node: '>=0.10.0'}
- dependencies:
- safer-buffer: 2.1.2
- dev: false
-
- /ieee754@1.2.1:
- resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
- dev: false
-
- /ignore@5.3.2:
- resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
- engines: {node: '>= 4'}
- dev: false
-
- /imurmurhash@0.1.4:
- resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
- engines: {node: '>=0.8.19'}
- dev: true
-
- /inherits@2.0.4:
- resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- dev: false
-
- /ip-address@10.1.0:
- resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
- engines: {node: '>= 12'}
- dev: true
-
- /ipaddr.js@1.9.1:
- resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
- engines: {node: '>= 0.10'}
- dev: false
-
- /is-extglob@2.1.1:
- resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
- engines: {node: '>=0.10.0'}
- dev: false
-
- /is-fullwidth-code-point@3.0.0:
- resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
- engines: {node: '>=8'}
- dev: false
-
- /is-glob@4.0.3:
- resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
- engines: {node: '>=0.10.0'}
- dependencies:
- is-extglob: 2.1.1
- dev: false
-
- /is-interactive@1.0.0:
- resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
- engines: {node: '>=8'}
- dev: false
-
- /is-number@7.0.0:
- resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
- engines: {node: '>=0.12.0'}
- dev: false
-
- /is-promise@4.0.0:
- resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
- dev: false
-
- /is-stream@3.0.0:
- resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- dev: true
-
- /is-unicode-supported@0.1.0:
- resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
- engines: {node: '>=10'}
- dev: false
-
- /isexe@2.0.0:
- resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-
- /isexe@3.1.1:
- resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
- engines: {node: '>=16'}
- dev: true
-
- /javascript-natural-sort@0.7.1:
- resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
- requiresBuild: true
- dev: false
- optional: true
-
- /jose@6.1.3:
- resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
- dev: false
-
- /js-tokens@9.0.1:
- resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
- dev: true
-
- /json-bignum@0.0.3:
- resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==}
- engines: {node: '>=0.8'}
- dev: false
-
- /json-schema-traverse@1.0.0:
- resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
- dev: false
-
- /json-stringify-safe@5.0.1:
- resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
- dev: false
-
- /lmdb@3.4.4:
- resolution: {integrity: sha512-+Y2DqovevLkb6DrSQ6SXTYLEd6kvlRbhsxzgJrk7BUfOVA/mt21ak6pFDZDKxiAczHMWxrb02kXBTSTIA0O94A==}
- hasBin: true
- requiresBuild: true
- dependencies:
- msgpackr: 1.11.5
- node-addon-api: 6.1.0
- node-gyp-build-optional-packages: 5.2.2
- ordered-binary: 1.6.0
- weak-lru-cache: 1.2.2
- optionalDependencies:
- '@lmdb/lmdb-darwin-arm64': 3.4.4
- '@lmdb/lmdb-darwin-x64': 3.4.4
- '@lmdb/lmdb-linux-arm': 3.4.4
- '@lmdb/lmdb-linux-arm64': 3.4.4
- '@lmdb/lmdb-linux-x64': 3.4.4
- '@lmdb/lmdb-win32-arm64': 3.4.4
- '@lmdb/lmdb-win32-x64': 3.4.4
- dev: false
-
- /local-pkg@0.5.1:
- resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
- engines: {node: '>=14'}
- dependencies:
- mlly: 1.8.0
- pkg-types: 1.3.1
- dev: true
-
- /lodash.camelcase@4.3.0:
- resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
- dev: false
-
- /lodash@4.17.21:
- resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
- requiresBuild: true
- dev: false
- optional: true
-
- /log-symbols@4.1.0:
- resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
- engines: {node: '>=10'}
- dependencies:
- chalk: 4.1.2
- is-unicode-supported: 0.1.0
- dev: false
-
- /long@5.3.2:
- resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
- dev: false
-
- /loupe@2.3.7:
- resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
- dependencies:
- get-func-name: 2.0.2
- dev: true
-
- /lru-cache@11.2.4:
- resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
- engines: {node: 20 || >=22}
- dev: true
-
- /magic-string@0.30.21:
- resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
- dev: true
-
- /make-error@1.3.6:
- resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
- dev: true
-
- /make-fetch-happen@15.0.3:
- resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- '@npmcli/agent': 4.0.0
- cacache: 20.0.3
- http-cache-semantics: 4.2.0
- minipass: 7.1.2
- minipass-fetch: 5.0.0
- minipass-flush: 1.0.5
- minipass-pipeline: 1.2.4
- negotiator: 1.0.0
- proc-log: 6.1.0
- promise-retry: 2.0.1
- ssri: 13.0.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /matcher@3.0.0:
- resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
- engines: {node: '>=10'}
- dependencies:
- escape-string-regexp: 4.0.0
- dev: false
-
- /math-intrinsics@1.1.0:
- resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /mathjs@14.9.1:
- resolution: {integrity: sha512-xhqv8Xjf+caWG3WlaPekg4v8QFOR3D5+8ycfcjMcPcnCNDgAONQLaLfyGgrggJrcHx2yUGCpACRpiD4GmXwX+Q==}
- engines: {node: '>= 18'}
- hasBin: true
- requiresBuild: true
- dependencies:
- '@babel/runtime': 7.28.4
- complex.js: 2.4.3
- decimal.js: 10.6.0
- escape-latex: 1.2.0
- fraction.js: 5.3.4
- javascript-natural-sort: 0.7.1
- seedrandom: 3.0.5
- tiny-emitter: 2.1.0
- typed-function: 4.2.2
- dev: false
- optional: true
-
- /media-typer@1.1.0:
- resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /merge-descriptors@2.0.0:
- resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
- engines: {node: '>=18'}
- dev: false
-
- /merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
- dev: true
-
- /merge2@1.4.1:
- resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
- engines: {node: '>= 8'}
- dev: false
-
- /micromatch@4.0.8:
- resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
- engines: {node: '>=8.6'}
- dependencies:
- braces: 3.0.3
- picomatch: 2.3.1
- dev: false
-
- /mime-db@1.54.0:
- resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /mime-types@3.0.2:
- resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
- engines: {node: '>=18'}
- dependencies:
- mime-db: 1.54.0
- dev: false
-
- /mimic-fn@2.1.0:
- resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
- engines: {node: '>=6'}
- dev: false
-
- /mimic-fn@4.0.0:
- resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
- engines: {node: '>=12'}
- dev: true
-
- /minimatch@10.1.1:
- resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
- engines: {node: 20 || >=22}
- dependencies:
- '@isaacs/brace-expansion': 5.0.0
- dev: true
-
- /minipass-collect@2.0.1:
- resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==}
- engines: {node: '>=16 || 14 >=14.17'}
- dependencies:
- minipass: 7.1.2
- dev: true
-
- /minipass-fetch@5.0.0:
- resolution: {integrity: sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- minipass: 7.1.2
- minipass-sized: 1.0.3
- minizlib: 3.1.0
- optionalDependencies:
- encoding: 0.1.13
- dev: true
-
- /minipass-flush@1.0.5:
- resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
- engines: {node: '>= 8'}
- dependencies:
- minipass: 3.3.6
- dev: true
-
- /minipass-pipeline@1.2.4:
- resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
- engines: {node: '>=8'}
- dependencies:
- minipass: 3.3.6
- dev: true
-
- /minipass-sized@1.0.3:
- resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
- engines: {node: '>=8'}
- dependencies:
- minipass: 3.3.6
- dev: true
-
- /minipass@3.3.6:
- resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
- engines: {node: '>=8'}
- dependencies:
- yallist: 4.0.0
- dev: true
-
- /minipass@7.1.2:
- resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
- engines: {node: '>=16 || 14 >=14.17'}
-
- /minizlib@3.1.0:
- resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
- engines: {node: '>= 18'}
- dependencies:
- minipass: 7.1.2
-
- /mlly@1.8.0:
- resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
- dependencies:
- acorn: 8.15.0
- pathe: 2.0.3
- pkg-types: 1.3.1
- ufo: 1.6.1
- dev: true
-
- /ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
-
- /msgpackr-extract@3.0.3:
- resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
- hasBin: true
- requiresBuild: true
- dependencies:
- node-gyp-build-optional-packages: 5.2.2
- optionalDependencies:
- '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
- '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
- '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
- '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
- '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
- '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
- dev: false
- optional: true
-
- /msgpackr@1.11.5:
- resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==}
- optionalDependencies:
- msgpackr-extract: 3.0.3
- dev: false
-
- /mz@2.7.0:
- resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
- dependencies:
- any-promise: 1.3.0
- object-assign: 4.1.1
- thenify-all: 1.6.0
- dev: false
-
- /nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
- dev: true
-
- /negotiator@1.0.0:
- resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
- engines: {node: '>= 0.6'}
-
- /node-addon-api@6.1.0:
- resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
- dev: false
-
- /node-addon-api@8.5.0:
- resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
- engines: {node: ^18 || ^20 || >= 21}
- dev: false
-
- /node-gyp-build-optional-packages@5.2.2:
- resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
- hasBin: true
- dependencies:
- detect-libc: 2.1.2
- dev: false
-
- /node-gyp-build@4.8.4:
- resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
- hasBin: true
- dev: false
-
- /node-gyp@12.1.0:
- resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==}
- engines: {node: ^20.17.0 || >=22.9.0}
- hasBin: true
- dependencies:
- env-paths: 2.2.1
- exponential-backoff: 3.1.3
- graceful-fs: 4.2.11
- make-fetch-happen: 15.0.3
- nopt: 9.0.0
- proc-log: 6.1.0
- semver: 7.7.3
- tar: 7.5.2
- tinyglobby: 0.2.15
- which: 6.0.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /nopt@9.0.0:
- resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
- engines: {node: ^20.17.0 || >=22.9.0}
- hasBin: true
- dependencies:
- abbrev: 4.0.0
- dev: true
-
- /npm-run-path@5.3.0:
- resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- dependencies:
- path-key: 4.0.0
- dev: true
-
- /object-assign@4.1.1:
- resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
- engines: {node: '>=0.10.0'}
- dev: false
-
- /object-inspect@1.13.4:
- resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /object-keys@1.1.1:
- resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
- engines: {node: '>= 0.4'}
- dev: false
-
- /on-finished@2.4.1:
- resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
- engines: {node: '>= 0.8'}
- dependencies:
- ee-first: 1.1.1
- dev: false
-
- /once@1.4.0:
- resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- dependencies:
- wrappy: 1.0.2
- dev: false
-
- /onetime@5.1.2:
- resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
- engines: {node: '>=6'}
- dependencies:
- mimic-fn: 2.1.0
- dev: false
-
- /onetime@6.0.0:
- resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
- engines: {node: '>=12'}
- dependencies:
- mimic-fn: 4.0.0
- dev: true
-
- /onnxruntime-common@1.21.0:
- resolution: {integrity: sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==}
- dev: false
-
- /onnxruntime-common@1.22.0-dev.20250409-89f8206ba4:
- resolution: {integrity: sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==}
- dev: false
-
- /onnxruntime-node@1.21.0:
- resolution: {integrity: sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==}
- os: [win32, darwin, linux]
- requiresBuild: true
- dependencies:
- global-agent: 3.0.0
- onnxruntime-common: 1.21.0
- tar: 7.5.2
- dev: false
-
- /onnxruntime-web@1.22.0-dev.20250409-89f8206ba4:
- resolution: {integrity: sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==}
- dependencies:
- flatbuffers: 25.9.23
- guid-typescript: 1.0.9
- long: 5.3.2
- onnxruntime-common: 1.22.0-dev.20250409-89f8206ba4
- platform: 1.3.6
- protobufjs: 7.5.4
- dev: false
-
- /ora@5.4.1:
- resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
- engines: {node: '>=10'}
- dependencies:
- bl: 4.1.0
- chalk: 4.1.2
- cli-cursor: 3.1.0
- cli-spinners: 2.9.2
- is-interactive: 1.0.0
- is-unicode-supported: 0.1.0
- log-symbols: 4.1.0
- strip-ansi: 6.0.1
- wcwidth: 1.0.1
- dev: false
-
- /ordered-binary@1.6.0:
- resolution: {integrity: sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==}
- dev: false
-
- /p-limit@5.0.0:
- resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
- engines: {node: '>=18'}
- dependencies:
- yocto-queue: 1.2.2
- dev: true
-
- /p-map@7.0.4:
- resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==}
- engines: {node: '>=18'}
- dev: true
-
- /parse5-htmlparser2-tree-adapter@6.0.1:
- resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==}
- dependencies:
- parse5: 6.0.1
- dev: false
-
- /parse5@5.1.1:
- resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==}
- dev: false
-
- /parse5@6.0.1:
- resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
- dev: false
-
- /parseurl@1.3.3:
- resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /path-key@3.1.1:
- resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
- engines: {node: '>=8'}
-
- /path-key@4.0.0:
- resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
- engines: {node: '>=12'}
- dev: true
-
- /path-scurry@2.0.1:
- resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
- engines: {node: 20 || >=22}
- dependencies:
- lru-cache: 11.2.4
- minipass: 7.1.2
- dev: true
-
- /path-to-regexp@8.3.0:
- resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
- dev: false
-
- /pathe@1.1.2:
- resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
- dev: true
-
- /pathe@2.0.3:
- resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- dev: true
-
- /pathval@1.1.1:
- resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
- dev: true
-
- /picocolors@1.1.1:
- resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
-
- /picomatch@2.3.1:
- resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
- engines: {node: '>=8.6'}
- dev: false
-
- /picomatch@4.0.3:
- resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
- engines: {node: '>=12'}
- dev: true
-
- /piscina@5.1.4:
- resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==}
- engines: {node: '>=20.x'}
- optionalDependencies:
- '@napi-rs/nice': 1.1.1
- dev: false
-
- /pkce-challenge@5.0.1:
- resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
- engines: {node: '>=16.20.0'}
- dev: false
-
- /pkg-types@1.3.1:
- resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
- dependencies:
- confbox: 0.1.8
- mlly: 1.8.0
- pathe: 2.0.3
- dev: true
-
- /platform@1.3.6:
- resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
- dev: false
-
- /postcss@8.5.6:
- resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
- engines: {node: ^10 || ^12 || >=14}
- dependencies:
- nanoid: 3.3.11
- picocolors: 1.1.1
- source-map-js: 1.2.1
- dev: true
-
- /pretty-format@29.7.0:
- resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/schemas': 29.6.3
- ansi-styles: 5.2.0
- react-is: 18.3.1
- dev: true
-
- /proc-log@6.1.0:
- resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dev: true
-
- /promise-retry@2.0.1:
- resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
- engines: {node: '>=10'}
- dependencies:
- err-code: 2.0.3
- retry: 0.12.0
- dev: true
-
- /protobufjs@7.5.4:
- resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
- engines: {node: '>=12.0.0'}
- requiresBuild: true
- dependencies:
- '@protobufjs/aspromise': 1.1.2
- '@protobufjs/base64': 1.1.2
- '@protobufjs/codegen': 2.0.4
- '@protobufjs/eventemitter': 1.1.0
- '@protobufjs/fetch': 1.1.0
- '@protobufjs/float': 1.0.2
- '@protobufjs/inquire': 1.1.0
- '@protobufjs/path': 1.1.2
- '@protobufjs/pool': 1.1.0
- '@protobufjs/utf8': 1.1.0
- '@types/node': 24.10.1
- long: 5.3.2
- dev: false
-
- /proxy-addr@2.0.7:
- resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
- engines: {node: '>= 0.10'}
- dependencies:
- forwarded: 0.2.0
- ipaddr.js: 1.9.1
- dev: false
-
- /qs@6.14.0:
- resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
- engines: {node: '>=0.6'}
- dependencies:
- side-channel: 1.1.0
- dev: false
-
- /queue-microtask@1.2.3:
- resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- dev: false
-
- /range-parser@1.2.1:
- resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /raw-body@3.0.2:
- resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
- engines: {node: '>= 0.10'}
- dependencies:
- bytes: 3.1.2
- http-errors: 2.0.1
- iconv-lite: 0.7.0
- unpipe: 1.0.0
- dev: false
-
- /react-is@18.3.1:
- resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
- dev: true
-
- /readable-stream@3.6.2:
- resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
- engines: {node: '>= 6'}
- dependencies:
- inherits: 2.0.4
- string_decoder: 1.3.0
- util-deprecate: 1.0.2
- dev: false
-
- /readdirp@4.1.2:
- resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
- engines: {node: '>= 14.18.0'}
- dev: false
-
- /reflect-metadata@0.2.2:
- resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
- dev: false
-
- /require-directory@2.1.1:
- resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
- engines: {node: '>=0.10.0'}
- dev: false
-
- /require-from-string@2.0.2:
- resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
- engines: {node: '>=0.10.0'}
- dev: false
-
- /restore-cursor@3.1.0:
- resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
- engines: {node: '>=8'}
- dependencies:
- onetime: 5.1.2
- signal-exit: 3.0.7
- dev: false
-
- /retry@0.12.0:
- resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
- engines: {node: '>= 4'}
- dev: true
-
- /reusify@1.1.0:
- resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
- engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- dev: false
-
- /roarr@2.15.4:
- resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
- engines: {node: '>=8.0'}
- dependencies:
- boolean: 3.2.0
- detect-node: 2.1.0
- globalthis: 1.0.4
- json-stringify-safe: 5.0.1
- semver-compare: 1.0.0
- sprintf-js: 1.1.3
- dev: false
-
- /rollup@4.53.3:
- resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
- engines: {node: '>=18.0.0', npm: '>=8.0.0'}
- hasBin: true
- dependencies:
- '@types/estree': 1.0.8
- optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.53.3
- '@rollup/rollup-android-arm64': 4.53.3
- '@rollup/rollup-darwin-arm64': 4.53.3
- '@rollup/rollup-darwin-x64': 4.53.3
- '@rollup/rollup-freebsd-arm64': 4.53.3
- '@rollup/rollup-freebsd-x64': 4.53.3
- '@rollup/rollup-linux-arm-gnueabihf': 4.53.3
- '@rollup/rollup-linux-arm-musleabihf': 4.53.3
- '@rollup/rollup-linux-arm64-gnu': 4.53.3
- '@rollup/rollup-linux-arm64-musl': 4.53.3
- '@rollup/rollup-linux-loong64-gnu': 4.53.3
- '@rollup/rollup-linux-ppc64-gnu': 4.53.3
- '@rollup/rollup-linux-riscv64-gnu': 4.53.3
- '@rollup/rollup-linux-riscv64-musl': 4.53.3
- '@rollup/rollup-linux-s390x-gnu': 4.53.3
- '@rollup/rollup-linux-x64-gnu': 4.53.3
- '@rollup/rollup-linux-x64-musl': 4.53.3
- '@rollup/rollup-openharmony-arm64': 4.53.3
- '@rollup/rollup-win32-arm64-msvc': 4.53.3
- '@rollup/rollup-win32-ia32-msvc': 4.53.3
- '@rollup/rollup-win32-x64-gnu': 4.53.3
- '@rollup/rollup-win32-x64-msvc': 4.53.3
- fsevents: 2.3.3
- dev: true
-
- /router@2.2.0:
- resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
- engines: {node: '>= 18'}
- dependencies:
- debug: 4.4.3
- depd: 2.0.0
- is-promise: 4.0.0
- parseurl: 1.3.3
- path-to-regexp: 8.3.0
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /run-parallel@1.2.0:
- resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- dependencies:
- queue-microtask: 1.2.3
- dev: false
-
- /safe-buffer@5.2.1:
- resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
- dev: false
-
- /safer-buffer@2.1.2:
- resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
- requiresBuild: true
-
- /seedrandom@3.0.5:
- resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
- requiresBuild: true
- dev: false
- optional: true
-
- /semver-compare@1.0.0:
- resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
- dev: false
-
- /semver@7.7.3:
- resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
- engines: {node: '>=10'}
- hasBin: true
-
- /send@1.2.0:
- resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
- engines: {node: '>= 18'}
- dependencies:
- debug: 4.4.3
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- fresh: 2.0.0
- http-errors: 2.0.1
- mime-types: 3.0.2
- ms: 2.1.3
- on-finished: 2.4.1
- range-parser: 1.2.1
- statuses: 2.0.2
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /serialize-error@7.0.1:
- resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
- engines: {node: '>=10'}
- dependencies:
- type-fest: 0.13.1
- dev: false
-
- /serve-static@2.2.0:
- resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
- engines: {node: '>= 18'}
- dependencies:
- encodeurl: 2.0.0
- escape-html: 1.0.3
- parseurl: 1.3.3
- send: 1.2.0
- transitivePeerDependencies:
- - supports-color
- dev: false
-
- /setprototypeof@1.2.0:
- resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
- dev: false
-
- /sharp@0.34.5:
- resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- requiresBuild: true
- dependencies:
- '@img/colour': 1.0.0
- detect-libc: 2.1.2
- semver: 7.7.3
- optionalDependencies:
- '@img/sharp-darwin-arm64': 0.34.5
- '@img/sharp-darwin-x64': 0.34.5
- '@img/sharp-libvips-darwin-arm64': 1.2.4
- '@img/sharp-libvips-darwin-x64': 1.2.4
- '@img/sharp-libvips-linux-arm': 1.2.4
- '@img/sharp-libvips-linux-arm64': 1.2.4
- '@img/sharp-libvips-linux-ppc64': 1.2.4
- '@img/sharp-libvips-linux-riscv64': 1.2.4
- '@img/sharp-libvips-linux-s390x': 1.2.4
- '@img/sharp-libvips-linux-x64': 1.2.4
- '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
- '@img/sharp-libvips-linuxmusl-x64': 1.2.4
- '@img/sharp-linux-arm': 0.34.5
- '@img/sharp-linux-arm64': 0.34.5
- '@img/sharp-linux-ppc64': 0.34.5
- '@img/sharp-linux-riscv64': 0.34.5
- '@img/sharp-linux-s390x': 0.34.5
- '@img/sharp-linux-x64': 0.34.5
- '@img/sharp-linuxmusl-arm64': 0.34.5
- '@img/sharp-linuxmusl-x64': 0.34.5
- '@img/sharp-wasm32': 0.34.5
- '@img/sharp-win32-arm64': 0.34.5
- '@img/sharp-win32-ia32': 0.34.5
- '@img/sharp-win32-x64': 0.34.5
- dev: false
-
- /shebang-command@2.0.0:
- resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
- engines: {node: '>=8'}
- dependencies:
- shebang-regex: 3.0.0
-
- /shebang-regex@3.0.0:
- resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
- engines: {node: '>=8'}
-
- /side-channel-list@1.0.0:
- resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
- engines: {node: '>= 0.4'}
- dependencies:
- es-errors: 1.3.0
- object-inspect: 1.13.4
- dev: false
-
- /side-channel-map@1.0.1:
- resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- object-inspect: 1.13.4
- dev: false
-
- /side-channel-weakmap@1.0.2:
- resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- object-inspect: 1.13.4
- side-channel-map: 1.0.1
- dev: false
-
- /side-channel@1.1.0:
- resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
- engines: {node: '>= 0.4'}
- dependencies:
- es-errors: 1.3.0
- object-inspect: 1.13.4
- side-channel-list: 1.0.0
- side-channel-map: 1.0.1
- side-channel-weakmap: 1.0.2
- dev: false
-
- /siginfo@2.0.0:
- resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
- dev: true
-
- /signal-exit@3.0.7:
- resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
- dev: false
-
- /signal-exit@4.1.0:
- resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
- engines: {node: '>=14'}
- dev: true
-
- /simsimd@6.5.5:
- resolution: {integrity: sha512-iMZLxr/rWOOhg+L0QimMMvehntzJrPMLizvOXr3VZtQHazskaIRuBtui9QtlZf8iXuPFDBwzLwAdrm/J3U/HwA==}
- engines: {node: ~10 >=10.20 || >=12.17}
- requiresBuild: true
- dependencies:
- bindings: 1.5.0
- node-addon-api: 8.5.0
- node-gyp-build: 4.8.4
- optionalDependencies:
- benchmark: 2.1.4
- mathjs: 14.9.1
- usearch: 2.21.4
- dev: false
-
- /sisteransi@1.0.5:
- resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
- dev: false
-
- /smart-buffer@4.2.0:
- resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
- engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
- dev: true
-
- /socks-proxy-agent@8.0.5:
- resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
- engines: {node: '>= 14'}
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- socks: 2.8.7
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /socks@2.8.7:
- resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
- engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
- dependencies:
- ip-address: 10.1.0
- smart-buffer: 4.2.0
- dev: true
-
- /source-map-js@1.2.1:
- resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
- engines: {node: '>=0.10.0'}
- dev: true
-
- /sprintf-js@1.1.3:
- resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
- dev: false
-
- /ssri@13.0.0:
- resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- minipass: 7.1.2
- dev: true
-
- /stackback@0.0.2:
- resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- dev: true
-
- /statuses@2.0.2:
- resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /std-env@3.10.0:
- resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
- dev: true
-
- /string-width@4.2.3:
- resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
- engines: {node: '>=8'}
- dependencies:
- emoji-regex: 8.0.0
- is-fullwidth-code-point: 3.0.0
- strip-ansi: 6.0.1
- dev: false
-
- /string_decoder@1.3.0:
- resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
- dependencies:
- safe-buffer: 5.2.1
- dev: false
-
- /strip-ansi@6.0.1:
- resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
- engines: {node: '>=8'}
- dependencies:
- ansi-regex: 5.0.1
- dev: false
-
- /strip-final-newline@3.0.0:
- resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
- engines: {node: '>=12'}
- dev: true
-
- /strip-literal@2.1.1:
- resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
- dependencies:
- js-tokens: 9.0.1
- dev: true
-
- /supports-color@7.2.0:
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
- engines: {node: '>=8'}
- dependencies:
- has-flag: 4.0.0
- dev: false
-
- /table-layout@4.1.1:
- resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==}
- engines: {node: '>=12.17'}
- dependencies:
- array-back: 6.2.2
- wordwrapjs: 5.1.1
- dev: false
-
- /tar@7.5.2:
- resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==}
- engines: {node: '>=18'}
- dependencies:
- '@isaacs/fs-minipass': 4.0.1
- chownr: 3.0.0
- minipass: 7.1.2
- minizlib: 3.1.0
- yallist: 5.0.0
-
- /thenify-all@1.6.0:
- resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
- engines: {node: '>=0.8'}
- dependencies:
- thenify: 3.3.1
- dev: false
-
- /thenify@3.3.1:
- resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
- dependencies:
- any-promise: 1.3.0
- dev: false
-
- /tiny-emitter@2.1.0:
- resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
- requiresBuild: true
- dev: false
- optional: true
-
- /tinybench@2.9.0:
- resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- dev: true
-
- /tinyglobby@0.2.15:
- resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
- engines: {node: '>=12.0.0'}
- dependencies:
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
- dev: true
-
- /tinypool@0.8.4:
- resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
- engines: {node: '>=14.0.0'}
- dev: true
-
- /tinyspy@2.2.1:
- resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
- engines: {node: '>=14.0.0'}
- dev: true
-
- /to-regex-range@5.0.1:
- resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
- engines: {node: '>=8.0'}
- dependencies:
- is-number: 7.0.0
- dev: false
-
- /toidentifier@1.0.1:
- resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
- engines: {node: '>=0.6'}
- dev: false
-
- /ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3):
- resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
- hasBin: true
- peerDependencies:
- '@swc/core': '>=1.2.50'
- '@swc/wasm': '>=1.2.50'
- '@types/node': '*'
- typescript: '>=2.7'
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- '@swc/wasm':
- optional: true
- dependencies:
- '@cspotcode/source-map-support': 0.8.1
- '@tsconfig/node10': 1.0.12
- '@tsconfig/node12': 1.0.11
- '@tsconfig/node14': 1.0.3
- '@tsconfig/node16': 1.0.4
- '@types/node': 24.10.1
- acorn: 8.15.0
- acorn-walk: 8.3.4
- arg: 4.1.3
- create-require: 1.1.1
- diff: 4.0.2
- make-error: 1.3.6
- typescript: 5.9.3
- v8-compile-cache-lib: 3.0.1
- yn: 3.1.1
- dev: true
-
- /tslib@2.8.1:
- resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
- dev: false
-
- /type-detect@4.1.0:
- resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
- engines: {node: '>=4'}
- dev: true
-
- /type-fest@0.13.1:
- resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
- engines: {node: '>=10'}
- dev: false
-
- /type-is@2.0.1:
- resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
- engines: {node: '>= 0.6'}
- dependencies:
- content-type: 1.0.5
- media-typer: 1.1.0
- mime-types: 3.0.2
- dev: false
-
- /typed-function@4.2.2:
- resolution: {integrity: sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==}
- engines: {node: '>= 18'}
- requiresBuild: true
- dev: false
- optional: true
-
- /typescript@5.9.3:
- resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
- engines: {node: '>=14.17'}
- hasBin: true
- dev: true
-
- /typical@4.0.0:
- resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==}
- engines: {node: '>=8'}
- dev: false
-
- /typical@7.3.0:
- resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==}
- engines: {node: '>=12.17'}
- dev: false
-
- /ufo@1.6.1:
- resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
- dev: true
-
- /undici-types@6.21.0:
- resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
- dev: false
-
- /undici-types@7.16.0:
- resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
-
- /unique-filename@5.0.0:
- resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- unique-slug: 6.0.0
- dev: true
-
- /unique-slug@6.0.0:
- resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==}
- engines: {node: ^20.17.0 || >=22.9.0}
- dependencies:
- imurmurhash: 0.1.4
- dev: true
-
- /unpipe@1.0.0:
- resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /usearch@2.21.4:
- resolution: {integrity: sha512-AzavmhAfGubKOLdR3S6Rh/6dvgXqxL+6Fzs1fsgKneQG8i7oLX2Gpqsc4EfdSyKb4sQXhavIiKIguMA2R3cRaA==}
- engines: {node: ~10 >=10.20 || >=12.17}
- requiresBuild: true
- dependencies:
- bindings: 1.5.0
- node-addon-api: 8.5.0
- node-gyp-build: 4.8.4
- dev: false
- optional: true
-
- /util-deprecate@1.0.2:
- resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
- dev: false
-
- /uuid@9.0.1:
- resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
- hasBin: true
- dev: false
-
- /v8-compile-cache-lib@3.0.1:
- resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
- dev: true
-
- /vary@1.1.2:
- resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
- engines: {node: '>= 0.8'}
- dev: false
-
- /vite-node@1.6.1(@types/node@24.10.1):
- resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
- engines: {node: ^18.0.0 || >=20.0.0}
- hasBin: true
- dependencies:
- cac: 6.7.14
- debug: 4.4.3
- pathe: 1.1.2
- picocolors: 1.1.1
- vite: 5.4.21(@types/node@24.10.1)
- transitivePeerDependencies:
- - '@types/node'
- - less
- - lightningcss
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- dev: true
-
- /vite@5.4.21(@types/node@24.10.1):
- resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
- engines: {node: ^18.0.0 || >=20.0.0}
- hasBin: true
- peerDependencies:
- '@types/node': ^18.0.0 || >=20.0.0
- less: '*'
- lightningcss: ^1.21.0
- sass: '*'
- sass-embedded: '*'
- stylus: '*'
- sugarss: '*'
- terser: ^5.4.0
- peerDependenciesMeta:
- '@types/node':
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- sass-embedded:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- dependencies:
- '@types/node': 24.10.1
- esbuild: 0.21.5
- postcss: 8.5.6
- rollup: 4.53.3
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
- /vitest@1.6.1(@types/node@24.10.1):
- resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
- engines: {node: ^18.0.0 || >=20.0.0}
- hasBin: true
- peerDependencies:
- '@edge-runtime/vm': '*'
- '@types/node': ^18.0.0 || >=20.0.0
- '@vitest/browser': 1.6.1
- '@vitest/ui': 1.6.1
- happy-dom: '*'
- jsdom: '*'
- peerDependenciesMeta:
- '@edge-runtime/vm':
- optional: true
- '@types/node':
- optional: true
- '@vitest/browser':
- optional: true
- '@vitest/ui':
- optional: true
- happy-dom:
- optional: true
- jsdom:
- optional: true
- dependencies:
- '@types/node': 24.10.1
- '@vitest/expect': 1.6.1
- '@vitest/runner': 1.6.1
- '@vitest/snapshot': 1.6.1
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- acorn-walk: 8.3.4
- chai: 4.5.0
- debug: 4.4.3
- execa: 8.0.1
- local-pkg: 0.5.1
- magic-string: 0.30.21
- pathe: 1.1.2
- picocolors: 1.1.1
- std-env: 3.10.0
- strip-literal: 2.1.1
- tinybench: 2.9.0
- tinypool: 0.8.4
- vite: 5.4.21(@types/node@24.10.1)
- vite-node: 1.6.1(@types/node@24.10.1)
- why-is-node-running: 2.3.0
- transitivePeerDependencies:
- - less
- - lightningcss
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- dev: true
-
- /wcwidth@1.0.1:
- resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
- dependencies:
- defaults: 1.0.4
- dev: false
-
- /weak-lru-cache@1.2.2:
- resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==}
- dev: false
-
- /web-tree-sitter@0.25.10:
- resolution: {integrity: sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==}
- peerDependencies:
- '@types/emscripten': ^1.40.0
- peerDependenciesMeta:
- '@types/emscripten':
- optional: true
- dev: false
-
- /which@2.0.2:
- resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
- engines: {node: '>= 8'}
- hasBin: true
- dependencies:
- isexe: 2.0.0
-
- /which@6.0.0:
- resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==}
- engines: {node: ^20.17.0 || >=22.9.0}
- hasBin: true
- dependencies:
- isexe: 3.1.1
- dev: true
-
- /why-is-node-running@2.3.0:
- resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
- engines: {node: '>=8'}
- hasBin: true
- dependencies:
- siginfo: 2.0.0
- stackback: 0.0.2
- dev: true
-
- /wordwrapjs@5.1.1:
- resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==}
- engines: {node: '>=12.17'}
- dev: false
-
- /wrap-ansi@7.0.0:
- resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
- engines: {node: '>=10'}
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
- dev: false
-
- /wrappy@1.0.2:
- resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
- dev: false
-
- /y18n@5.0.8:
- resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
- engines: {node: '>=10'}
- dev: false
-
- /yallist@4.0.0:
- resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
- dev: true
-
- /yallist@5.0.0:
- resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
- engines: {node: '>=18'}
-
- /yargs-parser@20.2.9:
- resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
- engines: {node: '>=10'}
- dev: false
-
- /yargs@16.2.0:
- resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
- engines: {node: '>=10'}
- dependencies:
- cliui: 7.0.4
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 20.2.9
- dev: false
-
- /yn@3.1.1:
- resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
- engines: {node: '>=6'}
- dev: true
-
- /yocto-queue@1.2.2:
- resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
- engines: {node: '>=12.20'}
- dev: true
-
- /zod-to-json-schema@3.25.0(zod@4.1.13):
- resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==}
- peerDependencies:
- zod: ^3.25 || ^4
- dependencies:
- zod: 4.1.13
- dev: false
-
- /zod@4.1.13:
- resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
diff --git a/public/bench.png b/public/bench.png
deleted file mode 100644
index 94f56bba..00000000
Binary files a/public/bench.png and /dev/null differ
diff --git a/scripts/compare-engines.ts b/scripts/compare-engines.ts
deleted file mode 100644
index 22dd4ab8..00000000
--- a/scripts/compare-engines.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import { spawnSync } from "node:child_process";
-import * as fs from "node:fs";
-import * as os from "node:os";
-import * as path from "node:path";
-
-type RunResult = {
- engine: string;
- cwd: string;
- cmd: string[];
- exitCode: number | null;
- signal: NodeJS.Signals | null;
- realSec?: number;
- userSec?: number;
- sysSec?: number;
- maxRssKb?: number;
- peakRssKb?: number;
- stdout: string;
- stderr: string;
- timeOutput: string;
-};
-
-type EngineConfig = {
- name: string;
- bin: string;
-};
-
-const DEFAULT_DATASET = path.resolve(
- __dirname,
- "../../opencode-old/packages/opencode/src",
-);
-const DEFAULT_OLD_BIN = path.resolve(
- __dirname,
- "../../old-osgrep/dist/index.js",
-);
-const DEFAULT_NEW_BIN = path.resolve(__dirname, "../dist/index.js");
-const DEFAULT_RUNS = Number.parseInt(process.env.RUNS || "1", 10) || 1;
-
-function parseTimeOutput(stderr: string) {
- const realSec = matchFloat(stderr, /([\d.]+)\s+real/);
- const userSec = matchFloat(stderr, /([\d.]+)\s+user/);
- const sysSec = matchFloat(stderr, /([\d.]+)\s+sys/);
- const maxRssKb = matchInt(
- stderr,
- /^\s*([\d]+)\s+maximum resident set size/im,
- );
- const peakRssKb = matchInt(stderr, /^\s*([\d]+)\s+peak memory footprint/im);
- return { realSec, userSec, sysSec, maxRssKb, peakRssKb };
-}
-
-function matchFloat(text: string, re: RegExp) {
- const m = text.match(re);
- return m ? Number.parseFloat(m[1]) : undefined;
-}
-
-function matchInt(text: string, re: RegExp) {
- const m = text.match(re);
- return m ? Number.parseInt(m[1], 10) : undefined;
-}
-
-function runTimedIndex(
- engine: EngineConfig,
- cwd: string,
- env: NodeJS.ProcessEnv,
-): RunResult {
- const cmd = [engine.bin, "index", "--reset"];
- const timeCmd = ["/usr/bin/time", "-l", "node", ...cmd];
- const proc = spawnSync(timeCmd[0], timeCmd.slice(1), {
- cwd,
- env,
- encoding: "utf-8",
- maxBuffer: 10 * 1024 * 1024,
- });
-
- const parsed = parseTimeOutput(proc.stderr ?? "");
-
- return {
- engine: engine.name,
- cwd,
- cmd,
- exitCode: proc.status,
- signal: proc.signal,
- ...parsed,
- stdout: proc.stdout ?? "",
- stderr: proc.stderr ?? "",
- timeOutput: proc.stderr ?? "",
- };
-}
-
-function copyDataset(src: string, dest: string) {
- fs.rmSync(dest, { recursive: true, force: true });
- fs.cpSync(src, dest, { recursive: true });
-}
-
-function formatMb(kb?: number) {
- if (!kb || Number.isNaN(kb)) return "n/a";
- return `${(kb / 1024).toFixed(1)} MB`;
-}
-
-function ensureDir(dir: string) {
- fs.mkdirSync(dir, { recursive: true });
-}
-
-function main() {
- const dataset = path.resolve(process.env.DATASET || DEFAULT_DATASET);
- const oldBin = path.resolve(process.env.OLD_BIN || DEFAULT_OLD_BIN);
- const newBin = path.resolve(process.env.NEW_BIN || DEFAULT_NEW_BIN);
- const runs = DEFAULT_RUNS;
-
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "osgrep-bench-"));
- const resultsDir = path.resolve(__dirname, "../benchmark/results");
- ensureDir(resultsDir);
-
- const env: NodeJS.ProcessEnv = {
- ...process.env,
- OSGREP_WORKER_THREADS: process.env.OSGREP_WORKER_THREADS || "4",
- OSGREP_WORKER_TASK_TIMEOUT_MS:
- process.env.OSGREP_WORKER_TASK_TIMEOUT_MS || "240000",
- OSGREP_LOG_PLAIN: "1",
- };
-
- const engines: EngineConfig[] = [
- { name: "old", bin: oldBin },
- { name: "new", bin: newBin },
- ];
-
- const results: RunResult[] = [];
-
- engines.forEach((engine) => {
- for (let i = 0; i < runs; i += 1) {
- const stageDir = path.join(tmpRoot, `${engine.name}-run-${i + 1}`);
- copyDataset(dataset, stageDir);
- const run = runTimedIndex(engine, stageDir, env);
- results.push(run);
- console.log(
- `[${engine.name}] run ${i + 1}: real=${run.realSec?.toFixed(2) ?? "?"}s rss=${formatMb(run.maxRssKb)} exit=${run.exitCode}`,
- );
- }
- });
-
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
- const summaryPath = path.join(resultsDir, `engine-compare-${timestamp}.json`);
- const latestPath = path.join(resultsDir, `engine-compare-latest.json`);
-
- const payload = {
- timestamp,
- dataset,
- runs,
- envOverrides: {
- OSGREP_WORKER_THREADS: env.OSGREP_WORKER_THREADS,
- OSGREP_WORKER_TASK_TIMEOUT_MS: env.OSGREP_WORKER_TASK_TIMEOUT_MS,
- },
- engines,
- results,
- };
-
- fs.writeFileSync(summaryPath, JSON.stringify(payload, null, 2));
- fs.writeFileSync(latestPath, JSON.stringify(payload, null, 2));
- console.log(`Saved results to ${summaryPath}`);
- console.log(`Latest alias: ${latestPath}`);
- console.log(`Staging kept at: ${tmpRoot}`);
-}
-
-main();
diff --git a/scripts/index-bench.sh b/scripts/index-bench.sh
deleted file mode 100755
index 4285f6ae..00000000
--- a/scripts/index-bench.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Simple indexing benchmark harness for macOS.
-# - Runs osgrep indexing against a target directory with multiple env configs.
-# - Wipes Lance data and meta between runs to keep results comparable.
-
-TARGET_DIR="${TARGET_DIR:-/Users/ryandonofrio/Desktop/osgrep2/opencode/packages/opencode/src}"
-OSGREP_BIN="${OSGREP_BIN:-node dist/index.js}"
-LOG_DIR="${LOG_DIR:-./benchmarks}"
-LOG_FILE="${LOG_FILE:-${LOG_DIR}/index-bench-$(date +%Y%m%d-%H%M%S).log}"
-TIMEOUT_SEC="${TIMEOUT_SEC:-600}"
-
-mkdir -p "${LOG_DIR}"
-
-CONFIGS=(
- "OSGREP_THREADS=1 OSGREP_WORKER_BATCH_SIZE=8"
- "OSGREP_THREADS=1 OSGREP_WORKER_BATCH_SIZE=12"
- "OSGREP_THREADS=2 OSGREP_WORKER_BATCH_SIZE=12"
- "OSGREP_THREADS=2 OSGREP_WORKER_BATCH_SIZE=16"
-)
-
-clean_state() {
- echo "Cleaning ~/.osgrep data/meta..."
- rm -rf "${HOME}/.osgrep/data" "${HOME}/.osgrep/meta.json" "${HOME}/.osgrep/meta.json.tmp"
-}
-
-run_one() {
- local env_line="$1"
- echo "==== ${env_line} ====" | tee -a "${LOG_FILE}"
- clean_state
- SECONDS=0
- local cmd="${env_line} OSGREP_DEBUG_INDEX=1 OSGREP_PROFILE=1 OSGREP_SKIP_META_SAVE=1 ${OSGREP_BIN} index --path \"${TARGET_DIR}\" --reset"
- # /usr/bin/time -l (macOS) for resource stats; falls back to builtin time if unavailable.
- if command -v /usr/bin/time >/dev/null 2>&1; then
- cmd="/usr/bin/time -l ${cmd}"
- else
- cmd="time ${cmd}"
- fi
- # Enforce timeout (perl alarm works on macOS)
- perl -e 'alarm shift; exec @ARGV' "${TIMEOUT_SEC}" bash -lc "${cmd}" 2>&1 | tee -a "${LOG_FILE}"
- echo "Elapsed: ${SECONDS}s" | tee -a "${LOG_FILE}"
- echo | tee -a "${LOG_FILE}"
-}
-
-echo "Benchmarking ${TARGET_DIR}" | tee "${LOG_FILE}"
-echo "Log: ${LOG_FILE}"
-echo
-
-for cfg in "${CONFIGS[@]}"; do
- run_one "${cfg}"
-done
-
-echo "Done. Results recorded in ${LOG_FILE}"
diff --git a/scripts/sync-eval-cases.ts b/scripts/sync-eval-cases.ts
deleted file mode 100644
index 3da66016..00000000
--- a/scripts/sync-eval-cases.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-
-const newEvalPath = path.resolve(__dirname, "../src/eval.ts");
-const oldEvalPath = path.resolve(__dirname, "../../old-osgrep/src/eval.ts");
-
-function extractCases(content: string, label: string) {
- const match = content.match(
- /export const cases: EvalCase\[] = ([\s\S]*?)\nconst topK/,
- );
- if (!match) {
- throw new Error(`Could not find cases array in ${label}`);
- }
- return match[1].trim();
-}
-
-function replaceCases(targetContent: string, newCases: string) {
- return targetContent.replace(
- /export const cases: EvalCase\[] = ([\s\S]*?)\nconst topK/,
- `export const cases: EvalCase[] = ${newCases}\nconst topK`,
- );
-}
-
-function main() {
- const newEval = fs.readFileSync(newEvalPath, "utf-8");
- const oldEval = fs.readFileSync(oldEvalPath, "utf-8");
-
- const newCases = extractCases(newEval, newEvalPath);
- const updatedOld = replaceCases(oldEval, newCases);
-
- fs.writeFileSync(oldEvalPath, updatedOld);
- console.log(`Synced eval cases from ${newEvalPath} -> ${oldEvalPath}`);
-}
-
-main();
diff --git a/scripts/verify_skeleton.ts b/scripts/verify_skeleton.ts
deleted file mode 100644
index 8c33b888..00000000
--- a/scripts/verify_skeleton.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import * as path from "path";
-import { VectorDB } from "../src/lib/store/vector-db";
-import { ensureProjectPaths } from "../src/lib/utils/project-root";
-
-async function main() {
- const root = process.cwd();
- const paths = ensureProjectPaths(root);
- const db = new VectorDB(paths.lancedbDir);
-
- try {
- const table = await db.ensureTable();
- const results = await table
- .query()
- .where("path LIKE '%src/commands/skeleton.ts%' AND is_anchor = true")
- .limit(1)
- .toArray();
-
- console.log(`Found ${results.length} anchor chunks.`);
-
- for (const r of results) {
- console.log(`File: ${r.path}`);
- const skel = r["file_skeleton"];
- if (skel && typeof skel === "string" && skel.length > 0) {
- console.log("ā
Has skeleton (" + skel.length + " chars)");
- console.log(skel.substring(0, 50) + "...");
- } else {
- console.log("ā No skeleton found!");
- }
- console.log("---");
- }
- } catch (e) {
- console.error(e);
- } finally {
- await db.close();
- }
-}
-
-main();
diff --git a/src/commands/claude-code.ts b/src/commands/agent/claude-code.ts
similarity index 100%
rename from src/commands/claude-code.ts
rename to src/commands/agent/claude-code.ts
diff --git a/src/commands/codex.ts b/src/commands/agent/codex.ts
similarity index 100%
rename from src/commands/codex.ts
rename to src/commands/agent/codex.ts
diff --git a/src/commands/droid.ts b/src/commands/agent/droid.ts
similarity index 100%
rename from src/commands/droid.ts
rename to src/commands/agent/droid.ts
diff --git a/src/commands/mcp.ts b/src/commands/agent/mcp.ts
similarity index 94%
rename from src/commands/mcp.ts
rename to src/commands/agent/mcp.ts
index 0276a984..3c6b7788 100644
--- a/src/commands/mcp.ts
+++ b/src/commands/agent/mcp.ts
@@ -7,9 +7,9 @@ import {
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Command } from "commander";
-import { initialSync } from "../lib/index/syncer";
-import { ensureSetup } from "../lib/setup/setup-helpers";
-import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
+import { initialSync } from "../../lib/index/syncer";
+import { ensureSetup } from "../../lib/setup/setup-helpers";
+import { ensureProjectPaths, findProjectRoot } from "../../lib/utils/project-root";
export const mcp = new Command("mcp")
.description("Start MCP server for osgrep")
diff --git a/src/commands/opencode.ts b/src/commands/agent/opencode.ts
similarity index 98%
rename from src/commands/opencode.ts
rename to src/commands/agent/opencode.ts
index 82a871d0..b503de38 100644
--- a/src/commands/opencode.ts
+++ b/src/commands/agent/opencode.ts
@@ -80,9 +80,6 @@ Read the specific line range, not the whole file.
# Trace call graph (who calls X, what X calls)
osgrep trace handleAuth
-# Skeleton of a huge file (to find which ranges to read)
-osgrep skeleton src/giant-2000-line-file.ts
-
# Just file paths when you only need locations
osgrep "authentication" --compact
diff --git a/src/commands/search.ts b/src/commands/search.ts
index 6765db03..d5a27922 100644
--- a/src/commands/search.ts
+++ b/src/commands/search.ts
@@ -1,4 +1,3 @@
-import * as fs from "node:fs";
import * as path from "node:path";
import type { Command } from "commander";
import { Command as CommanderCommand } from "commander";
@@ -9,9 +8,12 @@ import {
} from "../lib/index/sync-helpers";
import { initialSync } from "../lib/index/syncer";
import { Searcher } from "../lib/search/searcher";
+import type {
+ ExpandedResult,
+ ExpandOptions,
+ ExpansionNode,
+} from "../lib/search/expansion-types";
import { ensureSetup } from "../lib/setup/setup-helpers";
-import { Skeletonizer } from "../lib/skeleton";
-import { getStoredSkeleton } from "../lib/skeleton/retriever";
import type {
ChunkType,
FileMetadata,
@@ -22,7 +24,116 @@ import { gracefulExit } from "../lib/utils/exit";
import { formatTextResults, type TextResult } from "../lib/utils/formatter";
import { isLocked } from "../lib/utils/lock";
import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
-import { getServerForProject } from "../lib/utils/server-registry";
+import { getServerForProject, unregisterServer } from "../lib/utils/server-registry";
+
+/**
+ * Get expand options for --deep mode.
+ * Sweet spot config: callers + symbols at depth 2.
+ */
+function getDeepExpandOptions(deep: boolean): ExpandOptions | undefined {
+ if (!deep) return undefined;
+
+ return {
+ maxDepth: 2,
+ maxExpanded: 20,
+ maxTokens: 0,
+ strategies: ["callers", "symbols"],
+ };
+}
+
+/**
+ * Format expanded results for plain text output.
+ */
+function formatExpandedPlain(
+ expanded: ExpandedResult,
+ projectRoot: string,
+): string {
+ const lines: string[] = [];
+
+ // Stats header
+ const { stats } = expanded;
+ lines.push(
+ `\n--- Expanded Results (${expanded.expanded.length} chunks) ---`,
+ );
+ lines.push(
+ `symbols: ${stats.symbolsResolved} | callers: ${stats.callersFound} | neighbors: ${stats.neighborsAdded}${
+ stats.budgetRemaining !== undefined
+ ? ` | tokens: ${stats.totalTokens} (${stats.budgetRemaining} remaining)`
+ : ""
+ }${expanded.truncated ? " [truncated]" : ""}`,
+ );
+ lines.push("");
+
+ // Group by relationship type
+ const byRelation = new Map();
+ for (const node of expanded.expanded) {
+ const key = node.relationship;
+ if (!byRelation.has(key)) byRelation.set(key, []);
+ byRelation.get(key)!.push(node);
+ }
+
+ for (const [relation, nodes] of byRelation) {
+ lines.push(`[${relation}]`);
+ for (const node of nodes) {
+ const rawPath =
+ typeof (node.chunk.metadata as FileMetadata | undefined)?.path ===
+ "string"
+ ? ((node.chunk.metadata as FileMetadata).path as string)
+ : "Unknown";
+ const relPath = path.isAbsolute(rawPath)
+ ? path.relative(projectRoot, rawPath)
+ : rawPath;
+ const start = node.chunk.generated_metadata?.start_line ?? 0;
+ const defined = node.chunk.defined_symbols?.slice(0, 3).join(", ") || "";
+ lines.push(
+ ` ${relPath}:${start + 1} via="${node.via}" d=${node.depth}${
+ defined ? ` [${defined}]` : ""
+ }`,
+ );
+ }
+ lines.push("");
+ }
+
+ return lines.join("\n");
+}
+
+/**
+ * Format expanded results in compact TSV format.
+ */
+function formatExpandedCompact(
+ expanded: ExpandedResult,
+ projectRoot: string,
+): string {
+ const lines: string[] = [];
+ lines.push(`expanded\tcount=${expanded.expanded.length}\ttruncated=${expanded.truncated}`);
+ lines.push("path\tlines\trelation\tvia\tdepth\tdefined");
+
+ for (const node of expanded.expanded) {
+ const rawPath =
+ typeof (node.chunk.metadata as FileMetadata | undefined)?.path === "string"
+ ? ((node.chunk.metadata as FileMetadata).path as string)
+ : "Unknown";
+ const relPath = path.isAbsolute(rawPath)
+ ? path.relative(projectRoot, rawPath)
+ : rawPath;
+ const start = node.chunk.generated_metadata?.start_line ?? 0;
+ const end = node.chunk.generated_metadata?.end_line ?? start;
+ const defined = node.chunk.defined_symbols?.slice(0, 3).join(",") || "-";
+
+ lines.push(
+ [
+ relPath,
+ `${start + 1}-${end + 1}`,
+ node.relationship,
+ node.via,
+ String(node.depth),
+ defined,
+ ].join("\t"),
+ );
+ }
+
+ return lines.join("\n");
+}
function toTextResults(data: SearchResponse["data"]): TextResult[] {
return data.map((r) => {
@@ -262,99 +373,10 @@ function formatCompactTable(
return formatCompactPretty(hits, projectRoot, query, termWidth, true);
}
-// Reuse Skeletonizer instance
-let globalSkeletonizer: Skeletonizer | null = null;
-
-async function outputSkeletons(
- results: any[],
- projectRoot: string,
- limit: number,
- db?: VectorDB | null,
-): Promise {
- const seenPaths = new Set();
- const filesToProcess: string[] = [];
-
- for (const result of results) {
- const p = (result.metadata as any)?.path;
- if (typeof p === "string" && !seenPaths.has(p)) {
- seenPaths.add(p);
- filesToProcess.push(p);
- if (filesToProcess.length >= limit) break;
- }
- }
-
- if (filesToProcess.length === 0) {
- console.log("No skeleton matches found.");
- return;
- }
-
- // Reuse or init skeletonizer for fallbacks
- if (!globalSkeletonizer) {
- globalSkeletonizer = new Skeletonizer();
- // Lazy init only if we actually fallback
- }
-
- const skeletonOpts = { includeSummary: true };
- const skeletonResults: Array<{
- file: string;
- skeleton: string;
- tokens: number;
- error?: string;
- }> = [];
-
- for (const relPath of filesToProcess) {
- // 1. Try DB cache
- if (db) {
- const cached = await getStoredSkeleton(db, relPath);
- if (cached) {
- skeletonResults.push({
- file: relPath,
- skeleton: cached,
- tokens: Math.ceil(cached.length / 4), // Rough estimate
- });
- continue;
- }
- }
-
- // 2. Fallback to fresh generation
- await globalSkeletonizer.init();
- const absPath = path.resolve(projectRoot, relPath);
- if (!fs.existsSync(absPath)) {
- skeletonResults.push({
- file: relPath,
- skeleton: `// File not found: ${relPath}`,
- tokens: 0,
- error: "File not found",
- });
- continue;
- }
-
- const content = fs.readFileSync(absPath, "utf-8");
- const res = await globalSkeletonizer.skeletonizeFile(
- relPath,
- content,
- skeletonOpts,
- );
- skeletonResults.push({
- file: relPath,
- skeleton: res.skeleton,
- tokens: res.tokenEstimate,
- error: res.error,
- });
- }
-
- // Since search doesn't support --json explicitly yet, we just print text.
- // But if we ever add it, we have the structure.
- for (const res of skeletonResults) {
- console.log(res.skeleton);
- console.log(""); // Separator
- }
-}
-
export const search: Command = new CommanderCommand("search")
.description("File pattern searcher")
.option(
- "-m , --max-count ",
+ "-m, --max-count ",
"The maximum number of results to return (total)",
"5",
)
@@ -373,6 +395,12 @@ export const search: Command = new CommanderCommand("search")
)
.option("--plain", "Disable ANSI colors and use simpler formatting", false)
+ .option(
+ "--deep",
+ "Include related code (callers, definitions) for architectural context",
+ false,
+ )
+
.option(
"-s, --sync",
"Syncs the local files to the store before searching",
@@ -383,27 +411,22 @@ export const search: Command = new CommanderCommand("search")
"Show what would be indexed without actually indexing",
false,
)
- .option(
- "--skeleton",
- "Show code skeleton for matching files instead of snippets",
- false,
- )
.argument("", "The pattern to search for")
.argument("[path]", "The path to search in")
.allowUnknownOption(true)
.allowExcessArguments(true)
.action(async (pattern, exec_path, _options, cmd) => {
const options: {
- m: string;
+ maxCount: string;
content: boolean;
perFile: string;
scores: boolean;
minScore: string;
compact: boolean;
plain: boolean;
+ deep: boolean;
sync: boolean;
dryRun: boolean;
- skeleton: boolean;
} = cmd.optsWithGlobals();
if (exec_path?.startsWith("--")) {
@@ -411,6 +434,9 @@ export const search: Command = new CommanderCommand("search")
}
const root = process.cwd();
+ const limitRaw = Number.parseInt(options.maxCount, 10);
+ const limit =
+ Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 5;
const minScore = Number.isFinite(Number.parseFloat(options.minScore))
? Number.parseFloat(options.minScore)
: 0;
@@ -422,94 +448,182 @@ export const search: Command = new CommanderCommand("search")
findProjectRoot(execPathForServer) ?? execPathForServer;
const server = getServerForProject(projectRootForServer);
- if (server) {
- try {
- const response = await fetch(`http://localhost:${server.port}/search`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- query: pattern,
- limit: parseInt(options.m, 10),
- path: exec_path
- ? path.relative(projectRootForServer, path.resolve(exec_path))
- : undefined,
- }),
- });
-
- if (response.ok) {
- const body = (await response.json()) as { results: any[] };
-
- const searchResult = { data: body.results };
- const filteredData = searchResult.data.filter(
- (r) => typeof r.score !== "number" || r.score >= minScore,
- );
+ if (process.env.DEBUG_SERVER) {
+ console.error(`[search] projectRootForServer: ${projectRootForServer}`);
+ console.error(`[search] server found: ${JSON.stringify(server)}`);
+ }
- if (options.skeleton) {
- await outputSkeletons(
- filteredData,
- projectRootForServer,
- parseInt(options.m, 10),
- // Server doesn't easily expose DB instance here in HTTP client mode,
- // but we are in client. Wait, this text implies "Server Search" block.
- // Client talks to server. The server returns JSON.
- // We don't have DB access here.
- // So we pass null, and it will fallback to generating local skeleton (if file exists locally).
- // This is acceptable for Phase 3.
- null,
+ if (server && Number.isFinite(server.port) && server.port > 0) {
+ if (process.env.DEBUG_SERVER) {
+ console.error(`[search] attempting fetch to server on port ${server.port}`);
+ }
+ try {
+ // Fast preflight so a hung server doesn't add multi-second latency.
+ const healthTimeoutMsRaw = Number.parseInt(
+ process.env.OSGREP_SERVER_HEALTH_TIMEOUT_MS || "",
+ 10,
+ );
+ const healthTimeoutMs =
+ Number.isFinite(healthTimeoutMsRaw) && healthTimeoutMsRaw > 0
+ ? healthTimeoutMsRaw
+ : process.stdout.isTTY
+ ? 250
+ : 150;
+ {
+ const ac = new AbortController();
+ let timeout: NodeJS.Timeout | undefined;
+ try {
+ timeout = setTimeout(() => ac.abort(), healthTimeoutMs);
+ const health = await fetch(
+ `http://localhost:${server.port}/health`,
+ { signal: ac.signal },
);
- return;
+ if (!health.ok) throw new Error(`health_${health.status}`);
+ } finally {
+ if (timeout) clearTimeout(timeout);
}
+ }
- const compactHits = options.compact
- ? toCompactHits(filteredData)
- : [];
-
- if (options.compact) {
- const compactText = compactHits.length
- ? formatCompactTable(compactHits, projectRootForServer, pattern, {
- isTTY: !!process.stdout.isTTY,
- plain: !!options.plain,
- })
- : "No matches found.";
- console.log(compactText);
- return; // EXIT
- }
+ const timeoutMsRaw = Number.parseInt(
+ process.env.OSGREP_SERVER_TIMEOUT_MS || "",
+ 10,
+ );
+ const timeoutMs =
+ Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
+ ? timeoutMsRaw
+ : process.stdout.isTTY
+ ? 1200
+ : 700;
+ const ac = new AbortController();
+ let timeout: NodeJS.Timeout | undefined;
+ try {
+ timeout = setTimeout(() => ac.abort(), timeoutMs);
+
+ const response = await fetch(
+ `http://localhost:${server.port}/search`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ query: pattern,
+ limit,
+ path: exec_path
+ ? path.relative(projectRootForServer, path.resolve(exec_path))
+ : undefined,
+ deep: options.deep,
+ }),
+ signal: ac.signal,
+ },
+ );
- if (!filteredData.length) {
- console.log("No matches found.");
- return; // EXIT
+ if (process.env.DEBUG_SERVER) {
+ console.error(`[search] server response status: ${response.status}`);
}
+ if (response.ok) {
+ if (process.env.DEBUG_SERVER) {
+ console.error("[search] server returned OK, using server results");
+ }
+ const body = (await response.json()) as {
+ results: any[];
+ partial?: boolean;
+ initialSync?: {
+ filesProcessed: number;
+ filesIndexed: number;
+ totalFiles: number;
+ };
+ expanded?: ExpandedResult;
+ };
+
+ // Show warning if results are partial (server still indexing)
+ if (body.partial && body.initialSync) {
+ const { filesProcessed, filesIndexed } = body.initialSync;
+ console.warn(
+ `ā ļø Index building (${filesProcessed} files processed, ${filesIndexed} indexed). Results may be incomplete.`,
+ );
+ }
+
+ const searchResult = { data: body.results };
+ const filteredData = searchResult.data.filter(
+ (r) => typeof r.score !== "number" || r.score >= minScore,
+ );
- const isTTY = process.stdout.isTTY;
- const shouldBePlain = options.plain || !isTTY;
-
- if (shouldBePlain) {
- const mappedResults = toTextResults(filteredData);
- const output = formatTextResults(
- mappedResults,
- pattern,
- projectRootForServer,
- {
- isPlain: true,
- compact: options.compact,
+ const compactHits = options.compact
+ ? toCompactHits(filteredData)
+ : [];
+
+ if (options.compact) {
+ const compactText = compactHits.length
+ ? formatCompactTable(
+ compactHits,
+ projectRootForServer,
+ pattern,
+ {
+ isTTY: !!process.stdout.isTTY,
+ plain: !!options.plain,
+ },
+ )
+ : "No matches found.";
+ console.log(compactText);
+ // Add expanded results in compact format
+ if (body.expanded && body.expanded.expanded.length > 0) {
+ console.log("");
+ console.log(formatExpandedCompact(body.expanded, projectRootForServer));
+ }
+ return; // EXIT
+ }
+
+ if (!filteredData.length) {
+ console.log("No matches found.");
+ return; // EXIT
+ }
+
+ const isTTY = process.stdout.isTTY;
+ const shouldBePlain = options.plain || !isTTY;
+
+ if (shouldBePlain) {
+ const mappedResults = toTextResults(filteredData);
+ const output = formatTextResults(
+ mappedResults,
+ pattern,
+ projectRootForServer,
+ {
+ isPlain: true,
+ compact: options.compact,
+ content: options.content,
+ perFile: parseInt(options.perFile, 10),
+ showScores: options.scores,
+ },
+ );
+ console.log(output);
+ // Add expanded results
+ if (body.expanded && body.expanded.expanded.length > 0) {
+ console.log(formatExpandedPlain(body.expanded, projectRootForServer));
+ }
+ } else {
+ const { formatResults } = await import("../lib/output/formatter");
+ const output = formatResults(filteredData, projectRootForServer, {
content: options.content,
- perFile: parseInt(options.perFile, 10),
- showScores: options.scores,
- },
- );
- console.log(output);
- } else {
- const { formatResults } = await import("../lib/output/formatter");
- const output = formatResults(filteredData, projectRootForServer, {
- content: options.content,
- });
- console.log(output);
+ });
+ console.log(output);
+ // Add expanded results in plain format
+ if (body.expanded && body.expanded.expanded.length > 0) {
+ console.log(formatExpandedPlain(body.expanded, projectRootForServer));
+ }
+ }
+
+ return; // EXIT successful server search
}
-
- return; // EXIT successful server search
+ } finally {
+ if (timeout) clearTimeout(timeout);
}
} catch (e) {
- if (process.env.DEBUG) {
+ // If the server isn't reachable, remove the stale registry entry so we
+ // don't pay this timeout cost on every query.
+ const msg = e instanceof Error ? e.message : String(e);
+ if (msg.includes("ECONNREFUSED") || msg.includes("health_")) {
+ unregisterServer(server.pid);
+ }
+ if (process.env.DEBUG || process.env.DEBUG_SERVER) {
console.error(
"[search] server request failed, falling back to local:",
e,
@@ -518,8 +632,20 @@ export const search: Command = new CommanderCommand("search")
}
}
+ if (process.env.DEBUG_SERVER) {
+ console.error("[search] falling through to local search");
+ }
+
try {
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+ const t = (label: string) => DEBUG_TIMING && console.time(`[cmd] ${label}`);
+ const te = (label: string) => DEBUG_TIMING && console.timeEnd(`[cmd] ${label}`);
+
+ t("total-cmd");
+ t("ensureSetup");
await ensureSetup();
+ te("ensureSetup");
+
const searchRoot = exec_path ? path.resolve(exec_path) : root;
const projectRoot = findProjectRoot(searchRoot) ?? searchRoot;
const paths = ensureProjectPaths(projectRoot);
@@ -527,7 +653,9 @@ export const search: Command = new CommanderCommand("search")
// Propagate project root to worker processes
process.env.OSGREP_PROJECT_ROOT = projectRoot;
+ t("VectorDB-init");
vectorDb = new VectorDB(paths.lancedbDir);
+ te("VectorDB-init");
// Check for active indexing lock and warn if present
// This allows agents (via shim) to know results might be partial.
@@ -537,7 +665,9 @@ export const search: Command = new CommanderCommand("search")
);
}
+ t("hasAnyRows");
const hasRows = await vectorDb.hasAnyRows();
+ te("hasAnyRows");
const needsSync = options.sync || !hasRows;
if (needsSync) {
@@ -599,29 +729,32 @@ export const search: Command = new CommanderCommand("search")
}
const searcher = new Searcher(vectorDb);
+ const expandOpts = getDeepExpandOptions(options.deep);
+ t("searcher.search");
const searchResult = await searcher.search(
pattern,
- parseInt(options.m, 10),
+ limit,
{ rerank: true },
undefined,
exec_path ? path.relative(projectRoot, path.resolve(exec_path)) : "",
);
+ te("searcher.search");
+
+ // Expand results if requested (Phase 1-4 expansion)
+ let expanded: ExpandedResult | undefined;
+ if (expandOpts && searchResult.data.length > 0) {
+ t("searcher.expand");
+ expanded = await searcher.expand(searchResult.data, pattern, expandOpts);
+ te("searcher.expand");
+ }
+
+ te("total-cmd");
const filteredData = searchResult.data.filter(
(r) => typeof r.score !== "number" || r.score >= minScore,
);
- if (options.skeleton) {
- await outputSkeletons(
- filteredData,
- projectRoot,
- parseInt(options.m, 10),
- vectorDb,
- );
- return;
- }
-
const compactHits = options.compact ? toCompactHits(filteredData) : [];
const compactText =
options.compact && compactHits.length
@@ -640,6 +773,11 @@ export const search: Command = new CommanderCommand("search")
if (options.compact) {
console.log(compactText);
+ // Add expanded results in compact format
+ if (expanded && expanded.expanded.length > 0) {
+ console.log("");
+ console.log(formatExpandedCompact(expanded, projectRoot));
+ }
return;
}
@@ -656,6 +794,10 @@ export const search: Command = new CommanderCommand("search")
showScores: options.scores,
});
console.log(output);
+ // Add expanded results
+ if (expanded && expanded.expanded.length > 0) {
+ console.log(formatExpandedPlain(expanded, projectRoot));
+ }
} else {
// Use new holographic formatter for TTY
const { formatResults } = await import("../lib/output/formatter");
@@ -663,6 +805,10 @@ export const search: Command = new CommanderCommand("search")
content: options.content,
});
console.log(output);
+ // Add expanded results in plain format (for now)
+ if (expanded && expanded.expanded.length > 0) {
+ console.log(formatExpandedPlain(expanded, projectRoot));
+ }
}
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
diff --git a/src/commands/serve.ts b/src/commands/serve.ts
index f24eb166..d2514153 100644
--- a/src/commands/serve.ts
+++ b/src/commands/serve.ts
@@ -2,15 +2,25 @@ import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as http from "node:http";
import * as path from "node:path";
+import chokidar from "chokidar";
+import type { FSWatcher } from "chokidar";
import { Command } from "commander";
import { PATHS } from "../config";
import { ensureGrammars } from "../lib/index/grammar-loader";
-import { createIndexingSpinner } from "../lib/index/sync-helpers";
+import { DEFAULT_IGNORE_PATTERNS } from "../lib/index/ignore-patterns";
+import type { InitialSyncProgress } from "../lib/index/sync-helpers";
import { initialSync } from "../lib/index/syncer";
+import { GraphBuilder } from "../lib/graph/graph-builder";
import { Searcher } from "../lib/search/searcher";
+import type { ExpandOptions } from "../lib/search/expansion-types";
+import type { MetaEntry } from "../lib/store/meta-cache";
+import { MetaCache } from "../lib/store/meta-cache";
+import type { VectorRecord } from "../lib/store/types";
import { ensureSetup } from "../lib/setup/setup-helpers";
import { VectorDB } from "../lib/store/vector-db";
import { gracefulExit } from "../lib/utils/exit";
+import { isIndexableFile } from "../lib/utils/file-utils";
+import { acquireWriterLockWithRetry } from "../lib/utils/lock";
import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
import {
getServerForProject,
@@ -19,6 +29,7 @@ import {
registerServer,
unregisterServer,
} from "../lib/utils/server-registry";
+import { processFile } from "../lib/workers/orchestrator";
export const serve = new Command("serve")
.description("Run osgrep as a background server with live indexing")
@@ -37,7 +48,11 @@ export const serve = new Command("serve")
// Check if already running
const existing = getServerForProject(projectRoot);
- if (existing && isProcessRunning(existing.pid)) {
+ if (
+ existing &&
+ existing.pid !== process.pid &&
+ isProcessRunning(existing.pid)
+ ) {
console.log(
`Server already running for ${projectRoot} (PID: ${existing.pid}, Port: ${existing.port})`,
);
@@ -61,6 +76,18 @@ export const serve = new Command("serve")
env: { ...process.env, OSGREP_BACKGROUND: "true" },
});
child.unref();
+
+ // Ensure the spawned server can be discovered/stopped immediately (even
+ // before it finishes indexing and starts listening).
+ if (typeof child.pid === "number") {
+ registerServer({
+ pid: child.pid,
+ port: 0,
+ projectRoot,
+ startTime: Date.now(),
+ });
+ }
+
console.log(`Started background server (PID: ${child.pid})`);
return;
}
@@ -70,45 +97,395 @@ export const serve = new Command("serve")
// Propagate project root to worker processes
process.env.OSGREP_PROJECT_ROOT = projectRoot;
+ // Register early to prevent race conditions where multiple sessions
+ // spawn servers before any finishes indexing. Port 0 = "starting up".
+ registerServer({
+ pid: process.pid,
+ port: 0,
+ projectRoot,
+ startTime: Date.now(),
+ });
+
try {
await ensureSetup();
await ensureGrammars(console.log, { silent: true });
const vectorDb = new VectorDB(paths.lancedbDir);
const searcher = new Searcher(vectorDb);
+ const metaCache = new MetaCache(paths.lmdbPath);
+
+ // Serialize DB writes (and optionally searches) to avoid LanceDB contention.
+ let dbWriteBarrier: Promise = Promise.resolve();
+ let isWriting = false;
+
+ // Track initial sync state for HTTP endpoints
+ let initialSyncState: {
+ inProgress: boolean;
+ filesProcessed: number;
+ filesIndexed: number;
+ totalFiles: number;
+ currentFile: string;
+ } = {
+ inProgress: true,
+ filesProcessed: 0,
+ filesIndexed: 0,
+ totalFiles: 0,
+ currentFile: "Starting...",
+ };
+
+ // Live indexing: watch filesystem changes and incrementally update the index.
+ // Enabled by default for `serve` (can disable with OSGREP_WATCH=0).
+ const watchEnabled = process.env.OSGREP_WATCH !== "0";
+ const watchVerbose = process.env.OSGREP_WATCH_VERBOSE === "1";
+ const watchMode = (process.env.OSGREP_WATCH_MODE || "auto").toLowerCase();
+ const watchDebounceMsRaw = Number.parseInt(
+ process.env.OSGREP_WATCH_DEBOUNCE_MS || "",
+ 10,
+ );
+ const watchDebounceMs =
+ Number.isFinite(watchDebounceMsRaw) && watchDebounceMsRaw >= 0
+ ? watchDebounceMsRaw
+ : 250;
+
+ const pendingUpserts = new Set();
+ const pendingUnlinks = new Set();
+ let watchTimer: NodeJS.Timeout | undefined;
+ let watcher: FSWatcher | null = null;
+ let nativeWatcher: fs.FSWatcher | null = null;
+ let didLogWatchFallback = false;
+
+ const shouldIgnoreRelPath = (relPathRaw: string): boolean => {
+ const rel = relPathRaw.split(path.sep).join("/");
+ if (!rel || rel === "." || rel.startsWith("../")) return true;
+ if (rel === ".git" || rel.startsWith(".git/")) return true;
+ if (rel === ".osgrep" || rel.startsWith(".osgrep/")) return true;
+ // Large/irrelevant directories (mirrors DEFAULT_IGNORE_PATTERNS intent).
+ if (rel === "node_modules" || rel.startsWith("node_modules/")) return true;
+ if (rel.includes("/node_modules/")) return true;
+ if (rel === "dist" || rel.startsWith("dist/")) return true;
+ if (rel.includes("/dist/")) return true;
+ if (rel === "build" || rel.startsWith("build/")) return true;
+ if (rel.includes("/build/")) return true;
+ if (rel === "out" || rel.startsWith("out/")) return true;
+ if (rel.includes("/out/")) return true;
+ if (rel === "target" || rel.startsWith("target/")) return true;
+ if (rel.includes("/target/")) return true;
+ if (rel === "coverage" || rel.startsWith("coverage/")) return true;
+ if (rel.includes("/coverage/")) return true;
+ if (rel === "benchmark" || rel.startsWith("benchmark/")) return true;
+ if (rel.includes("/benchmark/")) return true;
+ if (rel === ".idea" || rel.startsWith(".idea/")) return true;
+ if (rel === ".vscode" || rel.startsWith(".vscode/")) return true;
+ if (rel.endsWith(".DS_Store")) return true;
+ return false;
+ };
- // Only show spinner if not in background (or check isTTY)
- // If spawned in background with stdio ignore, console.log goes nowhere.
- // But we might want to log to a file in the future.
+ const scheduleFlush = () => {
+ if (watchTimer) clearTimeout(watchTimer);
+ watchTimer = setTimeout(() => {
+ void flushPending().catch((err) => {
+ console.error("[serve] live index flush failed:", err);
+ });
+ }, watchDebounceMs);
+ };
- if (!process.env.OSGREP_BACKGROUND) {
- const { spinner, onProgress } = createIndexingSpinner(
- projectRoot,
- "Indexing before starting server...",
- );
- try {
- await initialSync({
+ const recordUpsert = (absPath: string) => {
+ const rel = path.relative(projectRoot, absPath);
+ if (shouldIgnoreRelPath(rel)) return;
+ pendingUnlinks.delete(rel);
+ pendingUpserts.add(rel);
+ scheduleFlush();
+ };
+
+ const recordUnlink = (absPath: string) => {
+ const rel = path.relative(projectRoot, absPath);
+ if (shouldIgnoreRelPath(rel)) return;
+ pendingUpserts.delete(rel);
+ pendingUnlinks.add(rel);
+ scheduleFlush();
+ };
+
+ const flushPending = async () => {
+ if (!watchEnabled) return;
+ if (pendingUpserts.size === 0 && pendingUnlinks.size === 0) return;
+
+ const upserts = Array.from(pendingUpserts);
+ const unlinks = Array.from(pendingUnlinks);
+ pendingUpserts.clear();
+ pendingUnlinks.clear();
+
+ // Phase 1: prepare work outside the writer lock (hashing/embedding can be slow).
+ type PreparedUpsert = {
+ relPath: string;
+ absPath: string;
+ meta: MetaEntry;
+ shouldDelete: boolean;
+ vectorsCount: number;
+ vectors?: VectorRecord[];
+ };
+
+ const prepared: PreparedUpsert[] = [];
+
+ for (const relPath of upserts) {
+ const absPath = path.join(projectRoot, relPath);
+ try {
+ const stats = await fs.promises.stat(absPath);
+ if (!isIndexableFile(absPath, stats.size)) {
+ // If it was previously indexed, treat as deletion; still store meta to avoid rework.
+ prepared.push({
+ relPath,
+ absPath,
+ meta: { hash: "", mtimeMs: stats.mtimeMs, size: stats.size },
+ shouldDelete: true,
+ vectorsCount: 0,
+ });
+ continue;
+ }
+
+ const cached = metaCache.get(relPath);
+ if (
+ cached &&
+ cached.mtimeMs === stats.mtimeMs &&
+ cached.size === stats.size
+ ) {
+ continue;
+ }
+
+ const result = await processFile({ path: relPath, absolutePath: absPath });
+
+ prepared.push({
+ relPath,
+ absPath,
+ meta: { hash: result.hash, mtimeMs: result.mtimeMs, size: result.size },
+ shouldDelete: result.shouldDelete === true,
+ vectorsCount: result.vectors.length,
+ vectors: result.vectors,
+ });
+ } catch (err) {
+ const code = (err as NodeJS.ErrnoException)?.code;
+ if (code === "ENOENT") {
+ unlinks.push(relPath);
+ continue;
+ }
+ console.error(`[serve] live index: failed to prepare ${relPath}:`, err);
+ }
+ }
+
+ const plannedUnlinks = unlinks.length;
+ const plannedUpserts = prepared.length;
+
+ // Phase 2: apply updates under writer lock; serialize DB operations.
+ const apply = async () => {
+ let lock: { release: () => Promise } | null = null;
+ try {
+ isWriting = true;
+ lock = await acquireWriterLockWithRetry(paths.osgrepDir, {
+ maxRetries: 2,
+ retryDelayMs: 250,
+ });
+
+ if (unlinks.length > 0) {
+ await vectorDb.deletePaths(unlinks);
+ for (const relPath of unlinks) {
+ metaCache.delete(relPath);
+ }
+ }
+
+ for (const item of prepared) {
+ // If the file changed again since we prepared, skip and let the next event handle it.
+ try {
+ const stats = await fs.promises.stat(item.absPath);
+ if (
+ stats.mtimeMs !== item.meta.mtimeMs ||
+ stats.size !== item.meta.size
+ ) {
+ pendingUpserts.add(item.relPath);
+ continue;
+ }
+ } catch (err) {
+ const code = (err as NodeJS.ErrnoException)?.code;
+ if (code === "ENOENT") {
+ pendingUnlinks.add(item.relPath);
+ }
+ continue;
+ }
+
+ await vectorDb.deletePaths([item.relPath]);
+ if (!item.shouldDelete && item.vectorsCount > 0) {
+ await vectorDb.insertBatch(item.vectors as VectorRecord[]);
+ }
+ metaCache.put(item.relPath, item.meta);
+ }
+
+ if (watchVerbose && (plannedUnlinks > 0 || plannedUpserts > 0)) {
+ console.log(
+ `[serve] live index applied: upserts=${plannedUpserts} unlinks=${plannedUnlinks}`,
+ );
+ }
+ } finally {
+ if (lock) await lock.release();
+ isWriting = false;
+ }
+ };
+
+ dbWriteBarrier = dbWriteBarrier.then(apply, apply);
+ await dbWriteBarrier;
+
+ // If we re-queued work due to races, schedule another pass.
+ if (pendingUpserts.size > 0 || pendingUnlinks.size > 0) {
+ scheduleFlush();
+ }
+ };
+
+ if (watchEnabled) {
+ const startChokidar = (mode: "chokidar" | "poll") => {
+ const ignored: (string | RegExp)[] = [
+ ...DEFAULT_IGNORE_PATTERNS,
+ "**/.git/**",
+ "**/.osgrep/**",
+ ];
+
+ watcher = chokidar.watch(projectRoot, {
+ ignored,
+ ignoreInitial: true,
+ persistent: true,
+ usePolling: mode === "poll",
+ interval: mode === "poll" ? 1000 : undefined,
+ awaitWriteFinish: {
+ stabilityThreshold: 200,
+ pollInterval: 100,
+ },
+ });
+
+ watcher.on("add", recordUpsert);
+ watcher.on("change", recordUpsert);
+ watcher.on("unlink", recordUnlink);
+ watcher.on("error", async (err) => {
+ const code = (err as NodeJS.ErrnoException)?.code;
+ if (code === "EMFILE" && mode !== "poll") {
+ if (!didLogWatchFallback) {
+ didLogWatchFallback = true;
+ console.error(
+ "[serve] watcher hit EMFILE; falling back to polling mode (set OSGREP_WATCH_MODE=poll to force).",
+ );
+ }
+ try {
+ await watcher?.close();
+ } catch {}
+ watcher = null;
+ startChokidar("poll");
+ return;
+ }
+ console.error("[serve] watcher error:", err);
+ });
+ };
+
+ const startNative = () => {
+ nativeWatcher = fs.watch(
projectRoot,
- onProgress,
+ { recursive: true },
+ (eventType, filename) => {
+ const name =
+ typeof filename === "string"
+ ? filename
+ : typeof (filename as any)?.toString === "function"
+ ? String((filename as any).toString("utf-8"))
+ : "";
+ if (!name) return;
+ const rel = name.split(path.sep).join("/");
+ if (shouldIgnoreRelPath(rel)) return;
+ const absPath = path.join(projectRoot, name);
+ // "rename" can be add/unlink/move; stat in flush determines outcome.
+ if (eventType === "rename" || eventType === "change") {
+ recordUpsert(absPath);
+ }
+ },
+ );
+ nativeWatcher.on("error", (err) => {
+ const code = (err as NodeJS.ErrnoException)?.code;
+ if (code === "EMFILE") {
+ if (!didLogWatchFallback) {
+ didLogWatchFallback = true;
+ console.error(
+ "[serve] native watcher hit EMFILE; falling back to polling mode (set OSGREP_WATCH_MODE=poll to force).",
+ );
+ }
+ try {
+ nativeWatcher?.close();
+ } catch {}
+ nativeWatcher = null;
+ startChokidar("poll");
+ return;
+ }
+ console.error("[serve] native watcher error:", err);
});
+ };
+
+ if (watchMode === "off") {
+ // noop
+ } else if (watchMode === "native") {
+ startNative();
+ } else if (watchMode === "poll") {
+ startChokidar("poll");
+ } else if (watchMode === "chokidar") {
+ startChokidar("chokidar");
+ } else {
+ // auto
+ if (process.platform === "darwin" || process.platform === "win32") {
+ startNative();
+ } else {
+ startChokidar("chokidar");
+ }
+ }
+ }
+
+ // Helper to run initial sync after server is listening
+ const runInitialSync = async () => {
+ const onProgress = (info: InitialSyncProgress) => {
+ initialSyncState.filesProcessed = info.processed;
+ initialSyncState.filesIndexed = info.indexed;
+ initialSyncState.totalFiles = info.total;
+ initialSyncState.currentFile = info.filePath ?? "";
+ };
+
+ try {
+ await initialSync({ projectRoot, onProgress });
await vectorDb.createFTSIndex();
- spinner.succeed("Initial index ready. Starting server...");
+ initialSyncState.inProgress = false;
+ initialSyncState.currentFile = "";
+
+ if (!process.env.OSGREP_BACKGROUND) {
+ console.log("Initial index ready.");
+ }
} catch (e) {
- spinner.fail("Indexing failed");
- throw e;
+ console.error("Initial sync failed:", e);
+ // Mark as done but leave currentFile as error indicator
+ initialSyncState.inProgress = false;
+ initialSyncState.currentFile = "sync_failed";
}
- } else {
- // In background, just sync quietly
- await initialSync({ projectRoot });
- await vectorDb.createFTSIndex();
- }
+ };
const server = http.createServer(async (req, res) => {
try {
if (req.method === "GET" && req.url === "/health") {
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
- res.end(JSON.stringify({ status: "ok" }));
+ res.end(
+ JSON.stringify({
+ status: initialSyncState.inProgress ? "initializing" : "ok",
+ initialSync: initialSyncState.inProgress
+ ? {
+ inProgress: true,
+ filesProcessed: initialSyncState.filesProcessed,
+ filesIndexed: initialSyncState.filesIndexed,
+ totalFiles: initialSyncState.totalFiles,
+ currentFile: initialSyncState.currentFile,
+ }
+ : null,
+ indexing: isWriting,
+ watch: watchEnabled,
+ }),
+ );
return;
}
@@ -165,32 +542,130 @@ export const serve = new Command("serve")
// Add AbortController for cancellation
const ac = new AbortController();
- req.on("close", () => {
+ req.on("aborted", () => {
ac.abort();
});
+ res.on("close", () => {
+ if (!res.writableEnded) ac.abort();
+ });
- const result = await searcher.search(
- query,
- limit,
- { rerank: true },
- undefined,
- searchPath,
- undefined, // intent
- ac.signal,
+ const timeoutMsRaw = Number.parseInt(
+ process.env.OSGREP_SERVER_SEARCH_TIMEOUT_MS || "",
+ 10,
);
+ const timeoutMs =
+ Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
+ ? timeoutMsRaw
+ : 60000;
+ let timeout: NodeJS.Timeout | undefined;
+
+ try {
+ timeout = setTimeout(() => {
+ ac.abort();
+ if (!res.headersSent && !res.writableEnded) {
+ res.statusCode = 504;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ error: "search_timeout" }));
+ }
+ }, timeoutMs);
+
+ // Parse deep option from request body
+ let expandOpts: ExpandOptions | undefined;
+ if (body.deep === true) {
+ expandOpts = {
+ maxDepth: 2,
+ maxExpanded: 20,
+ maxTokens: 0,
+ strategies: ["callers", "symbols"],
+ };
+ }
- if (ac.signal.aborted) {
- // Request was cancelled, don't write response if possible
- // (Though usually 'close' means the socket is gone anyway)
- return;
- }
+ const debug = process.env.DEBUG_SERVER === "1";
+ if (debug) {
+ console.log(
+ `[serve] Starting search for "${query}", indexing=${isWriting} signal.aborted=${ac.signal.aborted}${
+ expandOpts ? " deep=true" : ""
+ }`,
+ );
+ }
- res.statusCode = 200;
- res.setHeader("Content-Type", "application/json");
- res.end(JSON.stringify({ results: result.data }));
+ const result = await searcher.search(
+ query,
+ limit,
+ { rerank: true },
+ undefined,
+ searchPath,
+ ac.signal,
+ );
+ if (debug) {
+ console.log(
+ `[serve] Search completed, ${result.data.length} results`,
+ );
+ }
+
+ if (ac.signal.aborted) {
+ console.log("[serve] Signal aborted after search");
+ return;
+ }
+
+ // Expand results if requested
+ let expanded;
+ if (expandOpts && result.data.length > 0) {
+ if (debug) {
+ console.log(`[serve] Expanding results with depth=${expandOpts.maxDepth}`);
+ }
+ expanded = await searcher.expand(result.data, query, expandOpts);
+ if (debug) {
+ console.log(
+ `[serve] Expansion completed, ${expanded.expanded.length} expanded chunks`,
+ );
+ }
+ }
+
+ res.statusCode = 200;
+ res.setHeader("Content-Type", "application/json");
+ const response: {
+ results: typeof result.data;
+ partial?: boolean;
+ initialSync?: {
+ filesProcessed: number;
+ filesIndexed: number;
+ totalFiles: number;
+ };
+ expanded?: typeof expanded;
+ } = { results: result.data };
+
+ if (initialSyncState.inProgress) {
+ response.partial = true;
+ response.initialSync = {
+ filesProcessed: initialSyncState.filesProcessed,
+ filesIndexed: initialSyncState.filesIndexed,
+ totalFiles: initialSyncState.totalFiles,
+ };
+ }
+
+ if (expanded) {
+ response.expanded = expanded;
+ }
+
+ res.end(JSON.stringify(response));
+ } finally {
+ if (timeout) clearTimeout(timeout);
+ }
} catch (err) {
+ console.log(`[serve] Search error: ${err instanceof Error ? err.name + ': ' + err.message : err}`);
if (err instanceof Error && err.name === "AbortError") {
- // Request cancelled
+ if (!res.headersSent && !res.writableEnded) {
+ res.statusCode = 504;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ error: "search_cancelled" }));
+ }
+ return;
+ }
+ if (isWriting && !res.headersSent && !res.writableEnded) {
+ res.statusCode = 503;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ error: "indexing_in_progress" }));
return;
}
res.statusCode = 500;
@@ -211,6 +686,85 @@ export const serve = new Command("serve")
return;
}
+ if (req.method === "POST" && req.url === "/trace") {
+ const chunks: Buffer[] = [];
+ let totalSize = 0;
+ let aborted = false;
+
+ req.on("data", (chunk) => {
+ if (aborted) return;
+ totalSize += chunk.length;
+ if (totalSize > 100_000) {
+ aborted = true;
+ res.statusCode = 413;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ error: "payload_too_large" }));
+ req.destroy();
+ return;
+ }
+ chunks.push(chunk);
+ });
+
+ req.on("end", async () => {
+ if (aborted) return;
+ try {
+ const body = chunks.length
+ ? JSON.parse(Buffer.concat(chunks).toString("utf-8"))
+ : {};
+ const symbol = typeof body.symbol === "string" ? body.symbol : "";
+
+ if (!symbol) {
+ res.statusCode = 400;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ error: "symbol_required" }));
+ return;
+ }
+
+ const graphBuilder = new GraphBuilder(vectorDb);
+ const graph = await graphBuilder.buildGraph(symbol, {
+ depth: typeof body.depth === "number" ? body.depth : 1,
+ callersOnly: body.callers === true,
+ calleesOnly: body.callees === true,
+ pathPrefix: typeof body.path === "string" ? body.path : undefined,
+ });
+
+ res.statusCode = 200;
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({
+ symbol,
+ center: graph.center
+ ? {
+ file: graph.center.file,
+ line: graph.center.line,
+ role: graph.center.role,
+ }
+ : null,
+ callers: graph.callers.map((c) => ({
+ symbol: c.symbol,
+ file: c.file,
+ line: c.line,
+ })),
+ callees: graph.callees,
+ }));
+ } catch (err) {
+ res.statusCode = 500;
+ res.setHeader("Content-Type", "application/json");
+ res.end(
+ JSON.stringify({
+ error: (err as Error)?.message || "trace_failed",
+ }),
+ );
+ }
+ });
+
+ req.on("error", (err) => {
+ console.error("[serve] trace request error:", err);
+ aborted = true;
+ });
+
+ return;
+ }
+
res.statusCode = 404;
res.end();
} catch (err) {
@@ -252,6 +806,7 @@ export const serve = new Command("serve")
console.log(
`osgrep server listening on http://localhost:${actualPort} (${projectRoot})`,
);
+ console.log("Starting initial index...");
}
registerServer({
pid: process.pid,
@@ -259,11 +814,28 @@ export const serve = new Command("serve")
projectRoot,
startTime: Date.now(),
});
+
+ // Start initial sync after server is listening (non-blocking)
+ runInitialSync().catch((err) => {
+ console.error("Initial sync error:", err);
+ });
});
const shutdown = async () => {
unregisterServer(process.pid);
+ if (watchTimer) {
+ clearTimeout(watchTimer);
+ watchTimer = undefined;
+ }
+ try {
+ await watcher?.close();
+ } catch {}
+ try {
+ nativeWatcher?.close();
+ } catch {}
+ nativeWatcher = null
+
// Properly await server close
await new Promise((resolve, reject) => {
server.close((err) => {
@@ -284,12 +856,16 @@ export const serve = new Command("serve")
} catch (e) {
console.error("Error closing vector DB:", e);
}
+ try {
+ metaCache.close();
+ } catch {}
await gracefulExit();
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
} catch (error) {
+ unregisterServer(process.pid);
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Serve failed:", message);
process.exitCode = 1;
@@ -316,28 +892,67 @@ serve
.command("stop")
.description("Stop background servers")
.option("--all", "Stop all servers", false)
- .action((options) => {
+ .action(async (options) => {
+ const waitForExit = async (pid: number, timeoutMs: number) => {
+ const deadline = Date.now() + Math.max(0, timeoutMs);
+ while (Date.now() < deadline) {
+ if (!isProcessRunning(pid)) return true;
+ await new Promise((r) => setTimeout(r, 75));
+ }
+ return !isProcessRunning(pid);
+ };
+
+ const stopPid = async (pid: number): Promise => {
+ try {
+ // If the process is stopped (job control), SIGTERM won't be handled until resumed.
+ process.kill(pid, "SIGCONT");
+ } catch {}
+
+ try {
+ process.kill(pid, "SIGTERM");
+ } catch (e) {
+ unregisterServer(pid);
+ return false;
+ }
+
+ const exited = await waitForExit(pid, 2000);
+ if (exited) {
+ unregisterServer(pid);
+ return true;
+ }
+
+ try {
+ process.kill(pid, "SIGKILL");
+ } catch (e) {
+ unregisterServer(pid);
+ return false;
+ }
+
+ const killed = await waitForExit(pid, 2000);
+ unregisterServer(pid);
+ return killed;
+ };
+
if (options.all) {
const servers = listServers();
let count = 0;
- servers.forEach((s) => {
- try {
- process.kill(s.pid, "SIGTERM");
- count++;
- } catch (e) {
- console.error(`Failed to stop PID ${s.pid}:`, e);
- }
- });
+ for (const s of servers) {
+ const ok = await stopPid(s.pid);
+ if (ok) count++;
+ else console.error(`Failed to stop PID ${s.pid}`);
+ }
console.log(`Stopped ${count} servers.`);
} else {
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
const server = getServerForProject(projectRoot);
if (server) {
- try {
- process.kill(server.pid, "SIGTERM");
- console.log(`Stopped server for ${projectRoot} (PID: ${server.pid})`);
- } catch (e) {
- console.error(`Failed to stop PID ${server.pid}:`, e);
+ const ok = await stopPid(server.pid);
+ if (ok) {
+ console.log(
+ `Stopped server for ${projectRoot} (PID: ${server.pid})`,
+ );
+ } else {
+ console.error(`Failed to stop PID ${server.pid}`);
}
} else {
console.log(`No server found for ${projectRoot}`);
diff --git a/src/commands/setup.ts b/src/commands/setup.ts
index eebb528f..13119e8a 100644
--- a/src/commands/setup.ts
+++ b/src/commands/setup.ts
@@ -1,8 +1,8 @@
import * as fs from "node:fs";
-import * as path from "node:path";
import { Command } from "commander";
-import { MODEL_IDS, PATHS } from "../config";
+import { PATHS } from "../config";
import { ensureGrammars } from "../lib/index/grammar-loader";
+import { initNative } from "../lib/native";
import { ensureSetup } from "../lib/setup/setup-helpers";
import { gracefulExit } from "../lib/utils/exit";
@@ -21,8 +21,6 @@ export const setup = new Command("setup")
// Show final status
console.log("\nSetup Complete!\n");
- const modelIds = [MODEL_IDS.embed, MODEL_IDS.colbert];
-
const checkDir = (name: string, p: string) => {
const exists = fs.existsSync(p);
const symbol = exists ? "ā" : "ā";
@@ -30,54 +28,16 @@ export const setup = new Command("setup")
};
checkDir("Global Root", PATHS.globalRoot);
- checkDir("Models", PATHS.models);
checkDir("Grammars", PATHS.grammars);
// Download Grammars
console.log("\nChecking Tree-sitter Grammars...");
await ensureGrammars();
- const modelStatuses = modelIds.map((id) => {
- const modelPath = path.join(PATHS.models, ...id.split("/"));
- return { id, path: modelPath, exists: fs.existsSync(modelPath) };
- });
-
- modelStatuses.forEach(({ id, exists }) => {
- const symbol = exists ? "ā" : "ā";
- console.log(`${symbol} Model: ${id}`);
- });
-
- // Check for skiplist.json and try to download if missing
- const colbertPath = path.join(
- PATHS.models,
- ...MODEL_IDS.colbert.split("/"),
- );
- const skiplistPath = path.join(colbertPath, "skiplist.json");
- if (fs.existsSync(skiplistPath)) {
- console.log(`ā Skiplist found: ${skiplistPath}`);
- } else {
- console.log(`ā Skiplist missing, attempting to download...`);
- try {
- const url = `https://huggingface.co/${MODEL_IDS.colbert}/resolve/main/skiplist.json`;
- const response = await fetch(url);
- if (response.ok) {
- const buffer = await response.arrayBuffer();
- fs.writeFileSync(skiplistPath, Buffer.from(buffer));
- console.log(`ā Skiplist downloaded successfully`);
- } else {
- console.log(
- `ā Skiplist download failed (HTTP ${response.status}), will use fallback`,
- );
- console.log(` Expected at: ${skiplistPath}`);
- }
- } catch (error) {
- console.log(`ā Skiplist download failed, will use fallback`);
- console.log(
- ` Error: ${error instanceof Error ? error.message : String(error)}`,
- );
- console.log(` Expected at: ${skiplistPath}`);
- }
- }
+ // Pre-warm native models (downloads via HuggingFace Hub cache on first run)
+ console.log("\nInitializing native models...");
+ await initNative();
+ console.log("ā Native models ready");
console.log(`\nosgrep is ready! You can now run:`);
console.log(` osgrep index # Index your repository`);
diff --git a/src/commands/skeleton.ts b/src/commands/skeleton.ts
deleted file mode 100644
index b24e9bbb..00000000
--- a/src/commands/skeleton.ts
+++ /dev/null
@@ -1,321 +0,0 @@
-/**
- * osgrep skeleton - Show code skeleton (signatures without implementation)
- *
- * Usage:
- * osgrep skeleton # Skeleton of a file
- * osgrep skeleton # Find symbol and skeleton its file
- * osgrep skeleton "query" # Search and skeleton top results
- */
-
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { Command } from "commander";
-import { createIndexingSpinner } from "../lib/index/sync-helpers";
-import { initialSync } from "../lib/index/syncer";
-import { Searcher } from "../lib/search/searcher";
-import { ensureSetup } from "../lib/setup/setup-helpers";
-import { getStoredSkeleton } from "../lib/skeleton/retriever";
-import { Skeletonizer } from "../lib/skeleton/skeletonizer";
-import { VectorDB } from "../lib/store/vector-db";
-import { gracefulExit } from "../lib/utils/exit";
-import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
-
-interface SkeletonOptions {
- limit: string;
- json: boolean;
- noSummary: boolean;
- sync: boolean;
-}
-
-/**
- * Check if target looks like a file path.
- */
-function isFilePath(target: string): boolean {
- // Has path separator or file extension
- return (
- target.includes("/") || target.includes("\\") || /\.\w{1,10}$/.test(target)
- );
-}
-
-/**
- * Check if target looks like a symbol name (PascalCase or camelCase identifier).
- */
-function isSymbolLike(target: string): boolean {
- // PascalCase class name or camelCase function name
- // Must be a single word without spaces
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(target) && !target.includes(" ");
-}
-
-/**
- * Find a file by symbol name in the index.
- */
-async function findFileBySymbol(
- symbol: string,
- db: VectorDB,
-): Promise {
- try {
- const table = await db.ensureTable();
-
- // Search for files that define this symbol
- const results = await table.search(symbol).limit(10).toArray();
-
- // Find a result where this symbol is defined
- for (const result of results) {
- const defined = result.defined_symbols as string[] | undefined;
- if (defined?.includes(symbol)) {
- return result.path as string;
- }
- }
-
- // Fallback: just return the first match's file
- if (results.length > 0) {
- return results[0].path as string;
- }
-
- return null;
- } catch {
- return null;
- }
-}
-
-export const skeleton = new Command("skeleton")
- .description("Show code skeleton (signatures without implementation)")
- .argument("", "File path, symbol name, or search query")
- .option("-l, --limit ", "Max files for query mode", "3")
- .option("--json", "Output as JSON", false)
- .option("--no-summary", "Omit call/complexity summary in bodies", false)
- .option("-s, --sync", "Sync index before searching", false)
- .action(async (target: string, options: SkeletonOptions, _cmd) => {
- let vectorDb: VectorDB | null = null;
-
- try {
- // Initialize
- await ensureSetup();
- const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
- const paths = ensureProjectPaths(projectRoot);
- vectorDb = new VectorDB(paths.lancedbDir);
-
- // Sync if requested
- if (options.sync) {
- const { spinner, onProgress } = createIndexingSpinner(
- projectRoot,
- "Syncing...",
- { verbose: false },
- );
- await initialSync({ projectRoot, onProgress });
- spinner.succeed("Sync complete");
- }
-
- // Initialize skeletonizer
- const skeletonizer = new Skeletonizer();
- await skeletonizer.init();
-
- const skeletonOpts = {
- includeSummary: !options.noSummary,
- };
-
- // Determine mode based on target
- if (isFilePath(target)) {
- // === FILE MODE ===
- const filePath = path.resolve(target);
-
- if (!fs.existsSync(filePath)) {
- console.error(`File not found: ${filePath}`);
- process.exitCode = 1;
- return;
- }
-
- if (vectorDb) {
- const relativeToProject = path.relative(projectRoot, filePath);
- const cached = await getStoredSkeleton(vectorDb, relativeToProject);
- if (cached) {
- outputResult(
- {
- success: true,
- skeleton: cached,
- tokenEstimate: Math.ceil(cached.length / 4),
- },
- options,
- );
- return;
- }
- }
-
- const content = fs.readFileSync(filePath, "utf-8");
- const result = await skeletonizer.skeletonizeFile(
- filePath,
- content,
- skeletonOpts,
- );
-
- outputResult(result, options);
- } else if (isSymbolLike(target) && !target.includes(" ")) {
- // === SYMBOL MODE ===
- const filePath = await findFileBySymbol(target, vectorDb);
-
- if (!filePath) {
- console.error(`Symbol not found in index: ${target}`);
- console.error(
- "Try running 'osgrep index' first or use a search query.",
- );
- process.exitCode = 1;
- return;
- }
-
- const absolutePath = path.resolve(projectRoot, filePath);
- if (!fs.existsSync(absolutePath)) {
- console.error(`File not found: ${absolutePath}`);
- process.exitCode = 1;
- return;
- }
-
- const cached = await getStoredSkeleton(vectorDb!, filePath);
- if (cached) {
- outputResult(
- {
- success: true,
- skeleton: cached,
- tokenEstimate: Math.ceil(cached.length / 4),
- },
- options,
- );
- return;
- }
-
- const content = fs.readFileSync(absolutePath, "utf-8");
- const result = await skeletonizer.skeletonizeFile(
- filePath,
- content,
- skeletonOpts,
- );
-
- outputResult(result, options);
- } else {
- // === QUERY MODE ===
- const searcher = new Searcher(vectorDb);
- const limit = Math.min(Number.parseInt(options.limit, 10) || 3, 10);
-
- const searchResults = await searcher.search(target, limit);
-
- if (!searchResults.data || searchResults.data.length === 0) {
- console.error(`No results found for: ${target}`);
- process.exitCode = 1;
- return;
- }
-
- // Get unique file paths from results
- const seenPaths = new Set();
- const filePaths: string[] = [];
-
- for (const result of searchResults.data) {
- const resultPath = (result.metadata as { path?: string })?.path;
- if (resultPath && !seenPaths.has(resultPath)) {
- seenPaths.add(resultPath);
- filePaths.push(resultPath);
- if (filePaths.length >= limit) break;
- }
- }
-
- // Skeletonize each file
- const results: Array<{
- file: string;
- skeleton: string;
- tokens: number;
- error?: string;
- }> = [];
-
- for (const filePath of filePaths) {
- const absolutePath = path.resolve(projectRoot, filePath);
-
- if (!fs.existsSync(absolutePath)) {
- results.push({
- file: filePath,
- skeleton: `// File not found: ${filePath}`,
- tokens: 0,
- error: "File not found",
- });
- continue;
- }
-
- // Try cache first
- const cached = await getStoredSkeleton(vectorDb!, filePath);
- if (cached) {
- results.push({
- file: filePath,
- skeleton: cached,
- tokens: Math.ceil(cached.length / 4),
- });
- continue;
- }
-
- const content = fs.readFileSync(absolutePath, "utf-8");
- const result = await skeletonizer.skeletonizeFile(
- filePath,
- content,
- skeletonOpts,
- );
-
- results.push({
- file: filePath,
- skeleton: result.skeleton,
- tokens: result.tokenEstimate,
- error: result.error,
- });
- }
-
- // Output results
- if (options.json) {
- console.log(JSON.stringify(results, null, 2));
- } else {
- for (const result of results) {
- console.log(result.skeleton);
- console.log(""); // Blank line between files
- }
- }
- }
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- console.error("Error:", message);
- process.exitCode = 1;
- } finally {
- if (vectorDb) {
- try {
- await vectorDb.close();
- } catch {
- // Ignore close errors
- }
- }
- const code = typeof process.exitCode === "number" ? process.exitCode : 0;
- await gracefulExit(code);
- }
- });
-
-/**
- * Output a skeleton result.
- */
-function outputResult(
- result: {
- success: boolean;
- skeleton: string;
- tokenEstimate: number;
- error?: string;
- },
- options: SkeletonOptions,
-): void {
- if (options.json) {
- console.log(
- JSON.stringify(
- {
- success: result.success,
- skeleton: result.skeleton,
- tokens: result.tokenEstimate,
- error: result.error,
- },
- null,
- 2,
- ),
- );
- } else {
- console.log(result.skeleton);
- }
-}
diff --git a/src/commands/trace.ts b/src/commands/trace.ts
index 7a52032d..28ac9572 100644
--- a/src/commands/trace.ts
+++ b/src/commands/trace.ts
@@ -1,38 +1,278 @@
import { Command } from "commander";
-import { GraphBuilder } from "../lib/graph/graph-builder";
-import { formatTrace } from "../lib/output/formatter";
+import { GraphBuilder, type CallGraph } from "../lib/graph/graph-builder";
import { VectorDB } from "../lib/store/vector-db";
import { gracefulExit } from "../lib/utils/exit";
import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
+const style = {
+ bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
+ dim: (s: string) => `\x1b[2m${s}\x1b[22m`,
+ cyan: (s: string) => `\x1b[36m${s}\x1b[39m`,
+ green: (s: string) => `\x1b[32m${s}\x1b[39m`,
+ yellow: (s: string) => `\x1b[33m${s}\x1b[39m`,
+};
+
+/**
+ * Dedupe and count callees. Returns "foo bar baz" or "foo bar (x3) baz" for repeats.
+ */
+function formatCallees(callees: string[]): string {
+ const counts = new Map();
+ for (const c of callees) {
+ counts.set(c, (counts.get(c) || 0) + 1);
+ }
+ return [...counts.entries()]
+ .map(([name, count]) => (count > 1 ? `${name} (x${count})` : name))
+ .join(" ");
+}
+
+/**
+ * Group callers by file. Returns map of file -> line numbers.
+ */
+function groupCallersByFile(
+ callers: Array<{ file: string; line: number; symbol: string }>,
+): Map {
+ const byFile = new Map();
+ for (const c of callers) {
+ const lines = byFile.get(c.file) || [];
+ lines.push(c.line);
+ byFile.set(c.file, lines);
+ }
+ // Sort lines within each file
+ for (const lines of byFile.values()) {
+ lines.sort((a, b) => a - b);
+ }
+ return byFile;
+}
+
+/**
+ * Count total items for format decision.
+ */
+function countItems(graph: CallGraph): number {
+ const uniqueCallees = new Set(graph.callees).size;
+ const callerFiles = groupCallersByFile(graph.callers).size;
+ return uniqueCallees + callerFiles;
+}
+
+/**
+ * Format call graph for minimal agent-friendly output.
+ * Uses tree format for small results, compact grouped format for large.
+ */
+function formatPlain(graph: CallGraph, symbol: string): string {
+ const itemCount = countItems(graph);
+
+ // Use tree format for small results (cleaner), compact for large
+ if (itemCount <= 10) {
+ return formatPlainTree(graph, symbol);
+ }
+ return formatPlainCompact(graph, symbol);
+}
+
+/**
+ * Tree format for small results - cleaner to read.
+ */
+function formatPlainTree(graph: CallGraph, symbol: string): string {
+ const lines: string[] = [];
+
+ if (graph.center) {
+ lines.push(`${symbol} (${graph.center.file}:${graph.center.line})`);
+ } else {
+ lines.push(`${symbol} (not found)`);
+ }
+
+ const hasCallees = graph.callees.length > 0;
+ const hasCallers = graph.callers.length > 0;
+
+ if (hasCallees) {
+ const branch = hasCallers ? "āāā" : "āāā";
+ lines.push(`${branch} calls:`);
+
+ // Dedupe callees
+ const counts = new Map();
+ for (const c of graph.callees) {
+ counts.set(c, (counts.get(c) || 0) + 1);
+ }
+ const calleeList = [...counts.entries()];
+
+ calleeList.forEach(([name, count], i) => {
+ const prefix = hasCallers ? "ā " : " ";
+ const sym = i === calleeList.length - 1 ? "āāā" : "āāā";
+ const countStr = count > 1 ? ` (x${count})` : "";
+ lines.push(`${prefix}${sym} ${name}${countStr}`);
+ });
+ }
+
+ if (hasCallers) {
+ lines.push("āāā called by:");
+ const byFile = groupCallersByFile(graph.callers);
+ const files = [...byFile.entries()];
+
+ files.forEach(([file, fileLines], i) => {
+ const sym = i === files.length - 1 ? "āāā" : "āāā";
+ const lineStr =
+ fileLines.length === 1
+ ? `line ${fileLines[0]}`
+ : `lines ${fileLines.join(", ")}`;
+ lines.push(` ${sym} ${file}: ${lineStr}`);
+ });
+ }
+
+ if (!hasCallees && !hasCallers) {
+ lines.push(" (no callers or callees found)");
+ }
+
+ return lines.join("\n");
+}
+
+/**
+ * Compact grouped format for large results.
+ */
+function formatPlainCompact(graph: CallGraph, symbol: string): string {
+ const lines: string[] = [];
+ lines.push(symbol);
+
+ if (graph.center) {
+ lines.push(` def: ${graph.center.file}:${graph.center.line}`);
+ } else {
+ lines.push(" def: (not found)");
+ }
+
+ if (graph.callees.length > 0) {
+ lines.push(` calls: ${formatCallees(graph.callees)}`);
+ }
+
+ if (graph.callers.length > 0) {
+ lines.push(" called_by:");
+ const byFile = groupCallersByFile(graph.callers);
+ for (const [file, fileLines] of byFile) {
+ const lineStr =
+ fileLines.length === 1
+ ? `line ${fileLines[0]}`
+ : `lines ${fileLines.join(", ")}`;
+ lines.push(` ${file}: ${lineStr}`);
+ }
+ }
+
+ return lines.join("\n");
+}
+
+/**
+ * Format call graph as a tree for human-readable output.
+ */
+function formatTree(graph: CallGraph, symbol: string): string {
+ const lines: string[] = [];
+
+ if (graph.center) {
+ const role = graph.center.role ? ` ${style.dim(graph.center.role)}` : "";
+ lines.push(
+ `${style.bold(symbol)} (${style.cyan(`${graph.center.file}:${graph.center.line}`)})${role}`,
+ );
+ } else {
+ lines.push(`${style.bold(symbol)} ${style.dim("(definition not found)")}`);
+ }
+
+ const hasCallees = graph.callees.length > 0;
+ const hasCallers = graph.callers.length > 0;
+
+ if (hasCallees) {
+ const branch = hasCallers ? "\u251c\u2500\u2500" : "\u2514\u2500\u2500";
+ lines.push(`${branch} ${style.yellow("calls:")}`);
+ graph.callees.forEach((callee, i) => {
+ const prefix = hasCallers ? "\u2502 " : " ";
+ const sym =
+ i === graph.callees.length - 1
+ ? "\u2514\u2500\u2500"
+ : "\u251c\u2500\u2500";
+ lines.push(`${prefix}${sym} ${callee}`);
+ });
+ }
+
+ if (hasCallers) {
+ lines.push(`\u2514\u2500\u2500 ${style.green("called by:")}`);
+ graph.callers.forEach((caller, i) => {
+ const sym =
+ i === graph.callers.length - 1
+ ? "\u2514\u2500\u2500"
+ : "\u251c\u2500\u2500";
+ lines.push(
+ ` ${sym} ${caller.symbol} (${style.cyan(`${caller.file}:${caller.line}`)})`,
+ );
+ });
+ }
+
+ if (!hasCallees && !hasCallers) {
+ lines.push(style.dim(" (no callers or callees found)"));
+ }
+
+ return lines.join("\n");
+}
+
+/**
+ * Format call graph as JSON for programmatic use.
+ */
+function formatJson(graph: CallGraph, symbol: string): string {
+ return JSON.stringify(
+ {
+ symbol,
+ center: graph.center
+ ? {
+ file: graph.center.file,
+ line: graph.center.line,
+ role: graph.center.role,
+ }
+ : null,
+ callers: graph.callers.map((c) => ({
+ symbol: c.symbol,
+ file: c.file,
+ line: c.line,
+ })),
+ callees: graph.callees,
+ },
+ null,
+ 2,
+ );
+}
+
export const trace = new Command("trace")
- .description("Trace the call graph for a symbol")
- .argument("", "The symbol to trace")
- .action(async (symbol) => {
- const root = process.cwd();
- let vectorDb: VectorDB | null = null;
+ .description("Show call graph for a symbol (callers and callees)")
+ .argument("", "Symbol name to trace")
+ .option("-d, --depth ", "Traversal depth (default: 1)", "1")
+ .option("--callers", "Show only callers (who calls this)")
+ .option("--callees", "Show only callees (what this calls)")
+ .option("-p, --path ", "Filter to path prefix")
+ .option("--pretty", "Pretty tree output (default for TTY)")
+ .option("--plain", "Plain minimal output (default for non-TTY)")
+ .option("--json", "JSON output")
+ .action(async (symbol, cmd) => {
+ const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
+ const paths = ensureProjectPaths(projectRoot);
+ const db = new VectorDB(paths.lancedbDir);
try {
- const projectRoot = findProjectRoot(root) ?? root;
- const paths = ensureProjectPaths(projectRoot);
-
- vectorDb = new VectorDB(paths.lancedbDir);
-
- const graphBuilder = new GraphBuilder(vectorDb);
- const graph = await graphBuilder.buildGraph(symbol);
- console.log(formatTrace(graph));
- } catch (error) {
- const message = error instanceof Error ? error.message : "Unknown error";
- console.error("Trace failed:", message);
- process.exitCode = 1;
- } finally {
- if (vectorDb) {
- try {
- await vectorDb.close();
- } catch (err) {
- console.error("Failed to close VectorDB:", err);
- }
+ const builder = new GraphBuilder(db);
+
+ const graph = await builder.buildGraph(symbol, {
+ depth: Number.parseInt(cmd.depth, 10) || 1,
+ callersOnly: cmd.callers as boolean,
+ calleesOnly: cmd.callees as boolean,
+ pathPrefix: cmd.path as string | undefined,
+ });
+
+ // Determine output format
+ let output: string;
+ if (cmd.json) {
+ output = formatJson(graph, symbol);
+ } else if (cmd.pretty) {
+ output = formatTree(graph, symbol);
+ } else if (cmd.plain || !process.stdout.isTTY) {
+ output = formatPlain(graph, symbol);
+ } else {
+ output = formatTree(graph, symbol);
}
- await gracefulExit();
+
+ console.log(output);
+ } finally {
+ await db.close();
}
+
+ await gracefulExit();
});
diff --git a/src/commands/doctor.ts b/src/commands/utility/doctor.ts
similarity index 57%
rename from src/commands/doctor.ts
rename to src/commands/utility/doctor.ts
index 58874f7d..0719f14c 100644
--- a/src/commands/doctor.ts
+++ b/src/commands/utility/doctor.ts
@@ -2,9 +2,10 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Command } from "commander";
-import { MODEL_IDS, PATHS } from "../config";
-import { gracefulExit } from "../lib/utils/exit";
-import { findProjectRoot } from "../lib/utils/project-root";
+import { PATHS } from "../../config";
+import { initNative } from "../../lib/native";
+import { gracefulExit } from "../../lib/utils/exit";
+import { findProjectRoot } from "../../lib/utils/project-root";
export const doctor = new Command("doctor")
.description("Check osgrep health and paths")
@@ -12,9 +13,7 @@ export const doctor = new Command("doctor")
console.log("š„ osgrep Doctor\n");
const root = PATHS.globalRoot;
- const models = PATHS.models;
const grammars = PATHS.grammars;
- const modelIds = [MODEL_IDS.embed, MODEL_IDS.colbert];
const checkDir = (name: string, p: string) => {
const exists = fs.existsSync(p);
@@ -23,24 +22,15 @@ export const doctor = new Command("doctor")
};
checkDir("Root", root);
- checkDir("Models", models);
checkDir("Grammars", grammars);
- const modelStatuses = modelIds.map((id) => {
- const modelPath = path.join(models, ...id.split("/"));
- return { id, path: modelPath, exists: fs.existsSync(modelPath) };
- });
-
- modelStatuses.forEach(({ id, path: p, exists }) => {
- const symbol = exists ? "ā
" : "ā";
- console.log(`${symbol} Model: ${id} (${p})`);
- });
-
- const missingModels = modelStatuses.filter(({ exists }) => !exists);
- if (missingModels.length > 0) {
- console.log(
- "ā Some models are missing; osgrep will try bundled copies first, then download.",
- );
+ try {
+ await initNative();
+ console.log("ā
Native models initialized");
+ } catch (e) {
+ const msg = e instanceof Error ? e.message : String(e);
+ console.log(`ā Native init failed: ${msg}`);
+ console.log(" Try: osgrep setup");
}
console.log(`\nLocal Project: ${process.cwd()}`);
diff --git a/src/commands/list.ts b/src/commands/utility/list.ts
similarity index 94%
rename from src/commands/list.ts
rename to src/commands/utility/list.ts
index 67a84189..a39b44d0 100644
--- a/src/commands/list.ts
+++ b/src/commands/utility/list.ts
@@ -1,8 +1,8 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { Command } from "commander";
-import { gracefulExit } from "../lib/utils/exit";
-import { ensureProjectPaths, findProjectRoot } from "../lib/utils/project-root";
+import { gracefulExit } from "../../lib/utils/exit";
+import { ensureProjectPaths, findProjectRoot } from "../../lib/utils/project-root";
const style = {
bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
diff --git a/src/commands/verify.ts b/src/commands/verify.ts
deleted file mode 100644
index 9484fa70..00000000
--- a/src/commands/verify.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { AutoTokenizer } from "@huggingface/transformers";
-import * as ort from "onnxruntime-node";
-
-// CONFIGURATION
-const MODEL_DIR = path.resolve("./osgrep-models/colbert"); // Adjust if your path differs
-const MODEL_PATH = path.join(MODEL_DIR, "model.onnx");
-const SKIPLIST_PATH = path.join(MODEL_DIR, "skiplist.json");
-
-async function main() {
- console.log("š Starting ColBERT Integrity Check...\n");
-
- // --- CHECK 1: FILES EXIST ---
- if (!fs.existsSync(MODEL_PATH))
- throw new Error(`Missing model at ${MODEL_PATH}`);
- if (!fs.existsSync(SKIPLIST_PATH))
- throw new Error(`Missing skiplist at ${SKIPLIST_PATH}`);
- console.log("ā
Files found.");
-
- // --- CHECK 2: TOKENIZER & MARKERS ---
- console.log("ā³ Loading Tokenizer...");
- const tokenizer = await AutoTokenizer.from_pretrained(MODEL_DIR);
-
- const queryText = "function test(a, b)";
- // We manually add the [Q] marker to simulate what the worker does
- // Note: We use the ID we know works from your export: 50368
- // But let's see if the tokenizer resolves "[Q] " correctly.
-
- const encoded = await tokenizer(queryText, { add_special_tokens: false });
- const inputIds = encoded.input_ids; // BigInt64Array in newer transformers versions
-
- // Convert to standard array for inspection
- const ids = Array.from(inputIds).map(Number);
-
- // Mixedbread expects: [CLS] [Q] ...tokens... [SEP]
- // Let's verify we can construct that.
- const Q_ID = 50368;
- const CLS_ID = tokenizer.model.tokens_to_ids.get("[CLS]") ?? 50281; // Fallback to standard if null
-
- console.log(`\n--- Tokenizer Check ---`);
- console.log(`Query: "${queryText}"`);
- console.log(`Raw IDs:`, ids);
-
- // Check if tokenizer recognizes the special tokens by text
- const qCheck = tokenizer.model.tokens_to_ids.get("[Q] ");
- const dCheck = tokenizer.model.tokens_to_ids.get("[D] ");
-
- if (qCheck === 50368 && dCheck === 50369) {
- console.log(`ā
Tokenizer Map Correct: [Q] -> ${qCheck}, [D] -> ${dCheck}`);
- } else {
- console.error(
- `ā Tokenizer Map Mismatch! Found [Q]->${qCheck}, [D]->${dCheck}`,
- );
- console.error(` Expected 50368 and 50369.`);
- }
-
- // --- CHECK 3: SKIPLIST ---
- const skiplist = new Set(JSON.parse(fs.readFileSync(SKIPLIST_PATH, "utf-8")));
- console.log(`\n--- Skiplist Check ---`);
- console.log(`Skiplist size: ${skiplist.size}`);
-
- // Check common punctuation
- const commaId = tokenizer.model.tokens_to_ids.get(",");
- const dotId = tokenizer.model.tokens_to_ids.get(".");
-
- if (skiplist.has(commaId) && skiplist.has(dotId)) {
- console.log(
- `ā
Skiplist contains punctuation ('.'=${dotId}, ','=${commaId})`,
- );
- } else {
- console.error(`ā Skiplist missing basic punctuation!`);
- }
-
- // --- CHECK 4: ONNX INFERENCE ---
- console.log(`\n--- ONNX Inference Check ---`);
- const session = await ort.InferenceSession.create(MODEL_PATH);
- console.log(`Session loaded. Input names: ${session.inputNames}`);
-
- // Construct a dummy batch: [CLS] [Q] test [SEP]
- const batchIds = [
- BigInt(CLS_ID),
- BigInt(Q_ID),
- BigInt(1234),
- BigInt(tokenizer.sep_token_id ?? 50282),
- ];
- const tensorIds = new ort.Tensor(
- "int64",
- new BigInt64Array(batchIds),
- [1, 4],
- );
- const tensorMask = new ort.Tensor(
- "int64",
- new BigInt64Array([BigInt(1), BigInt(1), BigInt(1), BigInt(1)]),
- [1, 4],
- );
-
- const start = performance.now();
- const feeds = { input_ids: tensorIds, attention_mask: tensorMask };
- const results = await session.run(feeds);
- const end = performance.now();
-
- const outputName = session.outputNames[0];
- const embeddings = results[outputName];
-
- // Dims should be [1, 4, 48]
- const dims = embeddings.dims;
- console.log(`Output Dimensions: [${dims.join(", ")}]`);
- console.log(`Inference Time (cold): ${(end - start).toFixed(2)}ms`);
-
- if (dims[2] !== 48) {
- console.error(`ā CRITICAL: Expected dimension 48, got ${dims[2]}`);
- process.exit(1);
- } else {
- console.log(`ā
Correct dimension (48d) detected.`);
- }
-
- // --- CHECK 5: MAXSIM PERFORMANCE SIMULATION ---
- console.log(`\n--- MaxSim Logic Benchmark ---`);
-
- // Create dummy vectors for a fake document (1000 tokens)
- const docLen = 1000;
- const docIds = new Array(docLen)
- .fill(0)
- .map(() => Math.floor(Math.random() * 50000));
-
- // Inject some punctuation into the dummy document to simulate real text
- // Let's say 15% of the doc is punctuation
- let punctuationCount = 0;
- for (let i = 0; i < docLen; i++) {
- if (Math.random() < 0.15) {
- docIds[i] = commaId ?? 0; // Force a comma
- punctuationCount++;
- }
- }
-
- const qLen = 32;
-
- // Naive Dot Product count
- const naiveOps = qLen * docLen;
-
- // Skiplist Dot Product count
- let optimizedOps = 0;
- for (let i = 0; i < qLen; i++) {
- for (let j = 0; j < docLen; j++) {
- if (!skiplist.has(docIds[j])) {
- optimizedOps++;
- }
- }
- }
-
- console.log(`Document Length: ${docLen} tokens`);
- console.log(
- `Punctuation/Skip tokens: ${punctuationCount} (~${((punctuationCount / docLen) * 100).toFixed(1)}%)`,
- );
- console.log(`Naive Operations: ${naiveOps}`);
- console.log(`Skiplist Operations: ${optimizedOps}`);
- console.log(`Savings: ${naiveOps - optimizedOps} operations avoided`);
- console.log(
- `ā” Speedup: ${(naiveOps / optimizedOps).toFixed(2)}x (theoretical)`,
- );
-
- console.log("\nā
VERIFICATION COMPLETE. MODEL IS GOOD TO GO.");
-}
-
-main().catch((err) => {
- console.error("\nā TEST FAILED:", err);
- process.exit(1);
-});
diff --git a/src/config.ts b/src/config.ts
index 65518218..511cb697 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -25,33 +25,11 @@ export const CONFIG = {
QUERY_PREFIX: "",
};
-export const WORKER_TIMEOUT_MS = Number.parseInt(
- process.env.OSGREP_WORKER_TIMEOUT_MS || "60000",
- 10,
-);
-
-export const WORKER_BOOT_TIMEOUT_MS = Number.parseInt(
- process.env.OSGREP_WORKER_BOOT_TIMEOUT_MS || "300000",
- 10,
-);
-
-export const MAX_WORKER_MEMORY_MB = Number.parseInt(
- process.env.OSGREP_MAX_WORKER_MEMORY_MB ||
- String(
- Math.max(
- 2048,
- Math.floor((os.totalmem() / 1024 / 1024) * 0.5), // 50% of system RAM
- ),
- ),
- 10,
-);
-
const HOME = os.homedir();
const GLOBAL_ROOT = path.join(HOME, ".osgrep");
export const PATHS = {
globalRoot: GLOBAL_ROOT,
- models: path.join(GLOBAL_ROOT, "models"),
grammars: path.join(GLOBAL_ROOT, "grammars"),
};
diff --git a/src/eval.ts b/src/eval.ts
deleted file mode 100644
index 82a3d875..00000000
--- a/src/eval.ts
+++ /dev/null
@@ -1,673 +0,0 @@
-// Reduce worker pool fan-out during eval to avoid ONNX concurrency issues
-process.env.OSGREP_WORKER_COUNT ??= "1";
-
-import { Searcher } from "./lib/search/searcher";
-import type { SearchResponse } from "./lib/store/types";
-import { VectorDB } from "./lib/store/vector-db";
-import { gracefulExit } from "./lib/utils/exit";
-import { ensureProjectPaths, findProjectRoot } from "./lib/utils/project-root";
-
-export type EvalCase = {
- query: string;
- expectedPath: string;
- avoidPath?: string; // <--- New field: If this ranks HIGHER than expected, it's a fail.
- note?: string;
-};
-
-export type EvalResult = {
- rr: number;
- found: boolean;
- recall: number;
- path: string;
- query: string;
- note?: string;
- timeMs: number;
-};
-
-export const cases: EvalCase[] = [
- // --- Search & Ranking ---
- {
- query: "How do we merge vector and keyword results before rerank?",
- expectedPath: "src/lib/search/searcher.ts",
- note: "Hybrid search path that stitches LanceDB vector search with FTS.",
- },
- {
- query: "Where do we dedupe overlapping vector and FTS candidates?",
- expectedPath: "src/lib/search/searcher.ts",
- note: "Combines results and removes duplicates ahead of rerank.",
- },
- {
- query: "How do we boost functions and downweight tests or docs?",
- expectedPath: "src/lib/search/searcher.ts",
- note: "applyStructureBoost handles path/type based adjustments.",
- },
- {
- query: "What controls the pre-rerank candidate fanout?",
- expectedPath: "src/lib/search/searcher.ts",
- note: "PRE_RERANK_K calculation before ColBERT scoring.",
- },
- {
- query: "How do we filter searches to a path prefix?",
- expectedPath: "src/lib/search/searcher.ts",
- note: "Path prefix WHERE clause for scoped queries.",
- },
- {
- query: "How do we apply ColBERT rerank scoring to candidates?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "Worker-side rerank that feeds query/docs into maxSim.",
- },
- {
- query: "ColBERT maxSim scoring implementation",
- expectedPath: "src/lib/workers/colbert-math.ts",
- note: "Summed max dot products between query and doc token grids.",
- },
-
- // --- Worker Pool & Embeddings ---
- {
- query: "Why are ONNX workers child processes instead of threads?",
- expectedPath: "src/lib/workers/pool.ts",
- note: "Process pool choice to isolate runtime crashes.",
- },
- {
- query: "How do we timeout and restart stuck worker tasks?",
- expectedPath: "src/lib/workers/pool.ts",
- note: "Task timeout handling that kills and respawns workers.",
- },
- {
- query: "Which script does the worker pool fork at runtime?",
- expectedPath: "src/lib/workers/pool.ts",
- note: "resolveProcessWorker chooses process-child entrypoint.",
- },
- {
- query: "How does worker pool shutdown terminate children?",
- expectedPath: "src/lib/workers/pool.ts",
- note: "destroy() kills processes with SIGTERM/SIGKILL fallback.",
- },
- {
- query: "Where are Granite embeddings loaded from onnx cache?",
- expectedPath: "src/lib/workers/embeddings/granite.ts",
- note: "resolvePaths + load selecting ONNX weights and tokenizer.",
- },
- {
- query: "How do we mean-pool Granite outputs to 384 dimensions?",
- expectedPath: "src/lib/workers/embeddings/granite.ts",
- note: "meanPool normalizes and pads vectors to CONFIG.VECTOR_DIM.",
- },
- {
- query: "How does ColBERT quantize token grids to int8 with a scale?",
- expectedPath: "src/lib/workers/embeddings/colbert.ts",
- note: "runBatch builds int8 arrays and records maxVal scale.",
- },
- {
- query: "Where do we compute pooled_colbert_48d summaries?",
- expectedPath: "src/lib/workers/embeddings/colbert.ts",
- note: "Per-chunk pooled embedding stored alongside dense vectors.",
- },
- {
- query: "How do we normalize ColBERT query embeddings before rerank?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "encodeQuery builds normalized matrix from ONNX output.",
- },
- {
- query: "How are dense and ColBERT embeddings combined for each chunk?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "computeHybrid pairs Granite dense vectors with ColBERT grids.",
- },
- {
- query: "Where do we build anchor chunks with imports and preamble?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "buildAnchorChunk prepends metadata-heavy anchor blocks.",
- },
- {
- query: "How is breadcrumb formatting added to chunk text?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "formatChunkText injects file + context headers.",
- },
- {
- query: "What are the chunk overlap and max size settings?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "MAX_CHUNK_LINES/CHARS and OVERLAP tuning in TreeSitterChunker.",
- },
- {
- query: "How do we fall back when a Tree-sitter grammar is missing?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "chunk() fallback path when parser/grammar cannot load.",
- },
- {
- query: "Where are grammars downloaded and cached?",
- expectedPath: "src/lib/index/grammar-loader.ts",
- note: "GRAMMARS_DIR and ensureGrammars downloader.",
- },
- {
- query: "Which languages and grammars are supported for chunking?",
- expectedPath: "src/lib/core/languages.ts",
- note: "LANGUAGES table that maps extensions to grammars.",
- },
-
- // --- Indexing & Sync ---
- {
- query: "Where do we enforce a writer lock to prevent concurrent indexing?",
- expectedPath: "src/lib/utils/lock.ts",
- note: "LOCK file acquisition and stale process detection.",
- },
- {
- query: "Where is DEFAULT_IGNORE_PATTERNS defined for indexing?",
- expectedPath: "src/lib/index/ignore-patterns.ts",
- note: "DEFAULT_IGNORE_PATTERNS with lockfiles and secrets.",
- },
- {
- query: "Which INDEXABLE_EXTENSIONS are allowed and what is the 10MB limit?",
- expectedPath: "src/config.ts",
- note: "INDEXABLE_EXTENSIONS and MAX_FILE_SIZE_BYTES.",
- },
- {
- query: "How do we reset when VectorDB and meta cache disagree?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "Inconsistency detection that forces drop + rebuild.",
- },
- {
- query: "How are batches flushed to LanceDB before updating meta cache?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "flushBatch writes VectorDB first, then meta entries.",
- },
- {
- query: "How do we remove stale or deleted paths from the index?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "Cleanup of stale paths after scanning is finished.",
- },
- {
- query: "How do we skip unchanged files using mtime/size hashes?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "Meta cache check to bypass re-embedding identical files.",
- },
- {
- query:
- "When does processFile mark shouldDelete for binary, empty, or too-big files?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "processFile returns shouldDelete for non-indexable snapshots.",
- },
- {
- query: "How is the file hash computed for change detection?",
- expectedPath: "src/lib/utils/file-utils.ts",
- note: "computeBufferHash SHA-256 helper.",
- },
- {
- query: "How do we snapshot a file and verify it didn't change during read?",
- expectedPath: "src/lib/utils/file-utils.ts",
- note: "readFileSnapshot double-checks size/mtime before returning.",
- },
-
- // --- Storage & Schema ---
- {
- query: "Where is the LanceDB schema defined, including pooled_colbert_48d?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "Schema with dense, colbert, and pooled embeddings.",
- },
- {
- query: "How do we warn about schema mismatches and ask for a reindex?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "insertBatch error message for field mismatches.",
- },
- {
- query: "Where do we create the full-text index on chunk content?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "createFTSIndex invoking LanceDB FTS.",
- },
-
- // --- CLI Commands ---
- {
- query:
- "How does the search command trigger initial indexing when the store is empty?",
- expectedPath: "src/commands/search.ts",
- note: "Checks hasAnyRows and runs initialSync + spinner.",
- },
- {
- query: "Where does search --dry-run print formatDryRunSummary?",
- expectedPath: "src/commands/search.ts",
- note: "formatDryRunSummary usage for dry-run summaries.",
- },
- {
- query:
- "How does the index command handle --reset then call createFTSIndex?",
- expectedPath: "src/commands/index.ts",
- note: "Indexing workflow before createFTSIndex.",
- },
- {
- query: "How does serve reject search paths outside the project root?",
- expectedPath: "src/commands/serve.ts",
- note: "Path normalization rejecting traversal outside projectRoot.",
- },
- {
- query: "Where does the server enforce a 1MB payload size limit?",
- expectedPath: "src/commands/serve.ts",
- note: "Request body guard that 413s payloads over 1MB.",
- },
- {
- query:
- "How does serve --background redirect logs to ~/.osgrep/logs/server.log?",
- expectedPath: "src/commands/serve.ts",
- note: "Background flag redirecting stdio to server.log.",
- },
- {
- query: "Where does setup ensureSetup runs and grammars get downloaded?",
- expectedPath: "src/commands/setup.ts",
- note: "Setup command invoking ensureSetup and ensureGrammars.",
- },
- {
- query: "How does doctor check PATHS.models for missing model directories?",
- expectedPath: "src/commands/doctor.ts",
- note: "Health checks for PATHS.models and MODEL_IDS.",
- },
- {
- query: "Where is Claude Code plugin installation defined?",
- expectedPath: "src/commands/claude-code.ts",
- note: "Marketplace add + install flow.",
- },
-
- // --- Paths, Config, Environment ---
- {
- query: "How do we create .osgrep directories and add them to .gitignore?",
- expectedPath: "src/lib/utils/project-root.ts",
- note: "ensureProjectPaths scaffolds directories and gitignore entry.",
- },
- {
- query: "How is the project root detected via .git or existing .osgrep?",
- expectedPath: "src/lib/utils/project-root.ts",
- note: "findProjectRoot walking parents and honoring repo roots.",
- },
- {
- query: "Where are PATHS.globalRoot, models, and grammars defined?",
- expectedPath: "src/config.ts",
- note: "PATHS pointing to ~/.osgrep directories.",
- },
- {
- query: "How do workers prefer a local ./models directory when present?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "env.localModelPath override when repo ships models.",
- },
- {
- query: "Where are VECTOR_DIM, COLBERT_DIM, and WORKER_THREADS configured?",
- expectedPath: "src/config.ts",
- note: "CONFIG with VECTOR_DIM, COLBERT_DIM, WORKER_THREADS.",
- },
-
- // --- Extended Coverage ---
- {
- query: "Where do we read WORKER_TIMEOUT_MS from OSGREP_WORKER_TIMEOUT_MS?",
- expectedPath: "src/config.ts",
- note: "WORKER_TIMEOUT_MS env override.",
- },
- {
- query: "Where is TASK_TIMEOUT_MS set for worker tasks?",
- expectedPath: "src/lib/workers/pool.ts",
- note: "OSGREP_WORKER_TASK_TIMEOUT_MS guarded timeout.",
- },
- {
- query:
- "How do we cap worker threads from OSGREP_WORKER_THREADS with a HARD_CAP of 4?",
- expectedPath: "src/config.ts",
- note: "DEFAULT_WORKER_THREADS calculation.",
- },
- {
- query: "Where do we set HF transformers cacheDir and allowLocalModels?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "env.cacheDir and env.allowLocalModels toggles.",
- },
- {
- query: "Where do we load Granite ONNX with CPU execution providers?",
- expectedPath: "src/lib/workers/embeddings/granite.ts",
- note: "load() builds sessionOptions for cpu backend.",
- },
- {
- query: "Where do we limit ColBERT ONNX runtime threads to 1?",
- expectedPath: "src/lib/workers/embeddings/colbert.ts",
- note: "ONNX_THREADS constant and session options.",
- },
- {
- query:
- "How do we normalize ColBERT doc vectors and quantize to int8 scale?",
- expectedPath: "src/lib/workers/embeddings/colbert.ts",
- note: "runBatch builds normalized grid and scale factor.",
- },
- {
- query: "Where do we normalize ColBERT query rows before building matrix?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "encodeQuery normalizes ONNX output rows.",
- },
- {
- query:
- "Where do we convert serialized Buffer objects to Int8Array for rerank?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "rerank converts Buffer/object into Int8Array.",
- },
- {
- query: "Where do we build UUIDs for chunk ids before inserting to LanceDB?",
- expectedPath: "src/lib/workers/orchestrator.ts",
- note: "toPreparedChunks uses uuidv4 for chunk IDs.",
- },
- {
- query:
- "How do we include imports, exports, and top comments in anchor chunks?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "buildAnchorChunk composes sections with metadata.",
- },
- {
- query: "Where do we warn about missing tree-sitter grammars and fall back?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "chunk() logs and falls back when getLanguage fails.",
- },
- {
- query: "Where do we split oversized chunks with line and char overlaps?",
- expectedPath: "src/lib/index/chunker.ts",
- note: "splitIfTooBig uses OVERLAP_LINES and OVERLAP_CHARS.",
- },
- {
- query: "Where is GRAMMARS_DIR set to ~/.osgrep/grammars?",
- expectedPath: "src/lib/index/grammar-loader.ts",
- note: "GRAMMARS_DIR constant.",
- },
- {
- query: "Where do we download grammars with fetch and a custom User-Agent?",
- expectedPath: "src/lib/index/grammar-loader.ts",
- note: "ensureGrammars downloadFile helper.",
- },
- {
- query: "Where do we guard against files changing during read?",
- expectedPath: "src/lib/utils/file-utils.ts",
- note: "readFileSnapshot compares pre/post stats.",
- },
- {
- query: "Where do we detect null bytes before indexing content?",
- expectedPath: "src/lib/utils/file-utils.ts",
- note: "hasNullByte check in processFile path.",
- },
- {
- query: "Where do we register cleanup tasks and execute them at exit?",
- expectedPath: "src/lib/utils/cleanup.ts",
- note: "registerCleanup and runCleanup functions.",
- },
- {
- query: "Where does gracefulExit destroy the worker pool before exiting?",
- expectedPath: "src/lib/utils/exit.ts",
- note: "gracefulExit calls destroyWorkerPool and runCleanup.",
- },
- {
- query: "Where is the LMDB meta cache opened with compression?",
- expectedPath: "src/lib/store/meta-cache.ts",
- note: "MetaCache constructor uses lmdb open() with compression.",
- },
- {
- query: "Where do we connect to LanceDB and seed the table schema?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "ensureTable creates schema and deletes seed row.",
- },
- {
- query: "Where do we drop the LanceDB table during resets?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "drop() helper invoked on reset.",
- },
- {
- query: "Where do we close LanceDB connections and unregister cleanup?",
- expectedPath: "src/lib/store/vector-db.ts",
- note: "close() method clears connections and cleanup hook.",
- },
- {
- query: "Where do we use fast-glob to stream files for indexing?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "fg.stream with glob options for repo walk.",
- },
- {
- query: "Where do we skip duplicate real paths and broken symlinks?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "visitedRealPaths plus try/catch around realpathSync.",
- },
- {
- query: "Where do we abort indexing when AbortSignal is triggered?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "Checks signal.aborted to stop scheduling.",
- },
- {
- query:
- "Where do we flush batches when batch/deletes/meta reach batchLimit?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "flush() checks batchLimit based on EMBED_BATCH_SIZE.",
- },
- {
- query:
- "Where do we detect stale cached paths and delete them after indexing?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "Removes stale paths from VectorDB and meta cache.",
- },
- {
- query:
- "Where do we detect inconsistent VectorDB vs meta cache and force rebuild?",
- expectedPath: "src/lib/index/syncer.ts",
- note: "isInconsistent triggers drop and meta reset.",
- },
- {
- query:
- "Where is createIndexingSpinner updating text for scanning and indexing files?",
- expectedPath: "src/lib/index/sync-helpers.ts",
- note: "createIndexingSpinner onProgress formatting.",
- },
- {
- query:
- "Where does ensureSetup create ~/.osgrep directories with ora spinner?",
- expectedPath: "src/lib/setup/setup-helpers.ts",
- note: "ensureSetup directory creation feedback.",
- },
- {
- query:
- "Where do we download models via a worker thread to avoid ONNX in main thread?",
- expectedPath: "src/lib/setup/model-loader.ts",
- note: "downloadModels spawns worker with ts-node/register when dev.",
- },
- {
- query: "Where do we check areModelsDownloaded before running setup?",
- expectedPath: "src/lib/setup/model-loader.ts",
- note: "areModelsDownloaded verifies cache directories.",
- },
- {
- query:
- "Where does setup command list model and grammar status after finishing?",
- expectedPath: "src/commands/setup.ts",
- note: "Setup command status output with model IDs.",
- },
- {
- query: "Where does doctor print system platform, arch, and Node version?",
- expectedPath: "src/commands/doctor.ts",
- note: "Doctor command system info logging.",
- },
- {
- query: "Where does the list command calculate directory sizes recursively?",
- expectedPath: "src/commands/list.ts",
- note: "getDirectorySize walk.",
- },
- {
- query: "Where does the list command format sizes and time ago text?",
- expectedPath: "src/commands/list.ts",
- note: "formatSize and formatDate helpers.",
- },
- {
- query: "Where does serve register running servers to servers.json?",
- expectedPath: "src/lib/utils/server-registry.ts",
- note: "registerServer writes to ~/.osgrep/servers.json.",
- },
- {
- query: "How does serve status enumerate active servers?",
- expectedPath: "src/commands/serve.ts",
- note: "serve status subcommand uses listServers().",
- },
- {
- query: "How does serve stop --all kill background servers?",
- expectedPath: "src/commands/serve.ts",
- note: "serve stop iterates listServers and SIGTERMs.",
- },
- {
- query: "Where is the LOCK file written and stale PID detection handled?",
- expectedPath: "src/lib/utils/lock.ts",
- note: "acquireWriterLock parses existing lock with pid/start time.",
- },
- {
- query: "Where do we parse .git worktree files to find the main repo root?",
- expectedPath: "src/lib/utils/git.ts",
- note: "getMainRepoRoot and getGitCommonDir for worktrees.",
- },
- {
- query: "Where do we format search results in plain mode for agents?",
- expectedPath: "src/lib/utils/formatter.ts",
- note: "formatTextResults plain mode with agent tags.",
- },
- {
- query: "Where do we apply syntax highlighting for human output?",
- expectedPath: "src/lib/utils/formatter.ts",
- note: "formatTextResults uses cli-highlight when not plain.",
- },
- {
- query:
- "Where do we merge nearby snippets from the same file before printing?",
- expectedPath: "src/lib/utils/formatter.ts",
- note: "Smart stitching merges overlapping chunks per file.",
- },
- {
- query:
- "Where are search CLI options like --scores, --compact, --per-file handled?",
- expectedPath: "src/commands/search.ts",
- note: "Commander options declared for search command.",
- },
- {
- query: "Where is the search path argument normalized against project root?",
- expectedPath: "src/commands/search.ts",
- note: "Relative path handling before searcher.search.",
- },
-];
-
-const topK = 20;
-
-export function evaluateCase(
- response: SearchResponse,
- evalCase: EvalCase,
- timeMs: number,
-): EvalResult {
- const expectedPaths = evalCase.expectedPath
- .split("|")
- .map((p) => p.trim().toLowerCase())
- .filter(Boolean);
-
- const rank = response.data.findIndex((chunk) => {
- const path = chunk.metadata?.path?.toLowerCase() || "";
- return expectedPaths.some((expected) => path.includes(expected));
- });
-
- const avoidRank = response.data.findIndex((chunk) =>
- chunk.metadata?.path
- ?.toLowerCase()
- .includes(evalCase.avoidPath?.toLowerCase() || "_____"),
- );
-
- const hitAvoid =
- evalCase.avoidPath && avoidRank >= 0 && (rank === -1 || avoidRank < rank);
- const found = rank >= 0 && !hitAvoid;
- const rr = found ? 1 / (rank + 1) : 0;
- const recall = found && rank < 10 ? 1 : 0;
-
- return {
- rr,
- found,
- recall,
- path: evalCase.expectedPath,
- query: evalCase.query,
- note: evalCase.note,
- timeMs,
- };
-}
-
-async function run() {
- const root = process.cwd();
- const searchRoot = root;
- const projectRoot = findProjectRoot(searchRoot) ?? searchRoot;
- const paths = ensureProjectPaths(projectRoot);
- const vectorDb = new VectorDB(paths.lancedbDir);
- const searcher = new Searcher(vectorDb);
-
- // 1. Ensure the store exists (VectorDB handles creation, but we check for data)
- const hasRows = await vectorDb.hasAnyRows();
- if (!hasRows) {
- console.error(`ā Store appears to be empty!`);
- console.error(` Run "osgrep index" to populate the store with data.`);
- process.exit(1);
- }
-
- // 2. Check if store has data (redundant but good for sanity)
- try {
- const testResult = await searcher.search("test", 1, { rerank: true });
- if (testResult.data.length === 0) {
- console.error(
- `ā ļø Store appears to be empty (search returned 0 results)!`,
- );
- console.error(` Run "osgrep index" to populate the store with data.`);
- process.exit(1);
- }
- } catch (err) {
- console.error(`ā Error checking store data:`, err);
- process.exit(1);
- }
-
- const results: EvalResult[] = [];
-
- console.log("Starting evaluation...\n");
- const startTime = performance.now();
-
- for (const c of cases) {
- const queryStart = performance.now();
- const res = await searcher.search(c.query, topK, { rerank: false });
- const queryEnd = performance.now();
- const timeMs = queryEnd - queryStart;
-
- results.push(evaluateCase(res, c, timeMs));
- }
-
- const totalTime = performance.now() - startTime;
- const mrr = results.reduce((sum, r) => sum + r.rr, 0) / results.length;
- const recallAt10 =
- results.reduce((sum, r) => sum + r.recall, 0) / results.length;
- const avgTime =
- results.reduce((sum, r) => sum + r.timeMs, 0) / results.length;
-
- console.log("=".repeat(80));
- console.log(`Eval results for store at: ${paths.lancedbDir}`);
- console.log("=".repeat(80));
- results.forEach((r) => {
- const status = r.found ? `rank ${(1 / r.rr).toFixed(0)}` : "ā missed";
- const emoji = r.found ? (r.rr === 1 ? "šÆ" : "ā") : "ā";
- console.log(`${emoji} ${r.query}`);
- console.log(
- ` => ${status} (target: ${r.path}) [${r.timeMs.toFixed(0)}ms]`,
- );
- if (r.note) {
- console.log(` // ${r.note}`);
- }
- });
- console.log("=".repeat(80));
- console.log(`MRR: ${mrr.toFixed(3)}`);
- console.log(`Recall@10: ${recallAt10.toFixed(3)}`);
- console.log(`Avg query time: ${avgTime.toFixed(0)}ms`);
- console.log(`Total time: ${totalTime.toFixed(0)}ms`);
- console.log(
- `Found: ${results.filter((r) => r.found).length}/${results.length}`,
- );
- console.log("=".repeat(80));
-
- await gracefulExit(0);
-}
-
-if (
- // Only auto-run when executed directly (not when imported for experiments/tests)
- require.main === module &&
- process.env.OSGREP_EVAL_AUTORUN !== "0"
-) {
- run().catch((err) => {
- console.error("Eval failed:", err);
- gracefulExit(1);
- });
-}
diff --git a/src/index.ts b/src/index.ts
index 8387883c..5a1d770a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,18 +2,17 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { program } from "commander";
-import { installClaudeCode } from "./commands/claude-code";
-import { installCodex } from "./commands/codex";
-import { doctor } from "./commands/doctor";
-import { installDroid } from "./commands/droid";
+import { installClaudeCode } from "./commands/agent/claude-code";
+import { installCodex } from "./commands/agent/codex";
+import { doctor } from "./commands/utility/doctor";
+import { installDroid } from "./commands/agent/droid";
import { index } from "./commands/index";
-import { list } from "./commands/list";
-import { mcp } from "./commands/mcp";
-import { installOpencode, uninstallOpencode } from "./commands/opencode";
+import { list } from "./commands/utility/list";
+import { mcp } from "./commands/agent/mcp";
+import { installOpencode, uninstallOpencode } from "./commands/agent/opencode";
import { search } from "./commands/search";
import { serve } from "./commands/serve";
import { setup } from "./commands/setup";
-import { skeleton } from "./commands/skeleton";
import { symbols } from "./commands/symbols";
import { trace } from "./commands/trace";
@@ -48,7 +47,6 @@ if (isIndexCommand && fs.existsSync(legacyDataPath)) {
program.addCommand(search, { isDefault: true });
program.addCommand(index);
program.addCommand(list);
-program.addCommand(skeleton);
program.addCommand(symbols);
program.addCommand(trace);
program.addCommand(setup);
diff --git a/src/lib/graph/graph-builder.ts b/src/lib/graph/graph-builder.ts
index a715feb2..9c04ec49 100644
--- a/src/lib/graph/graph-builder.ts
+++ b/src/lib/graph/graph-builder.ts
@@ -1,6 +1,6 @@
-import type { VectorRecord } from "../store/types";
import type { VectorDB } from "../store/vector-db";
-import { escapeSqlString } from "../utils/filter-builder";
+import type { VectorRecord } from "../store/types";
+import { escapeSqlString, normalizePath } from "../utils/filter-builder";
export interface GraphNode {
symbol: string;
@@ -8,122 +8,166 @@ export interface GraphNode {
line: number;
role: string;
calls: string[];
- calledBy: string[];
- complexity?: number;
+}
+
+export interface CallGraph {
+ center: GraphNode | null;
+ callers: GraphNode[];
+ callees: string[];
+}
+
+export interface TraceOptions {
+ depth?: number;
+ callersOnly?: boolean;
+ calleesOnly?: boolean;
+ pathPrefix?: string;
}
export class GraphBuilder {
constructor(private db: VectorDB) {}
/**
- * Find all chunks that call the given symbol.
+ * Find all chunks where the symbol is defined.
+ * Returns multiple if the same symbol is defined in different files.
*/
- async getCallers(symbol: string): Promise {
+ async findDefinitions(symbol: string): Promise {
const table = await this.db.ensureTable();
- const escaped = escapeSqlString(symbol);
+ const whereClause = `array_contains(defined_symbols, '${escapeSqlString(symbol)}')`;
- // Find chunks where referenced_symbols contains the symbol
- const rows = await table
+ const records = (await table
.query()
- .where(`array_contains(referenced_symbols, '${escaped}')`)
- .limit(100)
- .toArray();
+ .where(whereClause)
+ .limit(20)
+ .toArray()) as VectorRecord[];
- return rows.map((row) =>
- this.mapRowToNode(row as unknown as VectorRecord, symbol, "caller"),
- );
+ return records.map((r) => this.recordToNode(r, symbol));
}
/**
- * Find what the given symbol calls.
- * First finds the definition of the symbol, then returns its referenced_symbols.
+ * Find chunks that reference (call) the given symbol.
+ * Excludes chunks that also define the symbol (to avoid self-references).
*/
- async getCallees(symbol: string): Promise {
+ async findCallers(symbol: string, limit = 20): Promise {
const table = await this.db.ensureTable();
- const escaped = escapeSqlString(symbol);
+ const whereClause = `array_contains(referenced_symbols, '${escapeSqlString(symbol)}')`;
- // Find the definition of the symbol
- const rows = await table
+ const records = (await table
.query()
- .where(`array_contains(defined_symbols, '${escaped}')`)
- .limit(1)
- .toArray();
-
- if (rows.length === 0) return [];
-
- const record = rows[0] as unknown as VectorRecord;
- return record.referenced_symbols || [];
+ .where(whereClause)
+ .limit(limit + 20) // Fetch extra to filter self-definitions
+ .toArray()) as VectorRecord[];
+
+ // Filter out self-definitions (where this chunk also defines the symbol)
+ const filtered = records.filter((r) => {
+ const defined = this.toStringArray(r.defined_symbols);
+ return !defined.includes(symbol);
+ });
+
+ return filtered.slice(0, limit).map((r) => {
+ // Find the primary symbol for this chunk (caller)
+ const defined = this.toStringArray(r.defined_symbols);
+ const callerSymbol = defined[0] || r.parent_symbol || "(anonymous)";
+ return this.recordToNode(r, callerSymbol);
+ });
}
/**
- * Build a 1-hop graph around a symbol.
+ * Given a list of symbol names, return only those that have definitions
+ * in the index (i.e., internal to the project, not external libraries).
*/
- async buildGraph(symbol: string): Promise<{
- center: GraphNode | null;
- callers: GraphNode[];
- callees: string[];
- }> {
- const table = await this.db.ensureTable();
- const escaped = escapeSqlString(symbol);
-
- // 1. Get Center (Definition)
- const centerRows = await table
- .query()
- .where(`array_contains(defined_symbols, '${escaped}')`)
- .limit(1)
- .toArray();
-
- const center =
- centerRows.length > 0
- ? this.mapRowToNode(
- centerRows[0] as unknown as VectorRecord,
- symbol,
- "center",
- )
- : null;
+ async filterToInternal(symbols: string[]): Promise {
+ if (symbols.length === 0) return [];
- // 2. Get Callers
- const callers = await this.getCallers(symbol);
-
- // 3. Get Callees (from center)
- const callees = center ? center.calls : [];
+ const table = await this.db.ensureTable();
+ const internal: string[] = [];
+
+ // Batch check which symbols have definitions
+ // For efficiency, we use a single query with OR conditions
+ // But LanceDB doesn't support OR in array_contains well, so we check individually
+ // This is acceptable since callees are typically <20 per function
+ for (const sym of symbols) {
+ const whereClause = `array_contains(defined_symbols, '${escapeSqlString(sym)}')`;
+ const records = await table.query().where(whereClause).limit(1).toArray();
+ if (records.length > 0) {
+ internal.push(sym);
+ }
+ }
- return { center, callers, callees };
+ return internal;
}
- private mapRowToNode(
- row: VectorRecord,
- targetSymbol: string,
- type: "center" | "caller",
- ): GraphNode {
- // Helper to convert Arrow Vector to array if needed
- const toArray = (val: any): string[] => {
- if (val && typeof val.toArray === "function") {
- return val.toArray();
- }
- return Array.isArray(val) ? val : [];
- };
+ /**
+ * Build the call graph for a symbol.
+ * Returns the definition (center), callers, and callees (filtered to internal).
+ */
+ async buildGraph(
+ symbol: string,
+ options?: TraceOptions,
+ ): Promise {
+ const { callersOnly, calleesOnly, pathPrefix } = options || {};
+
+ // Find definitions
+ let definitions = await this.findDefinitions(symbol);
+
+ // Apply path prefix filter if specified
+ if (pathPrefix) {
+ const normalizedPrefix = normalizePath(pathPrefix);
+ definitions = definitions.filter((d) =>
+ d.file.startsWith(normalizedPrefix),
+ );
+ }
- const definedSymbols = toArray(row.defined_symbols);
- const referencedSymbols = toArray(row.referenced_symbols);
+ // For now, take the first definition as center (could show all)
+ const center = definitions[0] || null;
- // If it's a caller, the symbol of interest is the one DOING the calling.
- // We try to find the defined symbol in this chunk that is responsible for the call.
- // If multiple are defined, we pick the first one or the parent_symbol.
+ // Get callers if not callees-only
+ let callers: GraphNode[] = [];
+ if (!calleesOnly) {
+ callers = await this.findCallers(symbol);
+ if (pathPrefix) {
+ const normalizedPrefix = normalizePath(pathPrefix);
+ callers = callers.filter((c) => c.file.startsWith(normalizedPrefix));
+ }
+ }
- let symbol = definedSymbols[0] || row.parent_symbol || "unknown";
- if (type === "center") {
- symbol = targetSymbol;
+ // Get callees if not callers-only
+ let callees: string[] = [];
+ if (!callersOnly && center) {
+ // Get the raw referenced_symbols from the center definition
+ const rawCallees = center.calls;
+ // Filter to only internal symbols (have definitions in index)
+ callees = await this.filterToInternal(rawCallees);
}
+ return { center, callers, callees };
+ }
+
+ private recordToNode(record: VectorRecord, symbol: string): GraphNode {
return {
symbol,
- file: row.path,
- line: row.start_line,
- role: row.role || "IMPLEMENTATION",
- calls: referencedSymbols,
- calledBy: [], // To be filled if we do reverse lookup
- complexity: row.complexity,
+ file: record.path,
+ line: record.start_line,
+ role: record.role || "IMPL",
+ calls: this.toStringArray(record.referenced_symbols),
};
}
+
+ private toStringArray(val: unknown): string[] {
+ if (!val) return [];
+ if (Array.isArray(val)) {
+ return val.filter((v) => typeof v === "string");
+ }
+ if (typeof (val as any).toArray === "function") {
+ try {
+ const arr = (val as any).toArray();
+ if (Array.isArray(arr)) return arr.filter((v) => typeof v === "string");
+ return Array.from(arr || []).filter(
+ (v) => typeof v === "string",
+ ) as string[];
+ } catch {
+ return [];
+ }
+ }
+ return [];
+ }
}
diff --git a/src/lib/index/chunker.ts b/src/lib/index/chunker.ts
index 83251145..fa586a83 100644
--- a/src/lib/index/chunker.ts
+++ b/src/lib/index/chunker.ts
@@ -1,7 +1,7 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { CONFIG } from "../../config";
-import { getLanguageByExtension } from "../core/languages";
+import { getLanguageByExtension } from "../store/languages";
import { GRAMMARS_DIR } from "./grammar-loader";
// web-tree-sitter ships a CommonJS build
@@ -119,7 +119,7 @@ export function formatChunkText(
const sections: string[] = [];
// 1. File path (always first)
- sections.push(`// ${filePath}`);
+ sections.push(`// File: ${filePath}`);
// 2. Imports (if available)
if (chunk.imports && chunk.imports.length > 0) {
diff --git a/src/lib/index/grammar-loader.ts b/src/lib/index/grammar-loader.ts
index 4c91fd50..6ef9feef 100644
--- a/src/lib/index/grammar-loader.ts
+++ b/src/lib/index/grammar-loader.ts
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
-import { LANGUAGES } from "../core/languages";
+import { LANGUAGES } from "../store/languages";
export const GRAMMARS_DIR = path.join(os.homedir(), ".osgrep", "grammars");
diff --git a/src/lib/index/ignore-patterns.ts b/src/lib/index/ignore-patterns.ts
index f7245511..f6adb7a1 100644
--- a/src/lib/index/ignore-patterns.ts
+++ b/src/lib/index/ignore-patterns.ts
@@ -58,3 +58,41 @@ export const DEFAULT_IGNORE_PATTERNS = [
"**/.vscode/**",
"Thumbs.db",
];
+
+// Patterns for generated/auto-generated code files.
+// These are still indexed but receive a score penalty in search results.
+// Uses regex patterns (not globs) for matching against file paths.
+export const GENERATED_FILE_PATTERNS: RegExp[] = [
+ // TypeScript/JavaScript codegen
+ /\.gen\.[jt]sx?$/i,
+ /\.generated\.[jt]sx?$/i,
+ /\.g\.[jt]sx?$/i,
+ /_generated\.[jt]sx?$/i,
+ // TypeScript declaration files (ambient types, often auto-generated)
+ /\.d\.ts$/i,
+ // GraphQL codegen
+ /\.graphql\.[jt]sx?$/i,
+ /\/__generated__\//i,
+ // Protocol Buffers
+ /\.pb\.[a-z]+$/i, // .pb.go, .pb.ts, etc.
+ /_pb2\.py$/i, // Python protobuf
+ /_pb2_grpc\.py$/i,
+ // Go codegen
+ /_gen\.go$/i,
+ /_string\.go$/i, // stringer tool
+ /\.gen\.go$/i,
+ /mock_.*\.go$/i, // mockgen
+ // C# codegen
+ /\.Designer\.cs$/i,
+ /\.g\.cs$/i,
+ /\.g\.i\.cs$/i,
+ // OpenAPI / Swagger
+ /openapi.*\.gen\./i,
+ /swagger.*\.gen\./i,
+ // Prisma
+ /prisma\/client\//i,
+ // Generic patterns
+ /\/generated\//i,
+ /\/gen\//i,
+ /\/codegen\//i,
+];
diff --git a/src/lib/index/syncer.ts b/src/lib/index/syncer.ts
index dc540874..0726d6f2 100644
--- a/src/lib/index/syncer.ts
+++ b/src/lib/index/syncer.ts
@@ -7,8 +7,7 @@ import { VectorDB } from "../store/vector-db";
import { isIndexableFile } from "../utils/file-utils";
import { acquireWriterLockWithRetry, type LockHandle } from "../utils/lock";
import { ensureProjectPaths } from "../utils/project-root";
-import { getWorkerPool } from "../workers/pool";
-import type { ProcessFileResult } from "../workers/worker";
+import { processFile, type ProcessFileResult } from "../workers/orchestrator";
import type { InitialSyncProgress, InitialSyncResult } from "./sync-helpers";
import { walk } from "./walker";
@@ -119,7 +118,6 @@ export async function initialSync(
let total = 0;
onProgress?.({ processed: 0, indexed: 0, total, filePath: "Scanning..." });
- const pool = getWorkerPool();
const cachedPaths =
dryRun || treatAsEmptyCache
? new Set()
@@ -187,28 +185,15 @@ export async function initialSync(
await flushLock;
};
- const isTimeoutError = (err: unknown) =>
- err instanceof Error && err.message?.toLowerCase().includes("timed out");
-
const processFileWithRetry = async (
relPath: string,
absPath: string,
): Promise => {
- let retries = 0;
- while (true) {
- try {
- return await pool.processFile({
- path: relPath,
- absolutePath: absPath,
- });
- } catch (err) {
- if (isTimeoutError(err) && retries === 0) {
- retries += 1;
- continue;
- }
- throw err;
- }
- }
+ // No retries needed - native embedding is stable
+ return await processFile({
+ path: relPath,
+ absolutePath: absPath,
+ });
};
const schedule = async (task: () => Promise) => {
diff --git a/src/lib/native/index.ts b/src/lib/native/index.ts
new file mode 100644
index 00000000..a331eeab
--- /dev/null
+++ b/src/lib/native/index.ts
@@ -0,0 +1,268 @@
+
+
+import { CONFIG, MODEL_IDS } from "../../config";
+
+// Try to load native binding
+let native: typeof import("osgrep-core") | null = null;
+let initPromise: Promise | null = null;
+let initialized = false;
+
+async function loadNative() {
+ if (native) return native;
+
+ try {
+ native = await import("osgrep-core");
+ } catch (e) {
+ throw new Error(
+ `Failed to load osgrep-core native binding. Run 'npm run build:release' in osgrep/osgrep-core/: ${e}`
+ );
+ }
+
+ return native;
+}
+
+/**
+ * Initialize native models. Call once at startup.
+ */
+export async function initNative(): Promise {
+ if (initialized) return;
+ if (initPromise) return initPromise;
+
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+
+ initPromise = (async () => {
+ if (DEBUG_TIMING) console.time("[native] loadNative");
+ const n = await loadNative();
+ if (DEBUG_TIMING) console.timeEnd("[native] loadNative");
+
+ if (!n.isInitialized()) {
+ if (DEBUG_TIMING) console.time("[native] initModels");
+ n.initModels(MODEL_IDS.embed, MODEL_IDS.colbert);
+ if (DEBUG_TIMING) console.timeEnd("[native] initModels");
+ }
+
+ initialized = true;
+ })().finally(() => {
+ initPromise = null;
+ });
+
+ return initPromise;
+}
+
+/**
+ * Check if native models are initialized
+ */
+export function isNativeInitialized(): boolean {
+ return initialized && native?.isInitialized() === true;
+}
+
+// =============================================================================
+// Dense Embeddings
+// =============================================================================
+
+export interface DenseEmbedding {
+ vector: Float32Array;
+}
+
+/**
+ * Embed texts to dense vectors (384-dim, L2-normalized)
+ */
+export async function embedDense(texts: string[]): Promise {
+ await initNative();
+ const n = await loadNative();
+
+ const result = n.embedDense(texts);
+ const dim = CONFIG.VECTOR_DIM;
+
+ // Split flat array into per-text vectors
+ const vectors: Float32Array[] = [];
+ for (let i = 0; i < result.count; i++) {
+ const start = i * dim;
+ const vec = new Float32Array(dim);
+ for (let j = 0; j < dim; j++) {
+ vec[j] = result.embeddings[start + j];
+ }
+ vectors.push(vec);
+ }
+
+ return vectors;
+}
+
+// =============================================================================
+// ColBERT Embeddings (for indexing)
+// =============================================================================
+
+export interface ColbertPacked {
+ /** Quantized embeddings as Int8Array */
+ embeddings: Int8Array;
+ /** Number of tokens per document */
+ lengths: Uint32Array;
+ /** Byte offsets for each document */
+ offsets: Uint32Array;
+}
+
+/**
+ * Embed texts to ColBERT format (48-dim per token, INT8 quantized)
+ * Use at INDEX TIME to pre-compute embeddings
+ */
+export async function embedColbert(texts: string[]): Promise {
+ await initNative();
+ const n = await loadNative();
+
+ const result = n.embedColbertPacked(texts);
+
+ return {
+ embeddings: new Int8Array(result.embeddings),
+ lengths: new Uint32Array(result.lengths),
+ offsets: new Uint32Array(result.offsets),
+ };
+}
+
+// =============================================================================
+// Combined Embedding (for indexing)
+// =============================================================================
+
+export interface HybridEmbedding {
+ dense: Float32Array;
+ colbert: Int8Array;
+ token_ids: Uint32Array;
+ colbertLength: number;
+ colbertOffset: number;
+}
+
+/**
+ * Embed texts for indexing (both dense and ColBERT in one call)
+ * Returns per-text embeddings ready for storage
+ */
+export async function embedBatch(texts: string[]): Promise {
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+ await initNative();
+ const n = await loadNative();
+
+ if (DEBUG_TIMING) console.time(`[native] embedBatch (${texts.length} texts)`);
+ const result = n.embedBatch(texts);
+ if (DEBUG_TIMING) console.timeEnd(`[native] embedBatch (${texts.length} texts)`);
+ const dim = CONFIG.VECTOR_DIM;
+ const colbertDim = CONFIG.COLBERT_DIM;
+
+ const embeddings: HybridEmbedding[] = [];
+ const tokenIds = new Uint32Array(result.colbertTokenIds);
+ let tokenCursor = 0;
+
+ for (let i = 0; i < texts.length; i++) {
+ // Extract dense vector
+ const denseStart = i * dim;
+ const dense = new Float32Array(dim);
+ for (let j = 0; j < dim; j++) {
+ dense[j] = result.dense[denseStart + j];
+ }
+
+ // Extract ColBERT embedding for this doc
+ const colbertOffset = result.colbertOffsets[i];
+ const colbertLength = result.colbertLengths[i];
+ const colbertSize = colbertLength * colbertDim;
+ const colbert = new Int8Array(colbertSize);
+ for (let j = 0; j < colbertSize; j++) {
+ colbert[j] = result.colbertEmbeddings[colbertOffset + j];
+ }
+
+ // Extract token IDs for this doc (length = colbertLength)
+ const tokenSlice = tokenIds.subarray(tokenCursor, tokenCursor + colbertLength);
+ tokenCursor += colbertLength;
+
+ embeddings.push({
+ dense,
+ colbert,
+ token_ids: new Uint32Array(tokenSlice),
+ colbertLength,
+ colbertOffset: 0, // Will be set when storing
+ });
+ }
+
+ return embeddings;
+}
+
+// =============================================================================
+// ColBERT Query Encoding
+// =============================================================================
+
+/**
+ * Encode query for ColBERT reranking
+ * Returns query embedding matrix as Float32Array
+ */
+export async function encodeQueryColbert(query: string): Promise {
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+ await initNative();
+ const n = await loadNative();
+
+ if (DEBUG_TIMING) console.time("[native] encodeQueryColbert");
+ const result = n.encodeQueryColbert(query);
+ if (DEBUG_TIMING) console.timeEnd("[native] encodeQueryColbert");
+ return new Float32Array(result);
+}
+
+// =============================================================================
+// ColBERT Reranking
+// =============================================================================
+
+export interface RerankInput {
+ /** Query ColBERT embedding from encodeQueryColbert */
+ queryEmbedding: Float32Array;
+ /** Packed ColBERT doc embeddings (INT8) */
+ docEmbeddings: Int8Array;
+ /** Packed ColBERT doc token ids (UINT32) aligned to docEmbeddings */
+ docTokenIds: Uint32Array;
+ /** Token counts per doc */
+ docLengths: number[];
+ /** Byte offsets per doc */
+ docOffsets: number[];
+ /** Which doc indices to rerank */
+ candidateIndices: number[];
+ /** How many to return */
+ topK: number;
+}
+
+export interface RerankResult {
+ /** Original indices of top-k docs */
+ indices: number[];
+ /** MaxSim scores */
+ scores: number[];
+}
+
+/**
+ * Rerank documents using pre-indexed ColBERT embeddings
+ */
+export async function rerankColbert(input: RerankInput): Promise {
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+ await initNative();
+ const n = await loadNative();
+
+ const q = Float64Array.from(input.queryEmbedding as any);
+
+ const docs =
+ input.docEmbeddings instanceof Int8Array
+ ? input.docEmbeddings
+ : new Int8Array(input.docEmbeddings as any);
+
+ const tokenIds =
+ input.docTokenIds instanceof Uint32Array
+ ? input.docTokenIds
+ : Uint32Array.from(input.docTokenIds as any);
+
+ if (DEBUG_TIMING) console.time(`[native] rerankColbert (${input.candidateIndices.length} docs)`);
+ const result = n.rerankColbert(
+ q,
+ docs,
+ tokenIds,
+ input.docLengths,
+ input.docOffsets,
+ input.candidateIndices,
+ input.topK
+ );
+ if (DEBUG_TIMING) console.timeEnd(`[native] rerankColbert (${input.candidateIndices.length} docs)`);
+
+ return {
+ indices: Array.from(result.indices),
+ scores: Array.from(result.scores),
+ };
+}
diff --git a/src/lib/output/formatter.ts b/src/lib/output/formatter.ts
index 105e2b75..dc2d2647 100644
--- a/src/lib/output/formatter.ts
+++ b/src/lib/output/formatter.ts
@@ -1,18 +1,8 @@
import * as path from "node:path";
import { highlight } from "cli-highlight";
-import { getLanguageByExtension } from "../core/languages";
+import { getLanguageByExtension } from "../store/languages";
import type { ChunkType, FileMetadata } from "../store/types";
-
-const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
-
-const style = {
- bold: (s: string) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
- dim: (s: string) => (useColors ? `\x1b[2m${s}\x1b[22m` : s),
- green: (s: string) => (useColors ? `\x1b[32m${s}\x1b[39m` : s),
- blue: (s: string) => (useColors ? `\x1b[34m${s}\x1b[39m` : s),
- cyan: (s: string) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
- gray: (s: string) => (useColors ? `\x1b[90m${s}\x1b[39m` : s),
-};
+import { style } from "../utils/ansi";
function detectLanguage(filePath: string): string {
const ext = path.extname(filePath);
@@ -117,50 +107,3 @@ export function formatResults(
return results.map((r) => formatResult(r, root, options)).join("\n\n");
}
-import type { GraphNode } from "../graph/graph-builder";
-
-export function formatTrace(graph: {
- center: GraphNode | null;
- callers: GraphNode[];
- callees: string[];
-}): string {
- if (!graph.center) {
- return style.dim("Symbol not found.");
- }
-
- const lines: string[] = [];
-
- // 1. Callers (Upstream)
- if (graph.callers.length > 0) {
- lines.push(style.bold("Callers (Who calls this?):"));
- graph.callers.forEach((caller) => {
- lines.push(
- ` ${style.blue("ā")} ${style.green(caller.symbol)} ${style.dim(`(${caller.file}:${caller.line})`)}`,
- );
- });
- lines.push("");
- } else {
- lines.push(style.dim("No known callers."));
- lines.push("");
- }
-
- // 2. Center (The Symbol)
- lines.push(style.bold("ā¶ " + graph.center.symbol));
- lines.push(
- ` ${style.dim(`Defined in ${graph.center.file}:${graph.center.line}`)}`,
- );
- lines.push(` ${style.dim(`Role: ${graph.center.role}`)}`);
- lines.push("");
-
- // 3. Callees (Downstream)
- if (graph.callees.length > 0) {
- lines.push(style.bold("Callees (What does this call?):"));
- graph.callees.forEach((callee) => {
- lines.push(` ${style.cyan("ā")} ${callee}`);
- });
- } else {
- lines.push(style.dim("No known callees."));
- }
-
- return lines.join("\n");
-}
diff --git a/src/lib/output/json-formatter.ts b/src/lib/output/json-formatter.ts
deleted file mode 100644
index f9fccdc4..00000000
--- a/src/lib/output/json-formatter.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { GraphNode } from "../graph/graph-builder";
-import type { ChunkType } from "../store/types";
-
-export interface JsonOutput {
- results?: ChunkType[];
- hits?: unknown[];
- tsv?: string;
- format?: string;
- graph?: {
- center: GraphNode | null;
- callers: GraphNode[];
- callees: string[];
- };
- metadata?: {
- count: number;
- query?: string;
- };
-}
-
-export function formatJson(data: JsonOutput): string {
- return JSON.stringify(data, null, 2);
-}
diff --git a/src/lib/search/expander.ts b/src/lib/search/expander.ts
new file mode 100644
index 00000000..bf70d178
--- /dev/null
+++ b/src/lib/search/expander.ts
@@ -0,0 +1,567 @@
+/**
+ * Expander - Retrieval augmentation for osgrep search results.
+ *
+ * Transforms search results from "find relevant chunks" to "build understanding context"
+ * by following symbol references, finding callers, and including neighboring files.
+ *
+ * Design Principles:
+ * 1. Hot Path Stays Hot - No overhead when --expand is not used
+ * 2. Language Agnostic - Uses existing Tree-sitter extracted symbols, no import parsing
+ * 3. Graceful Degradation - Partial expansion is fine, never fail the search
+ * 4. Token Budget Aware - Respects LLM context limits
+ */
+
+import * as path from "node:path";
+import type { Table } from "@lancedb/lancedb";
+import type { ChunkType } from "../store/types";
+import type { VectorDB } from "../store/vector-db";
+import type {
+ ExpandedResult,
+ ExpandOptions,
+ ExpansionNode,
+ ExpansionStats,
+} from "./expansion-types";
+
+/** Default options for expansion */
+const DEFAULT_OPTIONS: Required = {
+ maxDepth: 1,
+ maxExpanded: 20,
+ maxTokens: 0, // 0 = unlimited
+ strategies: ["symbols", "callers", "neighbors"],
+};
+
+/** Tokens per character ratio (rough estimate for code) */
+const TOKENS_PER_CHAR = 0.25;
+
+/**
+ * Escape a string for use in SQL WHERE clauses.
+ */
+function escapeSql(str: string): string {
+ return str.replace(/'/g, "''");
+}
+
+/**
+ * Convert LanceDB array fields to string arrays.
+ */
+function toStrArray(val?: unknown): string[] {
+ if (!val) return [];
+ if (Array.isArray(val)) {
+ return val.filter((v) => typeof v === "string");
+ }
+ if (typeof (val as any).toArray === "function") {
+ try {
+ const arr = (val as any).toArray();
+ if (Array.isArray(arr)) return arr.filter((v) => typeof v === "string");
+ return Array.from(arr || []).filter((v) => typeof v === "string");
+ } catch {
+ return [];
+ }
+ }
+ return [];
+}
+
+/**
+ * Estimate token count for a string.
+ */
+function estimateTokens(content: string): number {
+ return Math.ceil(content.length * TOKENS_PER_CHAR);
+}
+
+/**
+ * Calculate file proximity score between two paths.
+ * Higher score = more related (same directory > nearby > distant).
+ */
+function fileProximity(fromPath: string, toPath: string): number {
+ if (fromPath === toPath) return 0; // Same file, skip
+
+ const fromParts = fromPath.split("/");
+ const toParts = toPath.split("/");
+
+ let common = 0;
+ for (let i = 0; i < Math.min(fromParts.length, toParts.length); i++) {
+ if (fromParts[i] === toParts[i]) common++;
+ else break;
+ }
+
+ // More common path segments = higher score
+ return common / Math.max(fromParts.length, toParts.length);
+}
+
+/**
+ * Extract import path hints from import strings.
+ * e.g., "../auth/handler" -> "auth/handler"
+ */
+function extractPathHint(importStr: string): string | null {
+ // Skip node_modules imports
+ if (!importStr.startsWith(".") && !importStr.startsWith("/")) return null;
+
+ // Remove relative prefix and extension
+ return importStr
+ .replace(/^\.\.?\//, "")
+ .replace(/\.[^/.]+$/, "")
+ .split("/")
+ .slice(-2)
+ .join("/");
+}
+
+export class Expander {
+ constructor(private db: VectorDB) {}
+
+ /**
+ * Expand search results to include related chunks.
+ *
+ * @param results Original search results
+ * @param query The original search query
+ * @param opts Expansion options
+ * @returns Expanded results with relationship metadata
+ */
+ async expand(
+ results: ChunkType[],
+ query: string,
+ opts: ExpandOptions = {},
+ ): Promise {
+ const options = { ...DEFAULT_OPTIONS, ...opts };
+ const { maxDepth, maxExpanded, maxTokens, strategies } = options;
+
+ const stats: ExpansionStats = {
+ symbolsResolved: 0,
+ callersFound: 0,
+ neighborsAdded: 0,
+ totalChunks: results.length,
+ totalTokens: 0,
+ };
+
+ // Track seen chunk IDs to avoid duplicates
+ const seen = new Set();
+ for (const r of results) {
+ const id = this.getChunkId(r);
+ if (id) seen.add(id);
+ stats.totalTokens += estimateTokens(r.text || "");
+ }
+
+ // Token budget tracking
+ let budgetRemaining = maxTokens > 0 ? maxTokens - stats.totalTokens : Infinity;
+
+ let table: Table;
+ try {
+ table = await this.db.ensureTable();
+ } catch {
+ // Database not ready, return original results
+ return {
+ query,
+ original: results,
+ expanded: [],
+ truncated: false,
+ stats,
+ };
+ }
+
+ const allExpanded: ExpansionNode[] = [];
+ let truncated = false;
+
+ // Multi-hop expansion using BFS
+ let frontier: { chunk: ChunkType; depth: number; score: number }[] = results.map(
+ (r) => ({
+ chunk: r,
+ depth: 0,
+ score: 1.0,
+ }),
+ );
+
+ for (let depth = 1; depth <= maxDepth; depth++) {
+ if (truncated || budgetRemaining <= 0) break;
+
+ const nextFrontier: typeof frontier = [];
+
+ for (const node of frontier) {
+ if (allExpanded.length >= maxExpanded || budgetRemaining <= 0) {
+ truncated = true;
+ break;
+ }
+
+ // Symbol resolution (Strategy 1)
+ if (strategies.includes("symbols")) {
+ const symbolNodes = await this.expandSymbols(
+ table,
+ node.chunk,
+ node.score,
+ depth,
+ seen,
+ maxExpanded - allExpanded.length,
+ budgetRemaining,
+ );
+
+ for (const n of symbolNodes) {
+ if (allExpanded.length >= maxExpanded || budgetRemaining <= 0) {
+ truncated = true;
+ break;
+ }
+ allExpanded.push(n);
+ nextFrontier.push({ chunk: n.chunk, depth, score: n.score });
+ stats.symbolsResolved++;
+ const tokens = estimateTokens(n.chunk.text || "");
+ stats.totalTokens += tokens;
+ budgetRemaining -= tokens;
+ }
+ }
+
+ // Caller resolution (Strategy 2)
+ if (strategies.includes("callers") && !truncated) {
+ const callerNodes = await this.expandCallers(
+ table,
+ node.chunk,
+ node.score,
+ depth,
+ seen,
+ maxExpanded - allExpanded.length,
+ budgetRemaining,
+ );
+
+ for (const n of callerNodes) {
+ if (allExpanded.length >= maxExpanded || budgetRemaining <= 0) {
+ truncated = true;
+ break;
+ }
+ allExpanded.push(n);
+ nextFrontier.push({ chunk: n.chunk, depth, score: n.score });
+ stats.callersFound++;
+ const tokens = estimateTokens(n.chunk.text || "");
+ stats.totalTokens += tokens;
+ budgetRemaining -= tokens;
+ }
+ }
+
+ // Neighbor expansion (Strategy 3) - only at depth 1
+ if (strategies.includes("neighbors") && depth === 1 && !truncated) {
+ const neighborNodes = await this.expandNeighbors(
+ table,
+ node.chunk,
+ node.score,
+ depth,
+ seen,
+ maxExpanded - allExpanded.length,
+ budgetRemaining,
+ );
+
+ for (const n of neighborNodes) {
+ if (allExpanded.length >= maxExpanded || budgetRemaining <= 0) {
+ truncated = true;
+ break;
+ }
+ allExpanded.push(n);
+ stats.neighborsAdded++;
+ const tokens = estimateTokens(n.chunk.text || "");
+ stats.totalTokens += tokens;
+ budgetRemaining -= tokens;
+ }
+ }
+ }
+
+ frontier = nextFrontier;
+ }
+
+ // Final sort by score (descending)
+ allExpanded.sort((a, b) => b.score - a.score);
+
+ stats.totalChunks = results.length + allExpanded.length;
+ if (maxTokens > 0) {
+ stats.budgetRemaining = Math.max(0, budgetRemaining);
+ }
+
+ return {
+ query,
+ original: results,
+ expanded: allExpanded,
+ truncated,
+ stats,
+ };
+ }
+
+ /**
+ * Expand by resolving referenced symbols to their definitions.
+ */
+ private async expandSymbols(
+ table: Table,
+ chunk: ChunkType,
+ parentScore: number,
+ depth: number,
+ seen: Set,
+ maxToAdd: number,
+ budgetRemaining: number,
+ ): Promise {
+ const refs = toStrArray(chunk.referenced_symbols);
+ if (refs.length === 0) return [];
+
+ const chunkPath = this.getChunkPath(chunk);
+ const importHints = toStrArray(chunk.imports).map(extractPathHint).filter(Boolean) as string[];
+ const expanded: ExpansionNode[] = [];
+
+ // Limit symbols to process per chunk
+ const symbolsToProcess = refs.slice(0, 10);
+
+ for (const symbol of symbolsToProcess) {
+ if (expanded.length >= maxToAdd || budgetRemaining <= 0) break;
+
+ try {
+ // Query for chunks that define this symbol
+ const results = await table
+ .query()
+ .where(`array_contains(defined_symbols, '${escapeSql(symbol)}')`)
+ .limit(10)
+ .toArray();
+
+ // Sort by proximity to requesting file (import hints help disambiguate)
+ const sorted = results
+ .map((r: any) => ({
+ record: r,
+ proximity: fileProximity(chunkPath, r.path || ""),
+ importMatch: importHints.some((h) => (r.path || "").includes(h)),
+ }))
+ .sort((a, b) => {
+ // Import matches first
+ if (a.importMatch !== b.importMatch) return b.importMatch ? 1 : -1;
+ // Then by proximity
+ return b.proximity - a.proximity;
+ })
+ .slice(0, 3);
+
+ for (const { record, proximity } of sorted) {
+ const id = record.id;
+ if (!id || seen.has(id)) continue;
+
+ const tokens = estimateTokens(record.content || record.display_text || "");
+ if (tokens > budgetRemaining) continue;
+
+ seen.add(id);
+ budgetRemaining -= tokens;
+
+ const mappedChunk = this.mapRecordToChunk(record);
+ const score = parentScore * 0.7 * (0.5 + proximity * 0.5);
+
+ expanded.push({
+ chunk: mappedChunk,
+ relationship: "symbols",
+ via: symbol,
+ depth,
+ score,
+ });
+
+ if (expanded.length >= maxToAdd) break;
+ }
+ } catch (err) {
+ // Graceful degradation - skip this symbol
+ continue;
+ }
+ }
+
+ return expanded;
+ }
+
+ /**
+ * Expand by finding callers (chunks that reference this chunk's defined symbols).
+ */
+ private async expandCallers(
+ table: Table,
+ chunk: ChunkType,
+ parentScore: number,
+ depth: number,
+ seen: Set,
+ maxToAdd: number,
+ budgetRemaining: number,
+ ): Promise {
+ const defined = toStrArray(chunk.defined_symbols);
+ if (defined.length === 0) return [];
+
+ const chunkPath = this.getChunkPath(chunk);
+ const expanded: ExpansionNode[] = [];
+
+ // Limit symbols to process
+ const symbolsToProcess = defined.slice(0, 5);
+
+ for (const symbol of symbolsToProcess) {
+ if (expanded.length >= maxToAdd || budgetRemaining <= 0) break;
+
+ try {
+ // Find chunks that reference this symbol
+ const results = await table
+ .query()
+ .where(`array_contains(referenced_symbols, '${escapeSql(symbol)}')`)
+ .limit(10)
+ .toArray();
+
+ // Sort by proximity
+ const sorted = results
+ .map((r: any) => ({
+ record: r,
+ proximity: fileProximity(chunkPath, r.path || ""),
+ }))
+ .filter((x) => x.record.path !== chunkPath) // Exclude same file
+ .sort((a, b) => b.proximity - a.proximity)
+ .slice(0, 3);
+
+ for (const { record, proximity } of sorted) {
+ const id = record.id;
+ if (!id || seen.has(id)) continue;
+
+ const tokens = estimateTokens(record.content || record.display_text || "");
+ if (tokens > budgetRemaining) continue;
+
+ seen.add(id);
+ budgetRemaining -= tokens;
+
+ const mappedChunk = this.mapRecordToChunk(record);
+ const score = parentScore * 0.6 * (0.5 + proximity * 0.5);
+
+ expanded.push({
+ chunk: mappedChunk,
+ relationship: "callers",
+ via: `uses ${symbol}`,
+ depth,
+ score,
+ });
+
+ if (expanded.length >= maxToAdd) break;
+ }
+ } catch {
+ continue;
+ }
+ }
+
+ return expanded;
+ }
+
+ /**
+ * Expand by including anchor chunks from the same directory.
+ */
+ private async expandNeighbors(
+ table: Table,
+ chunk: ChunkType,
+ parentScore: number,
+ depth: number,
+ seen: Set,
+ maxToAdd: number,
+ budgetRemaining: number,
+ ): Promise {
+ const chunkPath = this.getChunkPath(chunk);
+ if (!chunkPath) return [];
+
+ const dir = path.dirname(chunkPath);
+ const expanded: ExpansionNode[] = [];
+
+ try {
+ // Get anchor chunks from same directory (file summaries)
+ const results = await table
+ .query()
+ .where(`path LIKE '${escapeSql(dir)}/%' AND is_anchor = true`)
+ .limit(5)
+ .toArray();
+
+ for (const record of results as any[]) {
+ if (expanded.length >= maxToAdd || budgetRemaining <= 0) break;
+
+ const id = record.id;
+ if (!id || seen.has(id)) continue;
+ if (record.path === chunkPath) continue; // Skip same file
+
+ const tokens = estimateTokens(record.content || record.display_text || "");
+ if (tokens > budgetRemaining) continue;
+
+ seen.add(id);
+ budgetRemaining -= tokens;
+
+ const mappedChunk = this.mapRecordToChunk(record);
+
+ expanded.push({
+ chunk: mappedChunk,
+ relationship: "neighbors",
+ via: "same directory",
+ depth,
+ score: parentScore * 0.4,
+ });
+ }
+ } catch {
+ // Graceful degradation
+ }
+
+ return expanded;
+ }
+
+ /**
+ * Get a unique ID for a chunk.
+ */
+ private getChunkId(chunk: ChunkType): string {
+ if (chunk.metadata && typeof (chunk.metadata as any).hash === "string") {
+ return (chunk.metadata as any).hash;
+ }
+ const p = this.getChunkPath(chunk);
+ const start = chunk.generated_metadata?.start_line ?? 0;
+ return `${p}:${start}`;
+ }
+
+ /**
+ * Get the file path from a chunk.
+ */
+ private getChunkPath(chunk: ChunkType): string {
+ if (chunk.metadata && typeof (chunk.metadata as any).path === "string") {
+ return (chunk.metadata as any).path;
+ }
+ return "";
+ }
+
+ /**
+ * Map a LanceDB record to ChunkType.
+ */
+ private mapRecordToChunk(record: any): ChunkType {
+ const startLine = record.start_line ?? 0;
+ const endLine = typeof record.end_line === "number" ? record.end_line : startLine;
+ const numLines = Math.max(1, endLine - startLine + 1);
+
+ // Clean content (strip headers)
+ const content = record.display_text || record.content || "";
+ const lines = content.split("\n");
+ let startIdx = 0;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim();
+ if (!line) continue;
+ if (line.startsWith("// File:") || line.startsWith("File:")) continue;
+ if (line.startsWith("Imports:") || line.startsWith("Exports:")) continue;
+ if (line === "---" || line === "(anchor)") continue;
+ if (line.startsWith("//")) continue;
+ startIdx = i;
+ break;
+ }
+
+ const bodyLines = lines.slice(startIdx);
+ const MAX_LINES = 15;
+ let truncatedText = bodyLines.slice(0, MAX_LINES).join("\n");
+ if (bodyLines.length > MAX_LINES) {
+ truncatedText += `\n... (+${bodyLines.length - MAX_LINES} more lines)`;
+ }
+
+ return {
+ type: "text",
+ text: truncatedText.trim(),
+ score: 0,
+ metadata: {
+ path: record.path || "",
+ hash: record.hash || "",
+ is_anchor: !!record.is_anchor,
+ },
+ generated_metadata: {
+ start_line: startLine,
+ end_line: endLine,
+ num_lines: numLines,
+ type: record.chunk_type,
+ },
+ complexity: record.complexity,
+ is_exported: record.is_exported,
+ role: record.role,
+ parent_symbol: record.parent_symbol,
+ defined_symbols: toStrArray(record.defined_symbols),
+ referenced_symbols: toStrArray(record.referenced_symbols),
+ imports: toStrArray(record.imports),
+ exports: toStrArray(record.exports),
+ };
+ }
+}
diff --git a/src/lib/search/expansion-types.ts b/src/lib/search/expansion-types.ts
new file mode 100644
index 00000000..1e5b7c9b
--- /dev/null
+++ b/src/lib/search/expansion-types.ts
@@ -0,0 +1,101 @@
+/**
+ * Types for the retrieval augmentation (--expand) feature.
+ *
+ * This feature transforms osgrep from "find relevant chunks" to
+ * "build understanding context" by following symbol references,
+ * finding callers, and including neighboring files.
+ */
+
+import type { ChunkType } from "../store/types";
+
+/**
+ * Strategy for expanding search results.
+ * - symbols: Follow referenced_symbols ā defined_symbols
+ * - callers: Reverse lookup - who references my defined_symbols
+ * - neighbors: Same directory files (anchor chunks)
+ * - coexports: Files that export symbols I import
+ */
+export type ExpandStrategy = "symbols" | "callers" | "neighbors" | "coexports";
+
+/**
+ * Options for result expansion.
+ */
+export interface ExpandOptions {
+ /** Maximum traversal depth (default: 1) */
+ maxDepth?: number;
+ /** Maximum number of expanded chunks to return (default: 20) */
+ maxExpanded?: number;
+ /** Maximum token budget for expanded results (default: unlimited) */
+ maxTokens?: number;
+ /** Which strategies to use (default: all) */
+ strategies?: ExpandStrategy[];
+}
+
+/**
+ * A single expanded chunk with relationship metadata.
+ */
+export interface ExpansionNode {
+ /** The actual chunk data */
+ chunk: ChunkType;
+ /** How this chunk is related to the original results */
+ relationship: ExpandStrategy;
+ /** Human-readable explanation of the relationship */
+ via: string;
+ /** How many hops from the original result */
+ depth: number;
+ /** Relevance score (higher = more relevant, decays with depth) */
+ score: number;
+}
+
+/**
+ * Statistics about the expansion process.
+ */
+export interface ExpansionStats {
+ /** Number of symbols successfully resolved to definitions */
+ symbolsResolved: number;
+ /** Number of callers found */
+ callersFound: number;
+ /** Number of neighbor files added */
+ neighborsAdded: number;
+ /** Total chunks in the expanded result */
+ totalChunks: number;
+ /** Estimated total tokens in the result */
+ totalTokens: number;
+ /** Remaining token budget (if maxTokens was set) */
+ budgetRemaining?: number;
+}
+
+/**
+ * The result of expanding search results.
+ */
+export interface ExpandedResult {
+ /** The original search query */
+ query: string;
+ /** Original search results (unchanged) */
+ original: ChunkType[];
+ /** Expanded chunks with relationship metadata */
+ expanded: ExpansionNode[];
+ /** Whether any limit was hit during expansion */
+ truncated: boolean;
+ /** Expansion statistics */
+ stats: ExpansionStats;
+}
+
+/**
+ * Internal representation of a chunk for expansion processing.
+ * Contains the raw data needed for symbol/caller resolution.
+ */
+export interface ExpansionChunk {
+ id: string;
+ path: string;
+ startLine: number;
+ endLine: number;
+ content: string;
+ definedSymbols: string[];
+ referencedSymbols: string[];
+ imports: string[];
+ exports: string[];
+ isAnchor: boolean;
+ role?: string;
+ complexity?: number;
+}
diff --git a/src/lib/search/intent.ts b/src/lib/search/intent.ts
deleted file mode 100644
index f12edce6..00000000
--- a/src/lib/search/intent.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-export interface SearchIntent {
- type: "DEFINITION" | "FLOW" | "USAGE" | "ARCHITECTURE" | "GENERAL";
- filters?: {
- definitionsOnly?: boolean;
- usagesOnly?: boolean;
- };
- mode?: "orchestration_first" | "show_examples" | "group_by_role";
-}
-
-export function detectIntent(query: string): SearchIntent {
- const normalized = query.toLowerCase();
-
- // Definition queries
- if (/where is|what is|define/.test(normalized)) {
- return { type: "DEFINITION", filters: { definitionsOnly: true } };
- }
-
- // Implementation queries
- if (/how does|how is|implementation/.test(normalized)) {
- return { type: "FLOW", mode: "orchestration_first" };
- }
-
- // Usage queries
- if (/example|how to use|usage/.test(normalized)) {
- return { type: "USAGE", mode: "show_examples" };
- }
-
- // Architecture queries
- if (/architecture|system|overview/.test(normalized)) {
- return { type: "ARCHITECTURE", mode: "group_by_role" };
- }
-
- return { type: "GENERAL" };
-}
diff --git a/src/lib/search/searcher.ts b/src/lib/search/searcher.ts
index 040a4297..c72c3f32 100644
--- a/src/lib/search/searcher.ts
+++ b/src/lib/search/searcher.ts
@@ -1,5 +1,6 @@
import type { Table } from "@lancedb/lancedb";
import { CONFIG } from "../../config";
+import { GENERATED_FILE_PATTERNS } from "../index/ignore-patterns";
import type {
ChunkType,
SearchFilter,
@@ -8,12 +9,74 @@ import type {
} from "../store/types";
import type { VectorDB } from "../store/vector-db";
import { escapeSqlString, normalizePath } from "../utils/filter-builder";
-import { getWorkerPool } from "../workers/pool";
-import { detectIntent, type SearchIntent } from "./intent";
+import { encodeQuery, rerank } from "../workers/orchestrator";
+import { Expander } from "./expander";
+import type { ExpandedResult, ExpandOptions } from "./expansion-types";
export class Searcher {
constructor(private db: VectorDB) {}
+ private static readonly PRE_RERANK_K_MULT = 5;
+ private static readonly PRE_RERANK_K_MIN = 500;
+ private static readonly RERANK_CANDIDATES_K = 80;
+ private static readonly FUSED_WEIGHT = 0.5;
+ private static readonly MAX_PER_FILE = 3;
+ private static readonly FTS_STOPWORDS = new Set([
+ "a",
+ "an",
+ "and",
+ "are",
+ "as",
+ "at",
+ "be",
+ "before",
+ "but",
+ "by",
+ "do",
+ "does",
+ "for",
+ "from",
+ "how",
+ "i",
+ "if",
+ "in",
+ "into",
+ "is",
+ "it",
+ "of",
+ "on",
+ "or",
+ "our",
+ "that",
+ "the",
+ "their",
+ "then",
+ "this",
+ "to",
+ "we",
+ "what",
+ "when",
+ "where",
+ "which",
+ "who",
+ "why",
+ "with",
+ ]);
+
+ private static normalizeFtsQuery(query: string): string {
+ const tokens = query
+ .toLowerCase()
+ .replace(/[^a-z0-9_]+/g, " ")
+ .split(/\s+/g)
+ .map((t) => t.trim())
+ .filter(Boolean)
+ .filter((t) => t.length >= 3)
+ .filter((t) => !Searcher.FTS_STOPWORDS.has(t));
+
+ // Keep the query short to avoid pathological FTS parsing / scoring.
+ return tokens.slice(0, 16).join(" ");
+ }
+
private mapRecordToChunk(
record: Partial,
score: number,
@@ -132,104 +195,49 @@ export class Searcher {
};
}
+ private static readonly GENERATED_PENALTY = 0.4;
+
private applyStructureBoost(
record: Partial,
score: number,
- intent?: SearchIntent,
): number {
let adjusted = score;
- // Item 6: Anchors are recall helpers, not rank contenders
+ // Anchor penalty (anchors are recall helpers, not results)
if (record.is_anchor) {
- // Minimal penalty to break ties
- const anchorPenalty =
- Number.parseFloat(process.env.OSGREP_ANCHOR_PENALTY ?? "") || 0.99;
- adjusted *= anchorPenalty;
- } else {
- // Only boost non-anchors
- const chunkType = record.chunk_type || "";
- const boosted = [
- "function",
- "class",
- "method",
- "interface",
- "type_alias",
- ];
- if (boosted.includes(chunkType)) {
- let boostFactor = 1.0;
-
- // Base boost
- boostFactor *= 1.1;
-
- // --- Role Boost ---
- if (record.role === "ORCHESTRATION") {
- boostFactor *= 1.5;
- } else if (record.role === "DEFINITION") {
- boostFactor *= 1.2;
- } else if (record.role === "IMPLEMENTATION") {
- boostFactor *= 1.1;
- }
-
- // --- Complexity/Orchestration Boost (User Requested) ---
- const refs = record.referenced_symbols?.length || 0;
-
- if (refs > 5) {
- // Small boost for non-trivial functions
- boostFactor *= 1.1;
- }
- if (refs > 15) {
- // Massive boost for Orchestrators
- boostFactor *= 1.25;
- }
-
- // Intent-based boosts
- if (intent) {
- if (intent.type === "DEFINITION" && record.role === "DEFINITION") {
- boostFactor *= 1.2;
- }
- if (intent.type === "FLOW" && record.role === "ORCHESTRATION") {
- boostFactor *= 1.4;
- }
- if (intent.type === "USAGE" && record.role === "IMPLEMENTATION") {
- boostFactor *= 1.2;
- }
- }
-
- adjusted *= boostFactor;
- }
- }
-
- if (record.role === "DOCS") {
- adjusted *= 0.6;
+ adjusted *= 0.99;
}
const pathStr = (record.path || "").toLowerCase();
+ // Generated file penalty - auto-generated code rarely contains business logic
+ const isGenerated = GENERATED_FILE_PATTERNS.some((pattern) =>
+ pattern.test(pathStr),
+ );
+ if (isGenerated) {
+ adjusted *= Searcher.GENERATED_PENALTY;
+ }
+
// Use path-segment and filename patterns to avoid false positives like "latest"
const isTestPath =
/(^|\/)(__tests__|tests?|specs?|benchmark)(\/|$)/i.test(pathStr) ||
/\.(test|spec)\.[cm]?[jt]sx?$/i.test(pathStr);
if (isTestPath) {
- const testPenalty =
- Number.parseFloat(process.env.OSGREP_TEST_PENALTY ?? "") || 0.5;
- adjusted *= testPenalty;
+ adjusted *= 0.5;
+ }
+ // Tooling/docs-like paths often contain lots of "how do we..." text and can
+ // dominate semantic queries (e.g., `tools/eval.ts`).
+ if (/(^|\/)(tools|scripts|experiments)(\/|$)/i.test(pathStr)) {
+ adjusted *= 0.35;
}
if (
pathStr.endsWith(".md") ||
- pathStr.endsWith(".mdx") ||
- pathStr.endsWith(".txt") ||
pathStr.endsWith(".json") ||
pathStr.endsWith(".lock") ||
pathStr.includes("/docs/")
) {
- const docPenalty =
- Number.parseFloat(process.env.OSGREP_DOC_PENALTY ?? "") || 0.6;
- adjusted *= docPenalty; // Downweight docs/data
- }
- // Import-only penalty
- if ((record.content || "").length < 50 && !record.is_exported) {
- adjusted *= 0.9;
+ adjusted *= 0.6;
}
return adjusted;
@@ -278,22 +286,26 @@ export class Searcher {
return deduped;
}
- private ftsIndexChecked = false;
-
async search(
query: string,
top_k?: number,
- _search_options?: { rerank?: boolean },
- _filters?: SearchFilter,
+ options?: { rerank?: boolean },
+ filters?: SearchFilter,
pathPrefix?: string,
- intent?: SearchIntent,
signal?: AbortSignal,
): Promise {
- const finalLimit = top_k ?? 10;
- const doRerank = _search_options?.rerank ?? true;
- const searchIntent = intent || detectIntent(query);
+ const DEBUG_TIMING = process.env.DEBUG_SEARCH_TIMING === "1";
+ const DEBUG_ABORT = process.env.DEBUG_SERVER === "1" || process.env.DEBUG_ABORT === "1";
+ const t = (label: string) => DEBUG_TIMING && console.time(`[search] ${label}`);
+ const te = (label: string) => DEBUG_TIMING && console.timeEnd(`[search] ${label}`);
- const pool = getWorkerPool();
+ t("total");
+ const finalLimitRaw = top_k ?? 10;
+ const finalLimit =
+ Number.isFinite(finalLimitRaw) && finalLimitRaw > 0 ? finalLimitRaw : 10;
+ const doRerank = options?.rerank ?? true;
+
+ if (DEBUG_ABORT) console.log(`[searcher] start, signal.aborted=${signal?.aborted}`);
if (signal?.aborted) {
const err = new Error("Aborted");
@@ -301,12 +313,15 @@ export class Searcher {
throw err;
}
+ t("encodeQuery");
+ if (DEBUG_ABORT) console.log("[searcher] before encodeQuery");
const {
dense: queryVector,
colbert: queryMatrixRaw,
colbertDim,
- pooled_colbert_48d: queryPooled,
- } = await pool.encodeQuery(query, signal);
+ } = await encodeQuery(query);
+ te("encodeQuery");
+ if (DEBUG_ABORT) console.log(`[searcher] after encodeQuery, signal.aborted=${signal?.aborted}`);
if (signal?.aborted) {
const err = new Error("Aborted");
@@ -328,69 +343,60 @@ export class Searcher {
}
// Handle --def (definition) filter
- const defFilter = _filters?.def;
+ const defFilter = filters?.def;
if (typeof defFilter === "string" && defFilter) {
whereClauseParts.push(
`array_contains(defined_symbols, '${escapeSqlString(defFilter)}')`,
);
- } else if (
- searchIntent.type === "DEFINITION" &&
- searchIntent.filters?.definitionsOnly
- ) {
- // If intent is DEFINITION but no specific symbol provided, filter by role
- whereClauseParts.push(
- `(role = 'DEFINITION' OR array_length(defined_symbols) > 0)`,
- );
}
// Handle --ref (reference) filter
- const refFilter = _filters?.ref;
+ const refFilter = filters?.ref;
if (typeof refFilter === "string" && refFilter) {
whereClauseParts.push(
`array_contains(referenced_symbols, '${escapeSqlString(refFilter)}')`,
);
- } else if (
- searchIntent.type === "USAGE" &&
- searchIntent.filters?.usagesOnly
- ) {
- // If intent is USAGE, we might want to filter out definitions?
- // For now, let's just rely on boosting.
}
const whereClause =
whereClauseParts.length > 0 ? whereClauseParts.join(" AND ") : undefined;
- const envPreK = Number.parseInt(process.env.OSGREP_PRE_K ?? "", 10);
- const PRE_RERANK_K =
- Number.isFinite(envPreK) && envPreK > 0
- ? envPreK
- : Math.max(finalLimit * 5, 500);
+ const PRE_RERANK_K = Math.max(
+ finalLimit * Searcher.PRE_RERANK_K_MULT,
+ Searcher.PRE_RERANK_K_MIN,
+ );
+ t("ensureTable");
let table: Table;
try {
table = await this.db.ensureTable();
} catch {
+ te("ensureTable");
+ te("total");
return { data: [] };
}
+ te("ensureTable");
- // Ensure FTS index exists (lazy init on first search)
- if (!this.ftsIndexChecked) {
- this.ftsIndexChecked = true; // Set immediately to prevent retry spam
- try {
- await this.db.createFTSIndex();
- } catch (e) {
- console.warn("[Searcher] Failed to ensure FTS index:", e);
- }
- }
+ // Skip FTS index check during search - it should be created during indexing.
+ // The createFTSIndex call takes ~500ms even when index exists due to LanceDB overhead.
+ // If FTS search fails below, we fall back gracefully.
+ t("vectorSearch");
+ if (DEBUG_ABORT) console.log("[searcher] before vectorSearch");
let vectorQuery = table.vectorSearch(queryVector).limit(PRE_RERANK_K);
if (whereClause) {
vectorQuery = vectorQuery.where(whereClause);
}
const vectorResults = (await vectorQuery.toArray()) as VectorRecord[];
+ te("vectorSearch");
+ if (DEBUG_ABORT) console.log(`[searcher] after vectorSearch (${vectorResults.length} results), signal.aborted=${signal?.aborted}`);
+ t("ftsSearch");
+ if (DEBUG_ABORT) console.log("[searcher] before ftsSearch");
let ftsResults: VectorRecord[] = [];
try {
- let ftsQuery = table.search(query).limit(PRE_RERANK_K);
+ const ftsText = Searcher.normalizeFtsQuery(query);
+ if (!ftsText) throw new Error("Empty FTS query after normalization");
+ let ftsQuery = table.search(ftsText).limit(PRE_RERANK_K);
if (whereClause) {
ftsQuery = ftsQuery.where(whereClause);
}
@@ -399,6 +405,8 @@ export class Searcher {
const msg = e instanceof Error ? e.message : String(e);
console.warn(`[Searcher] FTS search failed: ${msg}`);
}
+ te("ftsSearch");
+ if (DEBUG_ABORT) console.log(`[searcher] after ftsSearch (${ftsResults.length} results), signal.aborted=${signal?.aborted}`);
if (signal?.aborted) {
const err = new Error("Aborted");
@@ -430,74 +438,27 @@ export class Searcher {
.map(([key]) => docMap.get(key))
.filter(Boolean) as VectorRecord[];
- // Item 8: Widen PRE_RERANK_K
- // Retrieve a wide set for Stage 1 filtering
- const envStage1 = Number.parseInt(process.env.OSGREP_STAGE1_K ?? "", 10);
- const STAGE1_K =
- Number.isFinite(envStage1) && envStage1 > 0 ? envStage1 : 200;
- const topCandidates = fused.slice(0, STAGE1_K);
-
- // Item 9: Two-stage rerank
- // Stage 1: Cheap pooled cosine filter
- let stage2Candidates = topCandidates;
- const envStage2K = Number.parseInt(process.env.OSGREP_STAGE2_K ?? "", 10);
- const STAGE2_K =
- Number.isFinite(envStage2K) && envStage2K > 0 ? envStage2K : 40;
-
- const envRerankTop = Number.parseInt(
- process.env.OSGREP_RERANK_TOP ?? "",
- 10,
- );
- const RERANK_TOP =
- Number.isFinite(envRerankTop) && envRerankTop > 0 ? envRerankTop : 20;
- const envBlend = Number.parseFloat(process.env.OSGREP_RERANK_BLEND ?? "");
- const FUSED_WEIGHT =
- Number.isFinite(envBlend) && envBlend >= 0 ? envBlend : 0.5;
-
- if (queryPooled && topCandidates.length > STAGE2_K) {
- const cosineScores = topCandidates.map((doc) => {
- if (!doc.pooled_colbert_48d) return -1;
- // Manual cosine sim since we don't have helper here easily
- // Assuming vectors are normalized (which they should be from orchestrator)
- let dot = 0;
- const docVec = doc.pooled_colbert_48d;
- for (let i = 0; i < queryPooled.length; i++) {
- dot += queryPooled[i] * (docVec[i] || 0);
- }
- return dot;
- });
-
- // Sort by cosine score and keep top N
- const withScore = topCandidates.map((doc, i) => ({
- doc,
- score: cosineScores[i],
- }));
- withScore.sort((a, b) => b.score - a.score);
- stage2Candidates = withScore.slice(0, STAGE2_K).map((x) => x.doc);
- }
-
- if (stage2Candidates.length === 0) {
+ if (fused.length === 0) {
return { data: [] };
}
- const rerankCandidates = stage2Candidates.slice(0, RERANK_TOP);
+ const rerankCandidates = fused.slice(0, Searcher.RERANK_CANDIDATES_K);
+ t("rerank");
const scores = doRerank
- ? await pool.rerank(
- {
+ ? await rerank({
query: queryMatrixRaw,
docs: rerankCandidates.map((doc) => ({
colbert: (doc.colbert as Buffer | Int8Array | number[]) ?? [],
- scale:
- typeof doc.colbert_scale === "number" ? doc.colbert_scale : 1,
token_ids: Array.isArray((doc as any).doc_token_ids)
? ((doc as any).doc_token_ids as number[])
- : undefined,
+ : typeof (doc as any).doc_token_ids?.toArray === "function"
+ ? (((doc as any).doc_token_ids.toArray() as unknown[]) ?? [])
+ .filter((v) => typeof v === "number") as number[]
+ : [],
})),
colbertDim,
- },
- signal,
- )
+ })
: rerankCandidates.map((doc, idx) => {
// If rerank is disabled, fall back to fusion ordering with structural boost
const key = doc.id || `${doc.path}:${doc.chunk_index}`;
@@ -505,6 +466,7 @@ export class Searcher {
// Small tie-breaker so later items don't all share 0
return fusedScore || 1 / (idx + 1);
});
+ te("rerank");
type ScoredItem = {
record: (typeof rerankCandidates)[number];
@@ -515,8 +477,8 @@ export class Searcher {
const base = scores?.[idx] ?? 0;
const key = doc.id || `${doc.path}:${doc.chunk_index}`;
const fusedScore = candidateScores.get(key) ?? 0;
- const blended = base + FUSED_WEIGHT * fusedScore;
- const boosted = this.applyStructureBoost(doc, blended, searchIntent);
+ const blended = base + Searcher.FUSED_WEIGHT * fusedScore;
+ const boosted = this.applyStructureBoost(doc, blended);
return { record: doc, score: boosted };
});
@@ -529,17 +491,11 @@ export class Searcher {
// Item 10: Per-file diversification
const seenFiles = new Map();
const diversified: ScoredItem[] = [];
- const envMaxPerFile = Number.parseInt(
- process.env.OSGREP_MAX_PER_FILE ?? "",
- 10,
- );
- const MAX_PER_FILE =
- Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3;
for (const item of uniqueScored) {
const path = item.record.path || "";
const count = seenFiles.get(path) || 0;
- if (count < MAX_PER_FILE) {
+ if (count < Searcher.MAX_PER_FILE) {
diversified.push(item);
seenFiles.set(path, count + 1);
}
@@ -556,6 +512,7 @@ export class Searcher {
// Item 12: Score Calibration
const maxScore = finalResults.length > 0 ? finalResults[0]._score : 1.0;
+ te("total");
return {
data: finalResults.map((r: (typeof finalResults)[number]) => {
const chunk = this.mapRecordToChunk(r, r._score || 0);
@@ -573,4 +530,64 @@ export class Searcher {
}),
};
}
+
+ /**
+ * Expand search results to include related chunks.
+ * This is opt-in and adds no overhead when not used.
+ *
+ * @param results Original search results
+ * @param query The original search query
+ * @param opts Expansion options
+ * @returns Expanded results with relationship metadata
+ */
+ async expand(
+ results: ChunkType[],
+ query: string,
+ opts?: ExpandOptions,
+ ): Promise {
+ const expander = new Expander(this.db);
+ return expander.expand(results, query, opts);
+ }
+
+ /**
+ * Search and expand in one call.
+ *
+ * @param query Search query
+ * @param top_k Number of results
+ * @param searchOpts Search options
+ * @param filters Search filters
+ * @param pathPrefix Path prefix filter
+ * @param signal Abort signal
+ * @param expandOpts Expansion options (if provided, results will be expanded)
+ * @returns Search response with optional expansion
+ */
+ async searchAndExpand(
+ query: string,
+ top_k?: number,
+ searchOpts?: { rerank?: boolean },
+ filters?: SearchFilter,
+ pathPrefix?: string,
+ signal?: AbortSignal,
+ expandOpts?: ExpandOptions,
+ ): Promise {
+ const searchResult = await this.search(
+ query,
+ top_k,
+ searchOpts,
+ filters,
+ pathPrefix,
+ signal,
+ );
+
+ if (!expandOpts) {
+ return searchResult;
+ }
+
+ const expanded = await this.expand(searchResult.data, query, expandOpts);
+
+ return {
+ ...searchResult,
+ expanded,
+ };
+ }
}
diff --git a/src/lib/setup/model-loader.ts b/src/lib/setup/model-loader.ts
deleted file mode 100644
index 2d014fa9..00000000
--- a/src/lib/setup/model-loader.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as fs from "node:fs";
-import * as os from "node:os";
-import * as path from "node:path";
-import { Worker } from "node:worker_threads";
-import { MODEL_IDS } from "../../config";
-
-const HOMEDIR = os.homedir();
-const CACHE_DIR = path.join(HOMEDIR, ".osgrep", "models");
-const LOG_MODELS =
- process.env.OSGREP_DEBUG_MODELS === "1" ||
- process.env.OSGREP_DEBUG_MODELS === "true";
-
-/**
- * Triggers the download of models by spawning a worker thread.
- * This prevents the main thread from loading onnxruntime, avoiding exit crashes.
- */
-export async function downloadModels(): Promise {
- return new Promise((resolve, reject) => {
- const tsWorkerPath = path.join(__dirname, "../workers/download-worker.ts");
- const jsWorkerPath = path.join(__dirname, "../workers/download-worker.js");
- const hasTsWorker = fs.existsSync(tsWorkerPath);
- const hasJsWorker = fs.existsSync(jsWorkerPath);
- const runningTs = path.extname(__filename) === ".ts";
- const isDev = (runningTs && hasTsWorker) || (hasTsWorker && !hasJsWorker);
-
- const workerPath = isDev ? tsWorkerPath : jsWorkerPath;
- const execArgv = isDev ? ["-r", "ts-node/register"] : [];
-
- const worker = new Worker(workerPath, { execArgv });
-
- worker.on("message", (msg) => {
- if (msg.type === "progress") {
- // Ignore progress messages for now, or log if debug enabled
- return;
- }
-
- if (msg.status === "success") {
- if (LOG_MODELS) console.log("Worker: Models ready.");
- resolve();
- } else if (msg.status === "error") {
- reject(new Error(msg.error || "Unknown worker error"));
- }
- // Ignore other messages
- });
-
- worker.on("error", (err) => {
- reject(err);
- });
-
- worker.on("exit", (code) => {
- if (code !== 0) {
- reject(new Error(`Download worker exited with code ${code}`));
- }
- });
- });
-}
-
-/**
- * Simple check to see if the cache folder exists for our models.
- * This is a loose check for the UI/Doctor command.
- */
-export function areModelsDownloaded(): boolean {
- // Check if the model directories exist in the cache
- const embedPath = path.join(CACHE_DIR, ...MODEL_IDS.embed.split("/"));
- const colbertPath = path.join(CACHE_DIR, ...MODEL_IDS.colbert.split("/"));
-
- return fs.existsSync(embedPath) && fs.existsSync(colbertPath);
-}
diff --git a/src/lib/setup/setup-helpers.ts b/src/lib/setup/setup-helpers.ts
index bbf7ddec..c5e26e8e 100644
--- a/src/lib/setup/setup-helpers.ts
+++ b/src/lib/setup/setup-helpers.ts
@@ -1,23 +1,19 @@
import * as fs from "node:fs";
import ora from "ora";
import { PATHS } from "../../config";
-import { areModelsDownloaded, downloadModels } from "./model-loader";
export interface SetupPaths {
root: string;
- models: string;
grammars: string;
}
export interface SetupStatus extends SetupPaths {
createdDirs: boolean;
- downloadedModels: boolean;
}
function getPaths(): SetupPaths {
return {
root: PATHS.globalRoot,
- models: PATHS.models,
grammars: PATHS.grammars,
};
}
@@ -32,7 +28,7 @@ export async function ensureSetup({
silent?: boolean;
} = {}): Promise {
const paths = getPaths();
- const dirs = [paths.root, paths.models, paths.grammars];
+ const dirs = [paths.root, paths.grammars];
const needsDirs = dirs.some((dir) => !fs.existsSync(dir));
let createdDirs = false;
@@ -56,22 +52,5 @@ export async function ensureSetup({
throw error;
}
- const modelsPresent = areModelsDownloaded();
- let downloadedModels = false;
-
- if (!modelsPresent) {
- const modelSpinner = !silent
- ? ora("Downloading models (first run)...").start()
- : null;
- try {
- await downloadModels();
- downloadedModels = true;
- modelSpinner?.succeed("Models downloaded and ready");
- } catch (error) {
- modelSpinner?.fail("Failed to download models");
- throw error;
- }
- }
-
- return { ...paths, createdDirs, downloadedModels };
+ return { ...paths, createdDirs };
}
diff --git a/src/lib/skeleton/body-fields.ts b/src/lib/skeleton/body-fields.ts
deleted file mode 100644
index 5c32b33d..00000000
--- a/src/lib/skeleton/body-fields.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/**
- * Body field mappings for TreeSitter AST nodes.
- *
- * Maps language -> node type -> body field name
- * - string: The field name containing the body to elide
- * - null: Node type has no body to elide (e.g., type aliases, interfaces)
- * - undefined: Node type not recognized for this language
- *
- * IMPORTANT: Classes are "containers" - we don't elide their bodies,
- * we recurse into them to skeletonize individual methods.
- */
-
-export const BODY_FIELDS: Record> = {
- typescript: {
- function_declaration: "body",
- method_definition: "body",
- arrow_function: "body",
- generator_function_declaration: "body",
- // Classes are containers - don't elide, recurse into them
- class_declaration: null,
- interface_declaration: null, // Interfaces have no body to elide - keep as-is
- type_alias_declaration: null, // Type aliases are already compact
- enum_declaration: null, // Enums are already compact
- },
-
- tsx: {
- // Same as typescript
- function_declaration: "body",
- method_definition: "body",
- arrow_function: "body",
- generator_function_declaration: "body",
- class_declaration: null, // Container
- interface_declaration: null,
- type_alias_declaration: null,
- enum_declaration: null,
- },
-
- javascript: {
- // Same as typescript (uses tsx grammar)
- function_declaration: "body",
- method_definition: "body",
- arrow_function: "body",
- generator_function_declaration: "body",
- class_declaration: null, // Container
- },
-
- python: {
- function_definition: "body",
- class_definition: null, // Container - recurse into methods
- },
-
- go: {
- function_declaration: "body",
- method_declaration: "body",
- type_declaration: null, // Type declarations are compact
- },
-
- rust: {
- function_item: "body",
- impl_item: null, // Container - recurse into methods
- trait_item: null, // Trait definitions show method signatures - keep as-is
- struct_item: null, // Struct definitions are compact
- enum_item: null, // Enum definitions are compact
- mod_item: "body",
- },
-
- java: {
- method_declaration: "body",
- constructor_declaration: "body",
- class_declaration: null, // Container
- interface_declaration: null,
- enum_declaration: null,
- },
-
- c_sharp: {
- method_declaration: "body",
- constructor_declaration: "body",
- class_declaration: null, // Container
- interface_declaration: null,
- struct_declaration: null, // Container
- namespace_declaration: null,
- },
-
- cpp: {
- function_definition: "body",
- class_specifier: null, // Container
- struct_specifier: null, // Container
- namespace_definition: null, // Container
- enum_specifier: null,
- },
-
- c: {
- function_definition: "body",
- struct_specifier: null,
- enum_specifier: null,
- },
-
- ruby: {
- method: "body",
- class: null, // Container
- module: null, // Container
- singleton_method: "body",
- },
-
- php: {
- function_definition: "body",
- method_declaration: "body",
- class_declaration: null, // Container
- interface_declaration: null,
- trait_declaration: null, // Container
- },
-};
-
-/**
- * Container types - these hold methods/functions but shouldn't be elided themselves.
- * We recurse into them to skeletonize their contents.
- */
-export const CONTAINER_TYPES: Record = {
- typescript: ["class_declaration", "class_body"],
- tsx: ["class_declaration", "class_body"],
- javascript: ["class_declaration", "class_body"],
- python: ["class_definition"],
- go: [], // Go doesn't have classes
- rust: ["impl_item"],
- java: ["class_declaration", "class_body"],
- c_sharp: ["class_declaration", "struct_declaration", "class_body"],
- cpp: ["class_specifier", "struct_specifier"],
- c: [],
- ruby: ["class", "module"],
- php: ["class_declaration", "trait_declaration"],
-};
-
-/**
- * Check if a node type is a container (holds methods).
- */
-export function isContainerType(langId: string, nodeType: string): boolean {
- return CONTAINER_TYPES[langId]?.includes(nodeType) ?? false;
-}
-
-/**
- * Get the body field name for a given language and node type.
- *
- * @returns string - Field name to access body
- * @returns null - Node has no body to elide (keep as-is)
- * @returns undefined - Node type not recognized
- */
-export function getBodyField(
- langId: string,
- nodeType: string,
-): string | null | undefined {
- return BODY_FIELDS[langId]?.[nodeType];
-}
-
-/**
- * Check if a node type has a body that can be elided.
- */
-export function hasBodyField(langId: string, nodeType: string): boolean {
- const field = getBodyField(langId, nodeType);
- return typeof field === "string";
-}
-
-/**
- * Check if a node type should be kept as-is (no elision).
- * These are typically type definitions, interfaces, etc.
- */
-export function shouldPreserveWhole(langId: string, nodeType: string): boolean {
- const field = getBodyField(langId, nodeType);
- return field === null;
-}
diff --git a/src/lib/skeleton/index.ts b/src/lib/skeleton/index.ts
deleted file mode 100644
index 8039d518..00000000
--- a/src/lib/skeleton/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Skeleton module - Code compression for AI agents.
- *
- * Reduces token usage by 80-95% while preserving:
- * - Function/method signatures
- * - Class declarations
- * - Type definitions
- * - Summary of what functions do
- */
-
-export {
- BODY_FIELDS,
- getBodyField,
- hasBodyField,
- shouldPreserveWhole,
-} from "./body-fields";
-export type { SkeletonOptions, SkeletonResult } from "./skeletonizer";
-export { Skeletonizer, skeletonizeFile } from "./skeletonizer";
-export type { ChunkMetadata, SummaryOptions } from "./summary-formatter";
-export {
- formatSkeletonHeader,
- formatSummary,
- getCommentStyle,
-} from "./summary-formatter";
diff --git a/src/lib/skeleton/retriever.ts b/src/lib/skeleton/retriever.ts
deleted file mode 100644
index bc9b7651..00000000
--- a/src/lib/skeleton/retriever.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { VectorDB } from "../store/vector-db";
-
-export async function getStoredSkeleton(
- db: VectorDB,
- filePath: string,
-): Promise {
- try {
- const table = await db.ensureTable();
- // LanceDB query
- const results = await table
- .query()
- .where(`path = '${filePath.replace(/'/g, "''")}' AND is_anchor = true`)
- .limit(1)
- .toArray();
-
- if (results.length > 0) {
- const skel = results[0].file_skeleton;
- if (typeof skel === "string" && skel.length > 0) {
- return skel;
- }
- }
- return null;
- } catch (e) {
- // If table doesn't exist or query fails, return null
- return null;
- }
-}
diff --git a/src/lib/skeleton/skeletonizer.ts b/src/lib/skeleton/skeletonizer.ts
deleted file mode 100644
index d53b8831..00000000
--- a/src/lib/skeleton/skeletonizer.ts
+++ /dev/null
@@ -1,562 +0,0 @@
-/**
- * Skeletonizer - Compress code by replacing function bodies with summaries.
- *
- * Reduces token usage by 80-95% while preserving:
- * - Function/method signatures
- * - Class/interface declarations (structure preserved, methods skeletonized)
- * - Type definitions
- * - Decorators and annotations
- * - Inline summaries of what functions do (calls, complexity, role)
- */
-
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { getLanguageByExtension } from "../core/languages";
-import { GRAMMARS_DIR } from "../index/grammar-loader";
-import { getBodyField } from "./body-fields";
-import {
- type ChunkMetadata,
- formatSkeletonHeader,
- formatSummary,
- getCommentStyle,
-} from "./summary-formatter";
-
-// Import web-tree-sitter (CommonJS)
-const TreeSitter = require("web-tree-sitter");
-const Parser = TreeSitter.Parser;
-const Language = TreeSitter.Language;
-
-// TreeSitter types (matching chunker.ts)
-interface TreeSitterNode {
- type: string;
- text: string;
- startPosition: { row: number; column: number };
- endPosition: { row: number; column: number };
- startIndex: number;
- endIndex: number;
- namedChildren?: TreeSitterNode[];
- children?: TreeSitterNode[];
- parent?: TreeSitterNode;
- childForFieldName?: (field: string) => TreeSitterNode | null;
- previousSibling?: TreeSitterNode | null;
-}
-
-interface TreeSitterParser {
- setLanguage(language: TreeSitterLanguage): void;
- parse(content: string): { rootNode: TreeSitterNode };
-}
-
-type TreeSitterLanguage = Record;
-
-// Result types
-export interface SkeletonResult {
- success: boolean;
- skeleton: string;
- tokenEstimate: number;
- symbolCount: number;
- language: string;
- error?: string;
-}
-
-export interface SkeletonOptions {
- /** Preserve decorators/annotations in output. Default: true */
- preserveDecorators?: boolean;
- /** Include summary comment in elided bodies. Default: true */
- includeSummary?: boolean;
- /** Max function calls to show in summary. Default: 4 */
- maxCallsInSummary?: number;
-}
-
-/** Represents a region to elide (replace with summary) */
-interface ElisionRegion {
- bodyStart: number;
- bodyEnd: number;
- summary: string;
- langId: string;
-}
-
-const DEFAULT_OPTIONS: Required = {
- preserveDecorators: true,
- includeSummary: true,
- maxCallsInSummary: 4,
-};
-
-// WASM locator (same as chunker.ts)
-function resolveTreeSitterWasmLocator(): string {
- try {
- return require.resolve("web-tree-sitter/tree-sitter.wasm");
- } catch {
- try {
- const pkgDir = path.dirname(
- require.resolve("web-tree-sitter/package.json"),
- );
- const candidate = path.join(pkgDir, "tree-sitter.wasm");
- if (fs.existsSync(candidate)) return candidate;
- } catch {
- // fall through
- }
- return path.join(
- __dirname,
- "..",
- "..",
- "..",
- "node_modules",
- "web-tree-sitter",
- "tree-sitter.wasm",
- );
- }
-}
-
-/**
- * Main Skeletonizer class.
- */
-export class Skeletonizer {
- private parser: TreeSitterParser | null = null;
- private languages: Map = new Map();
- private initialized = false;
-
- async init(): Promise {
- if (this.initialized) return;
-
- try {
- const wasmLocator = resolveTreeSitterWasmLocator();
- await Parser.init({ locator: wasmLocator });
- this.parser = new Parser() as TreeSitterParser;
- } catch (_err) {
- console.warn("ā ļø TreeSitter unavailable for skeletonization");
- this.parser = null;
- }
-
- if (!fs.existsSync(GRAMMARS_DIR)) {
- fs.mkdirSync(GRAMMARS_DIR, { recursive: true });
- }
-
- this.initialized = true;
- }
-
- /**
- * Check if a file can be skeletonized.
- */
- isSupported(filePath: string): {
- supported: boolean;
- language?: string;
- reason?: string;
- } {
- const ext = path.extname(filePath).toLowerCase();
- const langDef = getLanguageByExtension(ext);
-
- if (!langDef) {
- return {
- supported: false,
- reason: `Unknown file extension: ${ext}`,
- };
- }
-
- if (!langDef.grammar) {
- return {
- supported: false,
- language: langDef.id,
- reason: `No TreeSitter grammar for ${langDef.id}`,
- };
- }
-
- return {
- supported: true,
- language: langDef.id,
- };
- }
-
- /**
- * Skeletonize a file.
- */
- async skeletonizeFile(
- filePath: string,
- content: string,
- options?: SkeletonOptions,
- ): Promise {
- if (!this.initialized) await this.init();
-
- const opts = { ...DEFAULT_OPTIONS, ...options };
- const support = this.isSupported(filePath);
-
- // Handle unsupported languages
- if (!support.supported) {
- return this.createFallbackResult(
- filePath,
- content,
- support.reason || "Unsupported language",
- );
- }
-
- const ext = path.extname(filePath).toLowerCase();
- const langDef = getLanguageByExtension(ext);
- if (!langDef?.grammar) {
- return this.createFallbackResult(
- filePath,
- content,
- "No grammar available",
- );
- }
-
- // Load language
- const language = await this.getLanguage(langDef.grammar.name);
- if (!language || !this.parser) {
- return this.createFallbackResult(
- filePath,
- content,
- "Failed to load grammar",
- );
- }
-
- try {
- // Parse the file
- this.parser.setLanguage(language);
- const tree = this.parser.parse(content);
- const root = tree.rootNode;
-
- // Find all regions to elide (function/method bodies)
- const elisions: ElisionRegion[] = [];
- this.findElisionRegions(root, langDef.id, content, elisions, opts);
-
- if (elisions.length === 0) {
- // No functions found - return a compact version
- return this.createFallbackResult(
- filePath,
- content,
- "No functions/methods found",
- );
- }
-
- // Sort by position (ascending) for correct reconstruction
- elisions.sort((a, b) => a.bodyStart - b.bodyStart);
-
- // Build skeleton by replacing bodies with summaries
- const skeleton = this.buildSkeleton(content, elisions, langDef.id);
- const tokenEstimate = Math.ceil(skeleton.length / 4);
-
- return {
- success: true,
- skeleton: `${formatSkeletonHeader(filePath, tokenEstimate, langDef.id)}\n${skeleton}`,
- tokenEstimate,
- symbolCount: elisions.length,
- language: langDef.id,
- };
- } catch (err) {
- return this.createFallbackResult(
- filePath,
- content,
- `Parse error: ${err instanceof Error ? err.message : String(err)}`,
- );
- }
- }
-
- /**
- * Find all regions that should be elided (function/method bodies).
- * Recurses into containers (classes) to find methods.
- */
- private findElisionRegions(
- node: TreeSitterNode,
- langId: string,
- content: string,
- elisions: ElisionRegion[],
- opts: Required,
- ): void {
- // Check if this node has a body to elide
- const bodyFieldName = getBodyField(langId, node.type);
-
- if (typeof bodyFieldName === "string") {
- // This is a function/method - extract its body region
- const bodyNode = node.childForFieldName?.(bodyFieldName);
- if (bodyNode) {
- const summary = this.createSummary(bodyNode, langId, opts);
- elisions.push({
- bodyStart: bodyNode.startIndex,
- bodyEnd: bodyNode.endIndex,
- summary,
- langId,
- });
- // Don't recurse into the body - we're eliding it
- return;
- }
- }
-
- // For containers (classes) or other nodes, recurse into children
- const children = node.namedChildren || [];
- for (const child of children) {
- this.findElisionRegions(child, langId, content, elisions, opts);
- }
- }
-
- /**
- * Create a summary comment for a function body.
- */
- private createSummary(
- bodyNode: TreeSitterNode,
- langId: string,
- opts: Required,
- ): string {
- if (!opts.includeSummary) {
- return this.createElidedBody(langId, "// ...");
- }
-
- // Extract metadata from the body
- const referencedSymbols = this.extractReferencedSymbols(bodyNode);
- const complexity = this.calculateComplexity(bodyNode);
- const role = this.classifyRole(complexity, referencedSymbols.length);
-
- const metadata: ChunkMetadata = {
- referencedSymbols,
- complexity,
- role,
- };
-
- const commentStyle = getCommentStyle(langId);
- const summaryLine = formatSummary(metadata, {
- maxCalls: opts.maxCallsInSummary,
- commentStyle,
- });
-
- return this.createElidedBody(langId, summaryLine);
- }
-
- /**
- * Create an elided body with summary for a specific language.
- */
- private createElidedBody(langId: string, summary: string): string {
- switch (langId) {
- case "python":
- // Python uses indented ... (Ellipsis) - valid syntax
- return `\n ${summary}\n ...`;
- case "ruby":
- // Ruby body doesn't include closing 'end' in AST - it's preserved after
- return `\n ${summary}`;
- default:
- // C-style languages use { ... }
- return `{\n ${summary}\n }`;
- }
- }
-
- /**
- * Build the skeleton by replacing function bodies with summaries.
- */
- private buildSkeleton(
- content: string,
- elisions: ElisionRegion[],
- _langId: string,
- ): string {
- const parts: string[] = [];
- let cursor = 0;
-
- for (const elision of elisions) {
- // Add content before this body
- if (elision.bodyStart > cursor) {
- parts.push(content.slice(cursor, elision.bodyStart));
- }
-
- // Add the elided body with summary
- parts.push(elision.summary);
-
- cursor = elision.bodyEnd;
- }
-
- // Add remaining content after last elision
- if (cursor < content.length) {
- parts.push(content.slice(cursor));
- }
-
- return this.cleanupSkeleton(parts.join(""));
- }
-
- /**
- * Clean up the skeleton output.
- */
- private cleanupSkeleton(skeleton: string): string {
- return (
- skeleton
- // Remove excessive blank lines
- .replace(/\n{3,}/g, "\n\n")
- // Trim trailing whitespace on lines
- .split("\n")
- .map((line) => line.trimEnd())
- .join("\n")
- .trim()
- );
- }
-
- /**
- * Create a fallback result when skeletonization isn't possible.
- * Returns first 30 lines as preview.
- */
- private createFallbackResult(
- filePath: string,
- content: string,
- reason: string,
- ): SkeletonResult {
- const lines = content.split("\n");
- const previewLines = lines.slice(0, 30);
- const truncated = lines.length > 30;
-
- const preview = [
- `// Skeleton unavailable: ${reason}`,
- `// File: ${filePath}`,
- `// Showing first ${Math.min(30, lines.length)} lines${truncated ? " (truncated)" : ""}`,
- "",
- ...previewLines,
- ...(truncated ? ["", `// ... ${lines.length - 30} more lines`] : []),
- ].join("\n");
-
- return {
- success: false,
- skeleton: preview,
- tokenEstimate: Math.ceil(preview.length / 4),
- symbolCount: 0,
- language: reason.includes("Unknown file extension")
- ? path.extname(filePath)
- : "unknown",
- error: reason,
- };
- }
-
- /**
- * Load a TreeSitter language grammar.
- */
- private async getLanguage(lang: string): Promise {
- const cached = this.languages.get(lang);
- if (cached !== undefined) return cached;
-
- const wasmPath = path.join(GRAMMARS_DIR, `tree-sitter-${lang}.wasm`);
- if (!fs.existsSync(wasmPath)) {
- this.languages.set(lang, null);
- return null;
- }
-
- try {
- const language = Language
- ? ((await Language.load(wasmPath)) as TreeSitterLanguage | null)
- : null;
- this.languages.set(lang, language);
- return language;
- } catch {
- this.languages.set(lang, null);
- return null;
- }
- }
-
- /**
- * Extract referenced symbols (function calls) from a node.
- */
- private extractReferencedSymbols(node: TreeSitterNode): string[] {
- const refs: string[] = [];
- const seen = new Set();
-
- const extract = (n: TreeSitterNode) => {
- if (n.type === "call_expression" || n.type === "call") {
- const func = n.childForFieldName?.("function");
- if (func) {
- let funcName = func.text;
-
- // Handle member access (obj.method) - extract just method
- if (func.type === "member_expression") {
- const prop = func.childForFieldName?.("property");
- if (prop) funcName = prop.text;
- } else if (func.type === "attribute") {
- const attr = func.childForFieldName?.("attribute");
- if (attr) funcName = attr.text;
- }
-
- // Dedupe and filter noise
- if (funcName && !seen.has(funcName) && funcName.length < 30) {
- seen.add(funcName);
- refs.push(funcName);
- }
- }
- } else if (
- n.type === "method_invocation" || // Java
- n.type === "invocation_expression" // C#
- ) {
- // Java/C# method calls
- const nameNode =
- n.childForFieldName?.("name") || n.childForFieldName?.("function");
- if (nameNode) {
- refs.push(nameNode.text);
- seen.add(nameNode.text);
- }
- } else if (
- n.type === "method_call" || // Ruby
- n.type === "command" || // Ruby
- n.type === "command_call" // Ruby
- ) {
- const nameNode =
- n.childForFieldName?.("method") || n.childForFieldName?.("name");
- if (nameNode) {
- refs.push(nameNode.text);
- seen.add(nameNode.text);
- }
- }
-
- for (const child of n.namedChildren || []) {
- extract(child);
- }
- };
-
- extract(node);
- return refs;
- }
-
- /**
- * Calculate cyclomatic complexity of a node.
- */
- private calculateComplexity(node: TreeSitterNode): number {
- let complexity = 1;
- const complexTypes = [
- "if_statement",
- "for_statement",
- "while_statement",
- "switch_statement",
- "catch_clause",
- "conditional_expression",
- ];
-
- const count = (n: TreeSitterNode) => {
- if (complexTypes.includes(n.type)) {
- complexity++;
- }
- if (n.type === "binary_expression") {
- const op = n.childForFieldName?.("operator");
- if (["&&", "||", "??"].includes(op?.text || "")) {
- complexity++;
- }
- }
- for (const child of n.namedChildren || []) {
- count(child);
- }
- };
-
- count(node);
- return complexity;
- }
-
- /**
- * Classify the role of a function based on its characteristics.
- */
- private classifyRole(complexity: number, refCount: number): string {
- // High complexity + many calls = orchestration
- if (complexity > 5 && refCount > 5) {
- return "ORCHESTRATION";
- }
- return "IMPLEMENTATION";
- }
-}
-
-/**
- * Convenience function to skeletonize a file.
- */
-export async function skeletonizeFile(
- filePath: string,
- content: string,
- options?: SkeletonOptions,
-): Promise {
- const skeletonizer = new Skeletonizer();
- await skeletonizer.init();
- return skeletonizer.skeletonizeFile(filePath, content, options);
-}
diff --git a/src/lib/skeleton/summary-formatter.ts b/src/lib/skeleton/summary-formatter.ts
deleted file mode 100644
index 575dc53b..00000000
--- a/src/lib/skeleton/summary-formatter.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * Format inline summary comments for skeleton bodies.
- *
- * Output format: "// ā call1, call2, call3 | C:8 | ORCH"
- * - Shows referenced symbols (what the function calls)
- * - Shows complexity score
- * - Shows ORCH role only (the interesting one)
- */
-
-export interface ChunkMetadata {
- referencedSymbols?: string[];
- complexity?: number;
- role?: string;
-}
-
-export interface SummaryOptions {
- /** Maximum number of calls to show before truncating */
- maxCalls?: number;
- /** Whether to show complexity */
- showComplexity?: boolean;
- /** Whether to show role (only ORCH is shown) */
- showRole?: boolean;
- /** Comment style for the language */
- commentStyle?: "slash" | "hash" | "dash";
-}
-
-const DEFAULT_OPTIONS: Required = {
- maxCalls: 4,
- showComplexity: true,
- showRole: true,
- commentStyle: "slash",
-};
-
-/**
- * Format the inline summary comment for a function body.
- *
- * @example
- * // Input: { referencedSymbols: ['findByEmail', 'compare', 'sign'], complexity: 8, role: 'ORCHESTRATION' }
- * // Output: "// ā findByEmail, compare, sign | C:8 | ORCH"
- */
-export function formatSummary(
- metadata: ChunkMetadata,
- options: SummaryOptions = {},
-): string {
- const opts = { ...DEFAULT_OPTIONS, ...options };
- const parts: string[] = [];
-
- // Calls (referenced symbols)
- if (metadata.referencedSymbols?.length) {
- const calls = metadata.referencedSymbols.slice(0, opts.maxCalls);
- if (metadata.referencedSymbols.length > opts.maxCalls) {
- calls.push("...");
- }
- parts.push(`ā ${calls.join(", ")}`);
- }
-
- // Complexity (only if > 1, trivial functions don't need it)
- if (opts.showComplexity && metadata.complexity && metadata.complexity > 1) {
- parts.push(`C:${metadata.complexity}`);
- }
-
- // Role (only show ORCH - it's the architecturally interesting one)
- if (opts.showRole && metadata.role === "ORCHESTRATION") {
- parts.push("ORCH");
- }
-
- // Build the comment
- const commentPrefix = getCommentPrefix(opts.commentStyle);
-
- if (parts.length === 0) {
- return `${commentPrefix} ...`;
- }
-
- return `${commentPrefix} ${parts.join(" | ")}`;
-}
-
-/**
- * Get the single-line comment prefix for a language style.
- */
-function getCommentPrefix(style: "slash" | "hash" | "dash"): string {
- switch (style) {
- case "hash":
- return "#";
- case "dash":
- return "--";
- case "slash":
- default:
- return "//";
- }
-}
-
-/**
- * Get the appropriate comment style for a language.
- */
-export function getCommentStyle(langId: string): "slash" | "hash" | "dash" {
- switch (langId) {
- case "python":
- case "ruby":
- case "bash":
- return "hash";
- case "sql":
- case "lua":
- return "dash";
- default:
- return "slash";
- }
-}
-
-/**
- * Format the skeleton file header comment.
- */
-export function formatSkeletonHeader(
- filePath: string,
- tokenEstimate: number,
- langId?: string,
-): string {
- const prefix = langId
- ? getCommentPrefix(getCommentStyle(langId))
- : "//";
- return `${prefix} ${filePath} (skeleton, ~${tokenEstimate} tokens)`;
-}
diff --git a/src/lib/core/languages.ts b/src/lib/store/languages.ts
similarity index 100%
rename from src/lib/core/languages.ts
rename to src/lib/store/languages.ts
diff --git a/src/lib/store/types.ts b/src/lib/store/types.ts
index a22b807c..76ea4f0a 100644
--- a/src/lib/store/types.ts
+++ b/src/lib/store/types.ts
@@ -31,8 +31,6 @@ export type PreparedChunk = {
export type VectorRecord = PreparedChunk & {
vector: Float32Array | number[];
colbert: Int8Array | Buffer | number[];
- colbert_scale: number;
- pooled_colbert_48d?: Float32Array | number[];
doc_token_ids?: number[] | Int32Array;
} & Record;
diff --git a/src/lib/store/vector-db.ts b/src/lib/store/vector-db.ts
index 0c940ef9..094a65a8 100644
--- a/src/lib/store/vector-db.ts
+++ b/src/lib/store/vector-db.ts
@@ -6,7 +6,6 @@ import {
Field,
FixedSizeList,
Float32,
- Float64,
Int32,
List,
Schema,
@@ -56,8 +55,6 @@ export class VectorDB {
is_exported: false,
vector: Array(CONFIG.VECTOR_DIM).fill(0),
colbert: Buffer.alloc(0),
- colbert_scale: 1,
- pooled_colbert_48d: Array(CONFIG.COLBERT_DIM).fill(0),
doc_token_ids: [],
defined_symbols: [],
referenced_symbols: [],
@@ -108,15 +105,6 @@ export class VectorDB {
new Field("complexity", new Float32(), true),
new Field("is_exported", new Bool(), true),
new Field("colbert", new Binary(), true),
- new Field("colbert_scale", new Float64(), true),
- new Field(
- "pooled_colbert_48d",
- new FixedSizeList(
- CONFIG.COLBERT_DIM,
- new Field("item", new Float32(), false),
- ),
- true,
- ),
new Field(
"doc_token_ids",
new List(new Field("item", new Int32(), true)),
@@ -232,12 +220,7 @@ export class VectorDB {
is_exported: rec.is_exported ?? false,
vector: vec,
colbert: toBuffer(rec.colbert),
- colbert_scale:
- typeof rec.colbert_scale === "number" ? rec.colbert_scale : 1,
- pooled_colbert_48d: rec.pooled_colbert_48d
- ? Array.from(rec.pooled_colbert_48d)
- : undefined,
- doc_token_ids: rec.doc_token_ids ? Array.from(rec.doc_token_ids) : null,
+ doc_token_ids: rec.doc_token_ids ? Array.from(rec.doc_token_ids) : [],
defined_symbols: rec.defined_symbols ?? [],
referenced_symbols: rec.referenced_symbols ?? [],
imports: rec.imports ?? [],
diff --git a/src/lib/utils/ansi.ts b/src/lib/utils/ansi.ts
new file mode 100644
index 00000000..03c4fe5c
--- /dev/null
+++ b/src/lib/utils/ansi.ts
@@ -0,0 +1,10 @@
+const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
+
+export const style = {
+ bold: (s: string) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
+ dim: (s: string) => (useColors ? `\x1b[2m${s}\x1b[22m` : s),
+ green: (s: string) => (useColors ? `\x1b[32m${s}\x1b[39m` : s),
+ blue: (s: string) => (useColors ? `\x1b[34m${s}\x1b[39m` : s),
+ cyan: (s: string) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
+ gray: (s: string) => (useColors ? `\x1b[90m${s}\x1b[39m` : s),
+};
diff --git a/src/lib/utils/exit.ts b/src/lib/utils/exit.ts
index b9eac674..f498ae62 100644
--- a/src/lib/utils/exit.ts
+++ b/src/lib/utils/exit.ts
@@ -1,4 +1,3 @@
-import { destroyWorkerPool, isWorkerPoolInitialized } from "../workers/pool";
import { runCleanup } from "./cleanup";
export async function gracefulExit(code?: number): Promise {
@@ -9,14 +8,6 @@ export async function gracefulExit(code?: number): Promise {
? process.exitCode
: 0;
- try {
- if (isWorkerPoolInitialized()) {
- await destroyWorkerPool();
- }
- } catch (err) {
- console.error("[exit] Failed to destroy worker pool:", err);
- }
-
await runCleanup();
// Avoid exiting the process during test runs so Vitest can report results.
diff --git a/src/lib/utils/formatter.ts b/src/lib/utils/formatter.ts
index 33be0212..26b3022d 100644
--- a/src/lib/utils/formatter.ts
+++ b/src/lib/utils/formatter.ts
@@ -1,5 +1,7 @@
import * as path from "node:path";
import { highlight } from "cli-highlight";
+import { getLanguageByExtension } from "../store/languages";
+import { style } from "./ansi";
export interface TextResult {
path: string;
@@ -10,15 +12,6 @@ export interface TextResult {
end_line: number;
}
-const style = {
- bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
- dim: (s: string) => `\x1b[2m${s}\x1b[22m`,
- green: (s: string) => `\x1b[32m${s}\x1b[39m`,
- blue: (s: string) => `\x1b[34m${s}\x1b[39m`,
-};
-
-import { getLanguageByExtension } from "../core/languages";
-
function detectLanguage(filePath: string): string {
const ext = path.extname(filePath);
const lang = getLanguageByExtension(ext);
diff --git a/src/lib/utils/git.ts b/src/lib/utils/git.ts
index 1ddc9f97..a16b44ca 100644
--- a/src/lib/utils/git.ts
+++ b/src/lib/utils/git.ts
@@ -1,11 +1,45 @@
import * as fs from "node:fs";
import * as path from "node:path";
+function realpathOrSelf(filePath: string): string {
+ try {
+ return fs.realpathSync.native(filePath);
+ } catch {
+ return filePath;
+ }
+}
+
+function parseGitdirFromGitfile(gitFilePath: string): string | null {
+ try {
+ const content = fs.readFileSync(gitFilePath, "utf-8").trim();
+ if (!content.startsWith("gitdir: ")) return null;
+ return content.slice(8).trim();
+ } catch {
+ return null;
+ }
+}
+
export function isWorktree(dir: string): boolean {
const gitPath = path.join(dir, ".git");
try {
const stats = fs.statSync(gitPath);
- return stats.isFile();
+
+ // Standard worktree: `.git` is a gitfile pointing at `.git/worktrees/`.
+ if (stats.isFile()) {
+ const gitDir = parseGitdirFromGitfile(gitPath);
+ if (!gitDir) return false;
+
+ const absGitDir = realpathOrSelf(path.resolve(dir, gitDir));
+ return fs.existsSync(path.join(absGitDir, "commondir"));
+ }
+
+ // Some tooling uses a symlinked directory for `.git` (e.g. external workspaces).
+ // Worktree git dirs include a `commondir` file; main repo `.git` typically does not.
+ if (stats.isDirectory()) {
+ return fs.existsSync(path.join(gitPath, "commondir"));
+ }
+
+ return false;
} catch {
return false;
}
@@ -14,20 +48,34 @@ export function isWorktree(dir: string): boolean {
export function getGitCommonDir(worktreeRoot: string): string | null {
const gitPath = path.join(worktreeRoot, ".git");
try {
- const content = fs.readFileSync(gitPath, "utf-8").trim();
- if (!content.startsWith("gitdir: ")) return null;
+ const stats = fs.statSync(gitPath);
- const gitDir = content.slice(8).trim();
- const absGitDir = path.resolve(worktreeRoot, gitDir);
+ if (stats.isFile()) {
+ const gitDir = parseGitdirFromGitfile(gitPath);
+ if (!gitDir) return null;
+
+ const absGitDir = realpathOrSelf(path.resolve(worktreeRoot, gitDir));
+
+ const commonDirFile = path.join(absGitDir, "commondir");
+ if (fs.existsSync(commonDirFile)) {
+ const commonPath = fs.readFileSync(commonDirFile, "utf-8").trim();
+ return realpathOrSelf(path.resolve(absGitDir, commonPath));
+ }
+
+ // Fallback: assume standard structure
+ return realpathOrSelf(path.resolve(absGitDir, "../../"));
+ }
+
+ if (stats.isDirectory()) {
+ const commonDirFile = path.join(gitPath, "commondir");
+ if (!fs.existsSync(commonDirFile)) return null;
- const commonDirFile = path.join(absGitDir, "commondir");
- if (fs.existsSync(commonDirFile)) {
const commonPath = fs.readFileSync(commonDirFile, "utf-8").trim();
- return path.resolve(absGitDir, commonPath);
+ const resolvedGitDir = realpathOrSelf(gitPath);
+ return realpathOrSelf(path.resolve(resolvedGitDir, commonPath));
}
- // Fallback: assume standard structure
- return path.resolve(absGitDir, "../../");
+ return null;
} catch {
return null;
}
diff --git a/src/lib/workers/colbert-math.ts b/src/lib/workers/colbert-math.ts
deleted file mode 100644
index ea5b04d9..00000000
--- a/src/lib/workers/colbert-math.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { inner } from "simsimd";
-import { MODEL_IDS, PATHS } from "../../config";
-
-let SKIP_IDS: Set | null = null;
-
-function loadSkipIds(): Set {
- if (SKIP_IDS) return SKIP_IDS;
-
- // Check local models first (same logic as orchestrator)
- const PROJECT_ROOT = process.env.OSGREP_PROJECT_ROOT
- ? path.resolve(process.env.OSGREP_PROJECT_ROOT)
- : process.cwd();
- const localModels = path.join(PROJECT_ROOT, "models");
- const localColbert = path.join(localModels, ...MODEL_IDS.colbert.split("/"));
- const localSkipPath = path.join(localColbert, "skiplist.json");
-
- // Try local first, then global
- const globalBasePath = path.join(
- PATHS.models,
- ...MODEL_IDS.colbert.split("/"),
- );
- const globalSkipPath = path.join(globalBasePath, "skiplist.json");
-
- const skipPath = fs.existsSync(localSkipPath)
- ? localSkipPath
- : globalSkipPath;
-
- if (fs.existsSync(skipPath)) {
- try {
- const parsed = JSON.parse(fs.readFileSync(skipPath, "utf8")) as number[];
- SKIP_IDS = new Set(parsed.map((n) => Number(n)));
- return SKIP_IDS;
- } catch (_e) {
- // fall through to empty set
- }
- }
- SKIP_IDS = new Set();
- return SKIP_IDS;
-}
-
-export function maxSim(
- queryEmbeddings: number[][] | Float32Array[],
- docEmbeddings: number[][] | Float32Array[],
- docTokenIds?: number[],
-): number {
- if (queryEmbeddings.length === 0 || docEmbeddings.length === 0) {
- return 0;
- }
-
- const qVecs = queryEmbeddings.map((v) =>
- v instanceof Float32Array ? v : new Float32Array(v),
- );
- const dVecs = docEmbeddings.map((v) =>
- v instanceof Float32Array ? v : new Float32Array(v),
- );
- const dTokenIds =
- docTokenIds && docTokenIds.length === dVecs.length ? docTokenIds : null;
- const skipIds = loadSkipIds();
-
- let totalScore = 0;
- for (const qVec of qVecs) {
- let maxDotProduct = -Infinity;
- for (let idx = 0; idx < dVecs.length; idx++) {
- const tokenId = dTokenIds ? dTokenIds[idx] : null;
- if (tokenId !== null && skipIds.has(Number(tokenId))) continue;
- const dVec = dVecs[idx];
- const dim = Math.min(qVec.length, dVec.length);
- const dot = inner(qVec.subarray(0, dim), dVec.subarray(0, dim));
- if (dot > maxDotProduct) maxDotProduct = dot;
- }
- if (maxDotProduct === -Infinity) maxDotProduct = 0;
- totalScore += maxDotProduct;
- }
-
- return totalScore;
-}
-
-export function cosineSim(
- a: number[] | Float32Array,
- b: number[] | Float32Array,
-): number {
- const aVec = a instanceof Float32Array ? a : new Float32Array(a);
- const bVec = b instanceof Float32Array ? b : new Float32Array(b);
-
- const dim = Math.min(aVec.length, bVec.length);
- if (aVec.length !== bVec.length) {
- return inner(aVec.subarray(0, dim), bVec.subarray(0, dim));
- }
- return inner(aVec, bVec);
-}
diff --git a/src/lib/workers/colbert-tokenizer.ts b/src/lib/workers/colbert-tokenizer.ts
deleted file mode 100644
index 0571a1e5..00000000
--- a/src/lib/workers/colbert-tokenizer.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import {
- AutoTokenizer,
- type PreTrainedTokenizer,
-} from "@huggingface/transformers";
-
-const QUERY_MARKER_TOKEN = "[Q] ";
-const DOC_MARKER_TOKEN = "[D] ";
-const MASK_TOKEN = "[MASK]";
-const QUERY_MAXLEN = 32; // Standard ColBERT query length
-const DOC_MAXLEN = 512; // Standard ColBERT document length
-
-export class ColBERTTokenizer {
- private tokenizer: PreTrainedTokenizer | null = null;
- private specialTokenIds: {
- cls: number;
- sep: number;
- pad: number;
- mask: number;
- queryMarker: number;
- docMarker: number;
- } | null = null;
-
- async init(modelPath: string) {
- this.tokenizer = await AutoTokenizer.from_pretrained(modelPath);
-
- // Get special token IDs with fallbacks
- // We use the IDs we discovered in validation: [Q]=50368, [D]=50369
- // But we still try to look them up dynamically first.
-
- const tokenizer = this.tokenizer;
- const get = (token: string) => tokenizer?.model.tokens_to_ids.get(token);
-
- const specialTokens = tokenizer as Partial<{
- cls_token: string;
- sep_token: string;
- pad_token: string;
- }>;
- const clsId = get(specialTokens.cls_token ?? "[CLS]") ?? 50281;
- const sepId = get(specialTokens.sep_token ?? "[SEP]") ?? 50282;
- const padId = get(specialTokens.pad_token ?? "[PAD]") ?? 50283;
- const maskId = get(MASK_TOKEN) ?? 50284;
- const queryMarkerId = get(QUERY_MARKER_TOKEN) ?? 50368;
- const docMarkerId = get(DOC_MARKER_TOKEN) ?? 50369;
-
- this.specialTokenIds = {
- cls: clsId,
- sep: sepId,
- pad: padId,
- mask: maskId,
- queryMarker: queryMarkerId,
- docMarker: docMarkerId,
- };
- }
-
- async encodeQuery(
- text: string,
- ): Promise<{ input_ids: bigint[]; attention_mask: bigint[] }> {
- if (!this.tokenizer || !this.specialTokenIds) {
- throw new Error("Tokenizer not initialized. Call init() first.");
- }
-
- // Tokenize without special tokens
- const encoded = await this.tokenizer(text, {
- add_special_tokens: false,
- truncation: true,
- max_length: QUERY_MAXLEN - 2, // Reserve space for [CLS] and [Q]
- });
-
- const { input_ids } = encoded;
-
- // Build sequence: [CLS] [Q] token1 token2 ... [SEP] [MASK] [MASK] ...
- const finalIds: number[] = [
- this.specialTokenIds.cls,
- this.specialTokenIds.queryMarker,
- ...Array.from(input_ids.data as BigInt64Array).map(Number),
- this.specialTokenIds.sep,
- ];
-
- // Query Expansion: pad with [MASK] tokens up to QUERY_MAXLEN
- while (finalIds.length < QUERY_MAXLEN) {
- finalIds.push(this.specialTokenIds.mask);
- }
-
- // Truncate if somehow longer (safety check)
- if (finalIds.length > QUERY_MAXLEN) {
- finalIds.length = QUERY_MAXLEN;
- }
-
- // Create attention mask (1 for all tokens, since MASK is also attended to)
- const attentionMask = new Array(finalIds.length).fill(1);
-
- return {
- input_ids: finalIds.map((id) => BigInt(id)),
- attention_mask: attentionMask.map((v) => BigInt(v)),
- };
- }
-
- async encodeDoc(
- text: string,
- ): Promise<{ input_ids: bigint[]; attention_mask: bigint[] }> {
- if (!this.tokenizer || !this.specialTokenIds) {
- throw new Error("Tokenizer not initialized. Call init() first.");
- }
-
- // Tokenize without special tokens
- const encoded = await this.tokenizer(text, {
- add_special_tokens: false,
- truncation: true,
- max_length: DOC_MAXLEN - 3, // Reserve space for [CLS], [D], and [SEP]
- });
-
- const { input_ids } = encoded;
-
- // Build sequence: [CLS] [D] token1 token2 ... [SEP]
- const finalIds: number[] = [
- this.specialTokenIds.cls,
- this.specialTokenIds.docMarker,
- ...Array.from(input_ids.data as BigInt64Array).map(Number),
- this.specialTokenIds.sep,
- ];
-
- // Create attention mask
- const attentionMask = new Array(finalIds.length).fill(1);
-
- return {
- input_ids: finalIds.map((id) => BigInt(id)),
- attention_mask: attentionMask.map((v) => BigInt(v)),
- };
- }
-}
diff --git a/src/lib/workers/download-worker.ts b/src/lib/workers/download-worker.ts
deleted file mode 100644
index 1bfd9040..00000000
--- a/src/lib/workers/download-worker.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import * as fs from "node:fs";
-import * as os from "node:os";
-import * as path from "node:path";
-import { parentPort } from "node:worker_threads";
-import { env, pipeline } from "@huggingface/transformers";
-import { MODEL_IDS } from "../../config";
-
-// Configuration
-const HOMEDIR = os.homedir();
-const CACHE_DIR = path.join(HOMEDIR, ".osgrep", "models");
-env.cacheDir = CACHE_DIR;
-env.allowLocalModels = true;
-env.allowRemoteModels = true;
-
-// Suppress noisy warnings from transformers.js/onnxruntime
-const originalWarn = console.warn;
-console.warn = (...args) => {
- if (
- args[0] &&
- typeof args[0] === "string" &&
- args[0].includes("Unable to determine content-length")
- ) {
- return;
- }
- originalWarn(...args);
-};
-
-type QuantizationDType =
- | "auto"
- | "fp32"
- | "fp16"
- | "q8"
- | "int8"
- | "uint8"
- | "q4"
- | "bnb4"
- | "q4f16";
-
-type PipelineDType = QuantizationDType | Record;
-
-// Helper to download with timeout
-async function downloadModelWithTimeout(modelId: string, dtype: PipelineDType) {
- const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
-
- try {
- const downloadPromise = pipeline("feature-extraction", modelId, {
- dtype,
- progress_callback: (progress: unknown) => {
- if (parentPort) parentPort.postMessage({ type: "progress", progress });
- },
- });
-
- const timeoutPromise = new Promise((_, reject) => {
- setTimeout(
- () => reject(new Error(`Download timed out after ${TIMEOUT_MS} ms`)),
- TIMEOUT_MS,
- );
- });
-
- return Promise.race([downloadPromise, timeoutPromise]);
- } catch (err) {
- console.error(`Worker: pipeline creation failed for ${modelId}: `, err);
- throw err;
- }
-}
-
-// Helper to manually download extra files like skiplist.json
-async function downloadExtraFile(modelId: string, filename: string) {
- const url = `https://huggingface.co/${modelId}/resolve/main/${filename}`;
- // Construct path: ~/.osgrep/models/ryandono/osgrep-colbert-q8/skiplist.json
- const destDir = path.join(CACHE_DIR, ...modelId.split("/"));
- const destPath = path.join(destDir, filename);
-
- if (!fs.existsSync(destDir)) {
- fs.mkdirSync(destDir, { recursive: true });
- }
-
- // If file exists and is non-zero, skip (or implement hash check if you want SOTA robustness)
- if (fs.existsSync(destPath) && fs.statSync(destPath).size > 0) {
- return;
- }
-
- if (parentPort) {
- parentPort.postMessage({
- type: "progress",
- progress: { status: "downloading", file: filename },
- });
- }
-
- try {
- const res = await fetch(url);
- if (!res.ok) {
- throw new Error(`HTTP ${res.status}: ${res.statusText}`);
- }
- const buffer = await res.arrayBuffer();
- fs.writeFileSync(destPath, Buffer.from(buffer));
- if (parentPort) {
- parentPort.postMessage({
- type: "progress",
- progress: { status: "downloaded", file: filename },
- });
- }
- } catch (e) {
- const errorMsg = e instanceof Error ? e.message : String(e);
- console.warn(`ā ļø Failed to download ${filename} from ${url}:`, errorMsg);
- // Don't crash, just warn. The math worker has a fallback (empty set).
- // But report the failure so setup can retry
- if (parentPort) {
- parentPort.postMessage({
- type: "warning",
- file: filename,
- error: errorMsg,
- });
- }
- }
-}
-
-async function download() {
- try {
- // 1. Download Dense Model
- const embedPipeline = await downloadModelWithTimeout(MODEL_IDS.embed, "q4");
- await embedPipeline.dispose();
-
- // 2. Download ColBERT Model
- const colbertPipeline = await downloadModelWithTimeout(
- MODEL_IDS.colbert,
- "int8",
- );
- await colbertPipeline.dispose();
-
- // 3. Download the custom Skiplist
- await downloadExtraFile(MODEL_IDS.colbert, "skiplist.json");
-
- if (parentPort) {
- parentPort.postMessage({ status: "success" });
- } else {
- process.exit(0);
- }
- } catch (error) {
- console.error("Worker failed to download models:", error);
- if (parentPort) {
- const errorMsg = error instanceof Error ? error.message : String(error);
- parentPort.postMessage({ status: "error", error: errorMsg });
- } else {
- process.exit(1);
- }
- }
-}
-
-download();
diff --git a/src/lib/workers/embeddings/colbert.ts b/src/lib/workers/embeddings/colbert.ts
deleted file mode 100644
index 0982db18..00000000
--- a/src/lib/workers/embeddings/colbert.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import * as ort from "onnxruntime-node";
-import { MODEL_IDS, PATHS } from "../../../config";
-import { ColBERTTokenizer } from "../colbert-tokenizer";
-
-const CACHE_DIR = PATHS.models;
-const ONNX_THREADS = 1;
-const LOG_MODELS =
- process.env.OSGREP_DEBUG_MODELS === "1" ||
- process.env.OSGREP_DEBUG_MODELS === "true";
-const log = (...args: unknown[]) => {
- if (LOG_MODELS) console.log(...args);
-};
-
-export type HybridResult = {
- dense: Float32Array;
- colbert: Int8Array;
- scale: number;
- pooled_colbert_48d?: Float32Array;
- token_ids?: number[];
-};
-
-export class ColbertModel {
- private session: ort.InferenceSession | null = null;
- public tokenizer: ColBERTTokenizer | null = null;
-
- async load() {
- if (this.session && this.tokenizer) return;
-
- this.tokenizer = new ColBERTTokenizer();
-
- const basePath = path.join(CACHE_DIR, MODEL_IDS.colbert);
- const onnxDir = path.join(basePath, "onnx");
-
- const modelPath = path.join(onnxDir, "model_int8.onnx");
- if (!fs.existsSync(modelPath)) {
- throw new Error(`ColBERT ONNX model not found at ${modelPath}`);
- }
-
- await this.tokenizer.init(basePath);
-
- const sessionOptions: ort.InferenceSession.SessionOptions = {
- executionProviders: ["cpu"],
- intraOpNumThreads: ONNX_THREADS,
- interOpNumThreads: 1,
- graphOptimizationLevel: "all",
- };
-
- log(`Worker: Loading ColBERT ONNX session from ${modelPath}`);
- this.session = await ort.InferenceSession.create(modelPath, sessionOptions);
-
- if (!this.session) {
- throw new Error(`ColBERT ONNX load failed; tried ${modelPath}`);
- }
- }
-
- isReady(): boolean {
- return !!(this.session && this.tokenizer);
- }
-
- async runBatch(
- texts: string[],
- denseVectors: Float32Array[],
- vectorDimensions: number,
- ): Promise {
- if (!this.session || !this.tokenizer) return [];
- const tokenizer = this.tokenizer;
- const session = this.session;
-
- const encodedBatch = await Promise.all(
- texts.map((t) => tokenizer.encodeDoc(t)),
- );
-
- const maxLen = Math.max(...encodedBatch.map((e) => e.input_ids.length));
- const batchInputIds = new BigInt64Array(texts.length * maxLen);
- const batchAttentionMask = new BigInt64Array(texts.length * maxLen);
- const padId = BigInt(50283);
-
- for (let i = 0; i < encodedBatch.length; i++) {
- const encoded = encodedBatch[i];
- const offset = i * maxLen;
- for (let j = 0; j < maxLen; j++) {
- if (j < encoded.input_ids.length) {
- batchInputIds[offset + j] = encoded.input_ids[j];
- batchAttentionMask[offset + j] = encoded.attention_mask[j];
- } else {
- batchInputIds[offset + j] = padId;
- batchAttentionMask[offset + j] = BigInt(0);
- }
- }
- }
-
- const feeds = {
- input_ids: new ort.Tensor("int64", batchInputIds, [texts.length, maxLen]),
- attention_mask: new ort.Tensor("int64", batchAttentionMask, [
- texts.length,
- maxLen,
- ]),
- };
-
- const sessionOut = await session.run(feeds);
- const outputName = session.outputNames[0];
- const output = sessionOut[outputName];
- if (!output) {
- throw new Error("ColBERT session output missing embeddings tensor");
- }
-
- const data = output.data as Float32Array;
- const [batch, seq, dim] = output.dims as number[];
- const results: HybridResult[] = [];
-
- for (let b = 0; b < batch; b++) {
- const batchOffset = b * seq * dim;
- const originalLen = encodedBatch[b].input_ids.length;
- const normalized = new Float32Array(originalLen * dim);
- let maxVal = 0;
-
- for (let s = 0; s < originalLen; s++) {
- const offset = batchOffset + s * dim;
- let sumSq = 0;
- for (let d = 0; d < dim; d++) {
- const val = data[offset + d];
- sumSq += val * val;
- }
- const norm = Math.sqrt(sumSq) || 1;
-
- for (let d = 0; d < dim; d++) {
- const val = data[offset + d] / norm;
- const idx = s * dim + d;
- normalized[idx] = val;
- if (Math.abs(val) > maxVal) maxVal = Math.abs(val);
- }
- }
-
- if (maxVal === 0) maxVal = 1;
-
- const int8Array = new Int8Array(normalized.length);
- for (let i = 0; i < normalized.length; i++) {
- int8Array[i] = Math.max(
- -127,
- Math.min(127, Math.round((normalized[i] / maxVal) * 127)),
- );
- }
-
- const pooled = new Float32Array(dim);
- const tokenCount = Math.max(1, originalLen);
- for (let s = 0; s < originalLen; s++) {
- const tokenOffset = s * dim;
- for (let d = 0; d < dim; d++) {
- pooled[d] += normalized[tokenOffset + d];
- }
- }
- let pooledNorm = 0;
- for (let d = 0; d < dim; d++) {
- pooled[d] /= tokenCount;
- pooledNorm += pooled[d] * pooled[d];
- }
- pooledNorm = Math.sqrt(pooledNorm) || 1;
- for (let d = 0; d < dim; d++) {
- pooled[d] /= pooledNorm;
- }
-
- results.push({
- dense: denseVectors[b] ?? new Float32Array(vectorDimensions).fill(0),
- colbert: int8Array,
- scale: maxVal,
- pooled_colbert_48d: pooled,
- token_ids: Array.from(encodedBatch[b].input_ids, (v) => Number(v)),
- });
- }
-
- return results;
- }
-
- async encodeQuery(text: string): Promise<{
- input_ids: BigInt64Array;
- attention_mask: BigInt64Array;
- }> {
- if (!this.tokenizer) throw new Error("ColBERT tokenizer not initialized");
- const encoded = await this.tokenizer.encodeQuery(text);
- return {
- input_ids: new BigInt64Array(encoded.input_ids),
- attention_mask: new BigInt64Array(encoded.attention_mask),
- };
- }
-
- async runSession(
- feeds: Record,
- ): Promise {
- if (!this.session) throw new Error("ColBERT session not initialized");
- return this.session.run(feeds);
- }
-
- getOutputName(): string {
- if (!this.session) throw new Error("ColBERT session not initialized");
- return this.session.outputNames[0];
- }
-}
diff --git a/src/lib/workers/embeddings/granite.ts b/src/lib/workers/embeddings/granite.ts
deleted file mode 100644
index b468061f..00000000
--- a/src/lib/workers/embeddings/granite.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import {
- AutoTokenizer,
- type PreTrainedTokenizer,
-} from "@huggingface/transformers";
-import * as ort from "onnxruntime-node";
-import { CONFIG, MODEL_IDS, PATHS } from "../../../config";
-
-const CACHE_DIR = PATHS.models;
-const ONNX_THREADS = 1;
-const LOG_MODELS =
- process.env.OSGREP_DEBUG_MODELS === "1" ||
- process.env.OSGREP_DEBUG_MODELS === "true";
-const log = (...args: unknown[]) => {
- if (LOG_MODELS) console.log(...args);
-};
-
-export class GraniteModel {
- private session: ort.InferenceSession | null = null;
- private tokenizer: PreTrainedTokenizer | null = null;
- private readonly vectorDimensions = CONFIG.VECTOR_DIM;
-
- private resolvePaths(): { modelPath: string; tokenizerPath: string } {
- const basePath = path.join(CACHE_DIR, MODEL_IDS.embed);
- const onnxDir = path.join(basePath, "onnx");
- const candidates = ["model_q4.onnx", "model.onnx"];
-
- for (const candidate of candidates) {
- const candidatePath = path.join(onnxDir, candidate);
- if (fs.existsSync(candidatePath)) {
- return { modelPath: candidatePath, tokenizerPath: basePath };
- }
- }
-
- throw new Error(
- `Granite ONNX model not found. Looked for ${candidates.join(
- ", ",
- )} in ${onnxDir}`,
- );
- }
-
- async load() {
- if (this.session && this.tokenizer) return;
-
- const { modelPath, tokenizerPath } = this.resolvePaths();
- log(`Worker: Loading Granite ONNX session from ${modelPath}`);
-
- this.tokenizer = await AutoTokenizer.from_pretrained(tokenizerPath);
-
- const sessionOptions: ort.InferenceSession.SessionOptions = {
- executionProviders: ["cpu"],
- intraOpNumThreads: ONNX_THREADS,
- interOpNumThreads: 1,
- graphOptimizationLevel: "all",
- };
- this.session = await ort.InferenceSession.create(modelPath, sessionOptions);
- }
-
- isReady(): boolean {
- return !!(this.session && this.tokenizer);
- }
-
- private meanPool(
- hidden: Float32Array,
- attention: BigInt64Array,
- batch: number,
- seq: number,
- hiddenDim: number,
- targetDim: number,
- ): Float32Array[] {
- const vectors: Float32Array[] = [];
- const seqFromMask = attention.length / Math.max(1, batch);
- const usableSeq = Math.min(seq, seqFromMask);
- const dim = Math.min(hiddenDim, targetDim);
-
- for (let b = 0; b < batch; b++) {
- const sum = new Float32Array(dim);
- let count = 0;
- const attOffset = b * seqFromMask;
- const hiddenOffset = b * seq * hiddenDim;
-
- for (let s = 0; s < usableSeq; s++) {
- if (attention[attOffset + s] > 0) {
- count++;
- const tokenOffset = hiddenOffset + s * hiddenDim;
- for (let d = 0; d < dim; d++) {
- sum[d] += hidden[tokenOffset + d];
- }
- }
- }
-
- if (count === 0) count = 1;
- let norm = 0;
- for (let d = 0; d < dim; d++) {
- sum[d] /= count;
- norm += sum[d] * sum[d];
- }
- norm = Math.sqrt(norm) || 1;
- for (let d = 0; d < dim; d++) {
- sum[d] /= norm;
- }
-
- if (dim < targetDim) {
- const padded = new Float32Array(targetDim);
- padded.set(sum);
- vectors.push(padded);
- } else {
- vectors.push(sum);
- }
- }
-
- return vectors;
- }
-
- async runBatch(texts: string[]): Promise {
- if (!this.session || !this.tokenizer) return [];
-
- const encoded = await this.tokenizer(texts, {
- padding: true,
- truncation: true,
- max_length: 256,
- });
-
- type EncodedTensor = { data: BigInt64Array; dims?: number[] };
- const inputTensor = encoded.input_ids as unknown as EncodedTensor;
- const attentionTensor = encoded.attention_mask as unknown as EncodedTensor;
- const inputIds = inputTensor.data;
- const attentionMask = attentionTensor.data;
- const seqLen =
- inputTensor.dims?.[1] ??
- Math.max(1, Math.floor(inputIds.length / texts.length));
-
- const tokenTypeIdsRaw = (
- encoded as Partial<{ token_type_ids: EncodedTensor }>
- ).token_type_ids;
- const tokenTypeIds =
- tokenTypeIdsRaw &&
- tokenTypeIdsRaw.data.length === inputIds.length &&
- tokenTypeIdsRaw.data.length === attentionMask.length
- ? tokenTypeIdsRaw.data
- : new BigInt64Array(inputIds.length).fill(BigInt(0));
-
- const feeds = {
- input_ids: new ort.Tensor("int64", inputIds, [texts.length, seqLen]),
- attention_mask: new ort.Tensor("int64", attentionMask, [
- texts.length,
- seqLen,
- ]),
- token_type_ids: new ort.Tensor("int64", tokenTypeIds, [
- texts.length,
- seqLen,
- ]),
- };
-
- const sessionOut = await this.session.run(feeds);
- const hidden =
- sessionOut.last_hidden_state ?? sessionOut[this.session.outputNames[0]];
-
- if (!hidden) {
- throw new Error("Granite ONNX output missing last_hidden_state");
- }
-
- const hiddenData = hidden.data as Float32Array;
- const [batch, seq, dim] = hidden.dims as number[];
- return this.meanPool(
- hiddenData,
- attentionMask,
- batch,
- seq,
- dim,
- this.vectorDimensions,
- );
- }
-}
diff --git a/src/lib/workers/orchestrator.ts b/src/lib/workers/orchestrator.ts
index 63a366dd..6c8ec841 100644
--- a/src/lib/workers/orchestrator.ts
+++ b/src/lib/workers/orchestrator.ts
@@ -1,16 +1,24 @@
-import * as fs from "node:fs";
+/**
+ * Orchestrator: Coordinates file processing and embedding
+ *
+ * This module handles:
+ * - File reading and validation
+ * - Chunking via tree-sitter
+ * - Embedding via native Rust code
+ *
+ * No worker pool needed - Rust ONNX Runtime is fast and stable.
+ */
+
+import * as crypto from "node:crypto";
import * as path from "node:path";
-import { env } from "@huggingface/transformers";
-import * as ort from "onnxruntime-node";
-import { v4 as uuidv4 } from "uuid";
-import { CONFIG, PATHS } from "../../config";
+import { CONFIG } from "../../config";
import {
buildAnchorChunk,
type ChunkWithContext,
formatChunkText,
TreeSitterChunker,
} from "../index/chunker";
-import { Skeletonizer } from "../skeleton";
+import { embedBatch, initNative, encodeQueryColbert, rerankColbert } from "../native";
import type { PreparedChunk, VectorRecord } from "../store/types";
import {
computeBufferHash,
@@ -18,9 +26,10 @@ import {
isIndexableFile,
readFileSnapshot,
} from "../utils/file-utils";
-import { maxSim } from "./colbert-math";
-import { ColbertModel, type HybridResult } from "./embeddings/colbert";
-import { GraniteModel } from "./embeddings/granite";
+
+// =============================================================================
+// Types
+// =============================================================================
export type ProcessFileInput = {
path: string;
@@ -37,52 +46,26 @@ export type ProcessFileResult = {
export type RerankDoc = {
colbert: Buffer | Int8Array | number[];
- scale: number;
token_ids?: number[];
};
-const CACHE_DIR = PATHS.models;
-const LOG_MODELS =
- process.env.OSGREP_DEBUG_MODELS === "1" ||
- process.env.OSGREP_DEBUG_MODELS === "true";
-const log = (...args: unknown[]) => {
- if (LOG_MODELS) console.log(...args);
-};
-
-env.cacheDir = CACHE_DIR;
-env.allowLocalModels = true;
-env.allowRemoteModels = true;
+// =============================================================================
+// Orchestrator
+// =============================================================================
const PROJECT_ROOT = process.env.OSGREP_PROJECT_ROOT
? path.resolve(process.env.OSGREP_PROJECT_ROOT)
: process.cwd();
-const LOCAL_MODELS = path.join(PROJECT_ROOT, "models");
-if (fs.existsSync(LOCAL_MODELS)) {
- env.localModelPath = LOCAL_MODELS;
- log(`Worker: Using local models from ${LOCAL_MODELS}`);
-}
export class WorkerOrchestrator {
- private granite = new GraniteModel();
- private colbert = new ColbertModel();
private chunker = new TreeSitterChunker();
- private skeletonizer = new Skeletonizer();
private initPromise: Promise | null = null;
- private readonly vectorDimensions = CONFIG.VECTOR_DIM;
private async ensureReady() {
- if (this.granite.isReady() && this.colbert.isReady()) {
- return;
- }
if (this.initPromise) return this.initPromise;
this.initPromise = (async () => {
- await Promise.all([
- this.chunker.init(),
- this.skeletonizer.init(),
- this.granite.load(),
- this.colbert.load(),
- ]);
+ await Promise.all([this.chunker.init(), initNative()]);
})().finally(() => {
this.initPromise = null;
});
@@ -90,46 +73,14 @@ export class WorkerOrchestrator {
return this.initPromise;
}
- private async computeHybrid(
- texts: string[],
- onProgress?: () => void,
- ): Promise {
- if (!texts.length) return [];
- await this.ensureReady();
-
- const results: HybridResult[] = [];
- const envBatch = Number.parseInt(
- process.env.OSGREP_WORKER_BATCH_SIZE ?? "",
- 10,
- );
- const BATCH_SIZE =
- Number.isFinite(envBatch) && envBatch > 0
- ? Math.max(4, Math.min(16, envBatch))
- : 16;
- for (let i = 0; i < texts.length; i += BATCH_SIZE) {
- if (i > 0) onProgress?.();
- const batchTexts = texts.slice(i, i + BATCH_SIZE);
- const denseBatch = await this.granite.runBatch(batchTexts);
- const colbertBatch = await this.colbert.runBatch(
- batchTexts,
- denseBatch,
- this.vectorDimensions,
- );
- results.push(...colbertBatch);
- }
- onProgress?.();
-
- return results;
- }
-
private async chunkFile(
pathname: string,
- content: string,
+ content: string
): Promise {
await this.ensureReady();
const { chunks: parsedChunks, metadata } = await this.chunker.chunk(
pathname,
- content,
+ content
);
const anchorChunk = buildAnchorChunk(pathname, content, metadata);
@@ -159,12 +110,11 @@ export class WorkerOrchestrator {
}
private toPreparedChunks(
- path: string,
+ filePath: string,
hash: string,
- chunks: ChunkWithContext[],
- skeleton?: string,
+ chunks: ChunkWithContext[]
): PreparedChunk[] {
- const texts = chunks.map((chunk) => formatChunkText(chunk, path));
+ const texts = chunks.map((chunk) => formatChunkText(chunk, filePath));
const prepared: PreparedChunk[] = [];
for (let i = 0; i < texts.length; i++) {
@@ -174,11 +124,11 @@ export class WorkerOrchestrator {
const next = texts[i + 1]?.displayText;
prepared.push({
- id: uuidv4(),
- path,
+ id: crypto.randomUUID(),
+ path: filePath,
hash,
- content: content, // Now minimal
- display_text: displayText, // Now rich
+ content,
+ display_text: displayText,
context_prev: typeof prev === "string" ? prev : undefined,
context_next: typeof next === "string" ? next : undefined,
start_line: chunk.startLine,
@@ -192,7 +142,6 @@ export class WorkerOrchestrator {
referenced_symbols: chunk.referencedSymbols,
role: chunk.role,
parent_symbol: chunk.parentSymbol,
- file_skeleton: chunk.isAnchor ? skeleton : undefined,
});
}
@@ -201,7 +150,7 @@ export class WorkerOrchestrator {
async processFile(
input: ProcessFileInput,
- onProgress?: () => void,
+ onProgress?: () => void
): Promise {
const absolutePath = path.isAbsolute(input.path)
? input.path
@@ -225,49 +174,26 @@ export class WorkerOrchestrator {
onProgress?.();
const content = buffer.toString("utf-8");
- const chunksPromise = this.chunkFile(input.path, content);
-
- // Generate skeleton in parallel
- const skeletonPromise = this.skeletonizer.skeletonizeFile(
- input.path,
- content,
- {
- includeSummary: true,
- },
- );
-
- const [chunks, skeletonResult] = await Promise.all([
- chunksPromise,
- skeletonPromise,
- ]);
+ const chunks = await this.chunkFile(input.path, content);
onProgress?.();
if (!chunks.length) return { vectors: [], hash, mtimeMs, size };
- const preparedChunks = this.toPreparedChunks(
- input.path,
- hash,
- chunks,
- skeletonResult.success ? skeletonResult.skeleton : undefined,
- );
- const hybrids = await this.computeHybrid(
- preparedChunks.map((chunk) => chunk.content),
- onProgress,
+ const preparedChunks = this.toPreparedChunks(input.path, hash, chunks);
+
+ // Embed all chunks via native Rust
+ const hybrids = await embedBatch(
+ preparedChunks.map((chunk) => chunk.content)
);
+ onProgress?.();
- const vectors = preparedChunks.map((chunk, idx) => {
- const hybrid = hybrids[idx] ?? {
- dense: new Float32Array(),
- colbert: new Int8Array(),
- scale: 1,
- };
+ const vectors: VectorRecord[] = preparedChunks.map((chunk, idx) => {
+ const hybrid = hybrids[idx];
return {
...chunk,
vector: hybrid.dense,
colbert: Buffer.from(hybrid.colbert),
- colbert_scale: hybrid.scale,
- pooled_colbert_48d: hybrid.pooled_colbert_48d,
- doc_token_ids: hybrid.token_ids,
+ doc_token_ids: Array.from(hybrid.token_ids),
};
});
@@ -279,84 +205,32 @@ export class WorkerOrchestrator {
dense: number[];
colbert: number[][];
colbertDim: number;
- pooled_colbert_48d?: number[];
}> {
await this.ensureReady();
- const [denseVector] = await this.granite.runBatch([text]);
+ // Get dense embedding
+ const hybrids = await embedBatch([text]);
+ const denseVector = hybrids[0].dense;
- const encoded = await this.colbert.encodeQuery(text);
-
- const feeds = {
- input_ids: new ort.Tensor("int64", encoded.input_ids, [
- 1,
- encoded.input_ids.length,
- ]),
- attention_mask: new ort.Tensor("int64", encoded.attention_mask, [
- 1,
- encoded.attention_mask.length,
- ]),
- };
-
- const sessionOut = await this.colbert.runSession(feeds);
- const outputName = this.colbert.getOutputName();
- const output = sessionOut[outputName];
- if (!output) {
- throw new Error("ColBERT session output missing embeddings tensor");
- }
-
- const data = output.data as Float32Array;
- const [, seq, dim] = output.dims as number[];
+ // Get ColBERT query embedding
+ const colbertFlat = await encodeQueryColbert(text);
+ const dim = CONFIG.COLBERT_DIM;
+ const seqLen = colbertFlat.length / dim;
+ // Reshape to matrix
const matrix: number[][] = [];
-
- for (let s = 0; s < seq; s++) {
- let sumSq = 0;
- const offset = s * dim;
- for (let d = 0; d < dim; d++) {
- const val = data[offset + d];
- sumSq += val * val;
- }
- const norm = Math.sqrt(sumSq);
-
+ for (let s = 0; s < seqLen; s++) {
const row: number[] = [];
- if (norm > 1e-9) {
- for (let d = 0; d < dim; d++) {
- row.push(data[offset + d] / norm);
- }
- } else {
- for (let d = 0; d < dim; d++) {
- row.push(data[offset + d]);
- }
- }
- matrix.push(row);
- }
-
- // Compute pooled embedding (mean of tokens)
- const pooled = new Float32Array(dim);
- for (const row of matrix) {
- for (let d = 0; d < dim; d++) {
- pooled[d] += row[d];
- }
- }
- // Normalize pooled
- let sumSq = 0;
- for (let d = 0; d < dim; d++) {
- pooled[d] /= matrix.length || 1;
- sumSq += pooled[d] * pooled[d];
- }
- const norm = Math.sqrt(sumSq);
- if (norm > 1e-9) {
for (let d = 0; d < dim; d++) {
- pooled[d] /= norm;
+ row.push(colbertFlat[s * dim + d]);
}
+ matrix.push(row);
}
return {
- dense: Array.from(denseVector ?? []),
+ dense: Array.from(denseVector),
colbert: matrix,
colbertDim: dim,
- pooled_colbert_48d: Array.from(pooled),
};
}
@@ -366,27 +240,37 @@ export class WorkerOrchestrator {
colbertDim: number;
}): Promise {
await this.ensureReady();
- const queryMatrix = input.query.map((row) =>
- row instanceof Float32Array ? row : new Float32Array(row),
- );
- return input.docs.map((doc) => {
+ // Flatten query matrix to match native `rerankColbert` signature
+ const queryEmbedding: number[] = [];
+ for (const row of input.query) {
+ for (let i = 0; i < row.length; i++) {
+ queryEmbedding.push(row[i] ?? 0);
+ }
+ }
+
+ const docLengths: number[] = [];
+ const docOffsets: number[] = [];
+ const candidateIndices: number[] = [];
+ const packedTokenChunks: Uint32Array[] = [];
+
+ // Pack all doc embeddings into a single buffer; offsets are element offsets
+ const packedChunks: Int8Array[] = [];
+ let totalElements = 0;
+ let totalTokenIds = 0;
+
+ for (let i = 0; i < input.docs.length; i++) {
+ const doc = input.docs[i];
const col = doc.colbert;
- let colbert: Int8Array;
+ let colbert: Int8Array;
if (col instanceof Int8Array) {
colbert = col;
} else if (Buffer.isBuffer(col)) {
colbert = new Int8Array(col.buffer, col.byteOffset, col.byteLength);
- } else if (
- col &&
- typeof col === "object" &&
- "type" in col &&
- (col as any).type === "Buffer" &&
- Array.isArray((col as any).data)
- ) {
- // IPC serialization fallback (still copies, but unavoidable without SharedArrayBuffer)
- colbert = new Int8Array((col as any).data);
+ } else if (ArrayBuffer.isView(col)) {
+ // Handles Uint8Array and other typed arrays (e.g. from LanceDB)
+ colbert = new Int8Array(col.buffer, col.byteOffset, col.byteLength);
} else if (Array.isArray(col)) {
colbert = new Int8Array(col);
} else {
@@ -394,20 +278,85 @@ export class WorkerOrchestrator {
}
const seqLen = Math.floor(colbert.length / input.colbertDim);
- const docMatrix: Float32Array[] = [];
- for (let i = 0; i < seqLen; i++) {
- const start = i * input.colbertDim;
- const row = new Float32Array(input.colbertDim);
- for (let d = 0; d < input.colbertDim; d++) {
- row[d] = (colbert[start + d] * doc.scale) / 127.0;
- }
- docMatrix.push(row);
- }
- const tokenIds =
- Array.isArray(doc.token_ids) && doc.token_ids.length === seqLen
- ? doc.token_ids
- : undefined;
- return maxSim(queryMatrix, docMatrix, tokenIds);
+ const used = colbert.subarray(0, seqLen * input.colbertDim);
+
+ const tokenIdsRaw = doc.token_ids ?? [];
+ const tokenIds = Uint32Array.from(
+ tokenIdsRaw.slice(0, seqLen).map((v) => (Number.isFinite(v) ? v : 0)),
+ );
+
+ docOffsets.push(totalElements);
+ docLengths.push(seqLen);
+ candidateIndices.push(i);
+ packedChunks.push(used);
+ packedTokenChunks.push(tokenIds);
+ totalElements += used.length;
+ totalTokenIds += tokenIds.length;
+ }
+
+ const packed = new Int8Array(totalElements);
+ let cursor = 0;
+ for (const chunk of packedChunks) {
+ packed.set(chunk, cursor);
+ cursor += chunk.length;
+ }
+
+ const packedTokenIds = new Uint32Array(totalTokenIds);
+ let tokenCursor = 0;
+ for (const chunk of packedTokenChunks) {
+ packedTokenIds.set(chunk, tokenCursor);
+ tokenCursor += chunk.length;
+ }
+
+ const result = await rerankColbert({
+ queryEmbedding: new Float32Array(queryEmbedding),
+ docEmbeddings: packed,
+ docTokenIds: packedTokenIds,
+ docLengths,
+ docOffsets,
+ candidateIndices,
+ topK: input.docs.length,
});
+
+ const scoreByIndex = new Map();
+ for (let i = 0; i < result.indices.length; i++) {
+ const idx = result.indices[i] ?? -1;
+ const score = result.scores[i] ?? 0;
+ if (typeof idx === "number") scoreByIndex.set(idx, score);
+ }
+
+ // Return scores aligned to input order (Searcher expects this)
+ return candidateIndices.map((i) => scoreByIndex.get(i) ?? 0);
+ }
+}
+
+// =============================================================================
+// Singleton for direct use (no worker pool needed)
+// =============================================================================
+
+let orchestrator: WorkerOrchestrator | null = null;
+
+export function getOrchestrator(): WorkerOrchestrator {
+ if (!orchestrator) {
+ orchestrator = new WorkerOrchestrator();
}
+ return orchestrator;
+}
+
+export async function processFile(
+ input: ProcessFileInput
+): Promise {
+ return getOrchestrator().processFile(input);
+}
+
+export async function encodeQuery(text: string) {
+ return getOrchestrator().encodeQuery(text);
+}
+
+export async function rerank(input: {
+ query: number[][];
+ docs: RerankDoc[];
+ colbertDim: number;
+}) {
+ return getOrchestrator().rerank(input);
}
diff --git a/src/lib/workers/pool.ts b/src/lib/workers/pool.ts
deleted file mode 100644
index 0216464b..00000000
--- a/src/lib/workers/pool.ts
+++ /dev/null
@@ -1,450 +0,0 @@
-/**
- * Architecture Note: We use a custom Child Process pool instead of Worker Threads
- * to ensure the ONNX Runtime segfaults do not crash the main process.
- */
-import * as childProcess from "node:child_process";
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { CONFIG, WORKER_TIMEOUT_MS } from "../../config";
-import type { ProcessFileInput, ProcessFileResult, RerankDoc } from "./worker";
-
-type TaskMethod = "processFile" | "encodeQuery" | "rerank";
-
-type EncodeQueryResult = Awaited<
- ReturnType
->;
-type RerankResult = Awaited>;
-
-type TaskPayloads = {
- processFile: ProcessFileInput;
- encodeQuery: { text: string };
- rerank: { query: number[][]; docs: RerankDoc[]; colbertDim: number };
-};
-
-type TaskResults = {
- processFile: ProcessFileResult;
- encodeQuery: EncodeQueryResult;
- rerank: RerankResult;
-};
-
-type WorkerMessage =
- | { id: number; result: TaskResults[TaskMethod] }
- | { id: number; error: string }
- | { id: number; heartbeat: true };
-
-function reviveBufferLike(input: unknown): Buffer | Int8Array | unknown {
- if (
- input &&
- typeof input === "object" &&
- "type" in (input as Record) &&
- (input as Record).type === "Buffer" &&
- Array.isArray((input as Record).data)
- ) {
- return Buffer.from((input as Record).data as number[]);
- }
- return input;
-}
-
-function reviveProcessFileResult(
- result: TaskResults["processFile"],
-): TaskResults["processFile"] {
- if (!result || !Array.isArray(result.vectors)) return result;
- const vectors = result.vectors.map((v) => {
- const revived = reviveBufferLike(v.colbert);
- return revived && (Buffer.isBuffer(revived) || revived instanceof Int8Array)
- ? { ...v, colbert: revived }
- : v;
- });
- return { ...result, vectors };
-}
-
-type PendingTask = {
- id: number;
- method: M;
- payload: TaskPayloads[M];
- resolve: (value: TaskResults[M]) => void;
- reject: (reason?: unknown) => void;
- worker?: ProcessWorker;
- timeout?: NodeJS.Timeout;
-};
-
-const TASK_TIMEOUT_MS = (() => {
- const fromEnv = Number.parseInt(
- process.env.OSGREP_WORKER_TASK_TIMEOUT_MS ?? "",
- 10,
- );
- if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv;
- return 120_000;
-})();
-
-const FORCE_KILL_GRACE_MS = 200;
-
-class ProcessWorker {
- child: childProcess.ChildProcess;
- busy = false;
- pendingTaskId: number | null = null;
-
- constructor(
- public modulePath: string,
- public execArgv: string[],
- ) {
- this.child = childProcess.fork(modulePath, {
- execArgv,
- env: { ...process.env },
- });
- }
-}
-
-function resolveProcessWorker(): { filename: string; execArgv: string[] } {
- const jsWorker = path.join(__dirname, "process-child.js");
- const tsWorker = path.join(__dirname, "process-child.ts");
-
- if (fs.existsSync(jsWorker)) {
- return { filename: jsWorker, execArgv: [] };
- }
-
- if (fs.existsSync(tsWorker)) {
- return { filename: tsWorker, execArgv: ["-r", "ts-node/register"] };
- }
-
- throw new Error("Process worker file not found");
-}
-
-export class WorkerPool {
- private workers: ProcessWorker[] = [];
- private taskQueue: number[] = [];
- private tasks = new Map>();
- private nextId = 1;
- private destroyed = false;
- private destroyPromise: Promise | null = null;
- private readonly modulePath: string;
- private readonly execArgv: string[];
-
- constructor() {
- const resolved = resolveProcessWorker();
- this.modulePath = resolved.filename;
- this.execArgv = resolved.execArgv;
-
- const workerCount = Math.max(1, CONFIG.WORKER_THREADS);
- for (let i = 0; i < workerCount; i++) {
- this.spawnWorker();
- }
- }
-
- private clearTaskTimeout(task: PendingTask) {
- if (task.timeout) {
- clearTimeout(task.timeout);
- task.timeout = undefined;
- }
- }
-
- private removeFromQueue(taskId: number) {
- const idx = this.taskQueue.indexOf(taskId);
- if (idx !== -1) this.taskQueue.splice(idx, 1);
- }
-
- private completeTask(
- task: PendingTask,
- worker: ProcessWorker | null,
- ) {
- this.clearTaskTimeout(task);
- this.tasks.delete(task.id);
- this.removeFromQueue(task.id);
-
- if (worker) {
- worker.busy = false;
- worker.pendingTaskId = null;
- }
- }
-
- private handleWorkerExit(
- worker: ProcessWorker,
- code: number | null,
- signal: NodeJS.Signals | null,
- ) {
- worker.busy = false;
- const failedTasks = Array.from(this.tasks.values()).filter(
- (t) => t.worker === worker,
- );
- for (const task of failedTasks) {
- this.clearTaskTimeout(task);
- task.reject(
- new Error(
- `Worker exited unexpectedly${code ? ` (code ${code})` : ""}${
- signal ? ` signal ${signal}` : ""
- }`,
- ),
- );
- this.completeTask(task, null);
- }
-
- this.workers = this.workers.filter((w) => w !== worker);
- if (!this.destroyed) {
- this.spawnWorker();
- this.dispatch();
- }
- }
-
- private spawnWorker() {
- const worker = new ProcessWorker(this.modulePath, this.execArgv);
-
- const onMessage = (msg: WorkerMessage) => {
- const task = this.tasks.get(msg.id);
- if (!task) return;
-
- if ("heartbeat" in msg) {
- // Reset timeout
- this.clearTaskTimeout(task);
- if (task.worker) {
- task.timeout = setTimeout(
- () => this.handleTaskTimeout(task, task.worker!),
- TASK_TIMEOUT_MS,
- );
- }
- return;
- }
-
- if ("error" in msg) {
- task.reject(new Error(msg.error));
- } else {
- let result = msg.result as TaskResults[TaskMethod];
- if (task.method === "processFile") {
- result = reviveProcessFileResult(
- result as TaskResults["processFile"],
- ) as TaskResults[TaskMethod];
- }
- task.resolve(result);
- }
-
- this.completeTask(task, worker);
- this.dispatch();
- };
-
- const onExit = (code: number | null, signal: NodeJS.Signals | null) =>
- this.handleWorkerExit(worker, code, signal);
-
- worker.child.on("message", onMessage);
- worker.child.on("exit", onExit);
- this.workers.push(worker);
- }
-
- private enqueue(
- method: M,
- payload: TaskPayloads[M],
- signal?: AbortSignal,
- ): Promise {
- if (this.destroyed) {
- return Promise.reject(new Error("Worker pool destroyed"));
- }
- if (signal?.aborted) {
- const err = new Error("Aborted");
- err.name = "AbortError";
- return Promise.reject(err);
- }
-
- const id = this.nextId++;
- return new Promise((resolve, reject) => {
- let settled = false;
- const safeResolve = (val: TaskResults[M]) => {
- if (!settled) {
- settled = true;
- resolve(val);
- }
- };
- const safeReject = (reason?: unknown) => {
- if (!settled) {
- settled = true;
- reject(reason);
- }
- };
-
- const task: PendingTask = {
- id,
- method,
- payload,
- resolve: safeResolve,
- reject: safeReject,
- };
-
- if (signal) {
- signal.addEventListener(
- "abort",
- () => {
- // If task is still in queue, remove it
- const idx = this.taskQueue.indexOf(id);
- if (idx !== -1) {
- this.taskQueue.splice(idx, 1);
- this.tasks.delete(id);
- const err = new Error("Aborted");
- err.name = "AbortError";
- safeReject(err);
- }
- // If task is already running (assigned to worker), we can't easily kill it without
- // killing the worker. For now, we just let it finish but reject the promise early so
- // the caller doesn't wait. The worker will eventually finish and we'll ignore the result.
- else if (this.tasks.has(id)) {
- // Task is running. Reject caller immediately.
- const err = new Error("Aborted");
- err.name = "AbortError";
- safeReject(err);
- // We intentionally do NOT delete the task map entry here,
- // because we need handleWorkerMessage to cleanly cleanup the worker state
- // when it eventually finishes.
- }
- },
- { once: true },
- );
- }
-
- this.tasks.set(id, task as unknown as PendingTask);
- this.taskQueue.push(id);
- this.dispatch();
- });
- }
-
- private handleTaskTimeout(
- task: PendingTask,
- worker: ProcessWorker,
- ) {
- if (this.destroyed || !this.tasks.has(task.id)) return;
-
- this.clearTaskTimeout(task);
- if (task.method !== "processFile") {
- console.warn(
- `[worker-pool] ${task.method} timed out after ${TASK_TIMEOUT_MS}ms; restarting worker.`,
- );
- }
- this.completeTask(task, null);
- task.reject(
- new Error(
- `Worker task ${task.method} timed out after ${TASK_TIMEOUT_MS}ms`,
- ),
- );
-
- worker.child.removeAllListeners("message");
- worker.child.removeAllListeners("exit");
- try {
- worker.child.kill("SIGKILL");
- } catch {}
-
- this.workers = this.workers.filter((w) => w !== worker);
- if (!this.destroyed) {
- this.spawnWorker();
- }
- this.dispatch();
- }
-
- private dispatch() {
- if (this.destroyed) return;
- const idle = this.workers.find((w) => !w.busy);
- const nextTaskId = this.taskQueue.find((id) => {
- const t = this.tasks.get(id);
- return t && !t.worker;
- });
-
- if (!idle || nextTaskId === undefined) return;
- const task = this.tasks.get(nextTaskId);
- if (!task) {
- this.removeFromQueue(nextTaskId);
- this.dispatch();
- return;
- }
-
- idle.busy = true;
- idle.pendingTaskId = task.id;
- task.worker = idle;
-
- task.timeout = setTimeout(
- () => this.handleTaskTimeout(task, idle),
- TASK_TIMEOUT_MS,
- );
-
- try {
- idle.child.send({
- id: task.id,
- method: task.method,
- payload: task.payload,
- });
- } catch (err) {
- this.clearTaskTimeout(task);
- this.completeTask(task, idle);
- task.reject(err);
- return;
- }
-
- this.dispatch();
- }
-
- processFile(input: ProcessFileInput) {
- // ProcessFile doesn't currently use cancellation, but we could add it later
- return this.enqueue("processFile", input);
- }
-
- encodeQuery(text: string, signal?: AbortSignal) {
- return this.enqueue("encodeQuery", { text }, signal);
- }
-
- rerank(input: TaskPayloads["rerank"], signal?: AbortSignal) {
- return this.enqueue("rerank", input, signal);
- }
-
- async destroy(): Promise {
- if (this.destroyPromise) return this.destroyPromise;
- if (this.destroyed) return;
-
- this.destroyed = true;
-
- for (const task of this.tasks.values()) {
- this.clearTaskTimeout(task);
- task.reject(new Error("Worker pool destroyed"));
- }
- this.tasks.clear();
- this.taskQueue = [];
-
- const killPromises = this.workers.map(
- (w) =>
- new Promise((resolve) => {
- w.child.removeAllListeners("message");
- w.child.removeAllListeners("exit");
- w.child.once("exit", () => resolve());
- w.child.kill("SIGTERM");
- const force = setTimeout(() => {
- try {
- w.child.kill("SIGKILL");
- } catch {}
- }, FORCE_KILL_GRACE_MS);
- setTimeout(() => {
- clearTimeout(force);
- resolve();
- }, WORKER_TIMEOUT_MS);
- }),
- );
-
- this.destroyPromise = Promise.allSettled(killPromises).then(() => {
- this.workers = [];
- this.destroyPromise = null;
- });
-
- await this.destroyPromise;
- }
-}
-
-let singleton: WorkerPool | null = null;
-
-export function getWorkerPool(): WorkerPool {
- if (!singleton) {
- singleton = new WorkerPool();
- }
- return singleton;
-}
-
-export async function destroyWorkerPool(): Promise {
- if (!singleton) return;
- const pool = singleton;
- singleton = null;
- await pool.destroy();
-}
-
-export function isWorkerPoolInitialized(): boolean {
- return singleton !== null;
-}
diff --git a/src/lib/workers/process-child.ts b/src/lib/workers/process-child.ts
deleted file mode 100644
index 25e5ccd1..00000000
--- a/src/lib/workers/process-child.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import process from "node:process";
-import processFile, {
- encodeQuery,
- type ProcessFileInput,
- type ProcessFileResult,
- type RerankDoc,
- rerank,
-} from "./worker";
-
-type IncomingMessage =
- | { id: number; method: "processFile"; payload: ProcessFileInput }
- | { id: number; method: "encodeQuery"; payload: { text: string } }
- | {
- id: number;
- method: "rerank";
- payload: { query: number[][]; docs: RerankDoc[]; colbertDim: number };
- };
-
-type OutgoingMessage =
- | { id: number; result: ProcessFileResult }
- | { id: number; result: Awaited> }
- | { id: number; result: Awaited> }
- | { id: number; error: string }
- | { id: number; heartbeat: true };
-
-const send = (msg: OutgoingMessage) => {
- if (process.send) {
- process.send(msg);
- }
-};
-
-process.on("message", async (msg: IncomingMessage) => {
- const { id, method, payload } = msg;
- try {
- if (method === "processFile") {
- const onProgress = () => {
- send({ id, heartbeat: true });
- };
- const result = await processFile(payload, onProgress);
- send({ id, result });
- return;
- }
- if (method === "encodeQuery") {
- const result = await encodeQuery(payload);
- send({ id, result });
- return;
- }
- if (method === "rerank") {
- const result = await rerank(payload);
- send({ id, result });
- return;
- }
- send({ id, error: `Unknown method: ${method}` });
- } catch (err) {
- const message = err instanceof Error ? err.message : String(err);
- send({ id, error: message });
- }
-});
-
-process.on("uncaughtException", (err) => {
- console.error("[process-worker] uncaughtException", err);
- process.exitCode = 1;
- process.exit();
-});
-
-process.on("unhandledRejection", (reason) => {
- console.error("[process-worker] unhandledRejection", reason);
- process.exitCode = 1;
- process.exit();
-});
diff --git a/src/lib/workers/worker.ts b/src/lib/workers/worker.ts
deleted file mode 100644
index 65f284de..00000000
--- a/src/lib/workers/worker.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import {
- type ProcessFileInput,
- type ProcessFileResult,
- type RerankDoc,
- WorkerOrchestrator,
-} from "./orchestrator";
-
-export type { ProcessFileInput, ProcessFileResult, RerankDoc };
-
-const orchestrator = new WorkerOrchestrator();
-
-export default async function processFile(
- input: ProcessFileInput,
- onProgress?: () => void,
-): Promise {
- return orchestrator.processFile(input, onProgress);
-}
-
-export async function encodeQuery(input: { text: string }) {
- return orchestrator.encodeQuery(input.text);
-}
-
-export async function rerank(input: {
- query: number[][];
- docs: RerankDoc[];
- colbertDim: number;
-}) {
- return orchestrator.rerank(input);
-}
diff --git a/test_skeleton.py b/test_skeleton.py
deleted file mode 100644
index 952cc70b..00000000
--- a/test_skeleton.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def hello():
- print("world")
diff --git a/tests/chunking.test.ts b/tests/chunking.test.ts
index a08812dc..d9e9ecb7 100644
--- a/tests/chunking.test.ts
+++ b/tests/chunking.test.ts
@@ -71,8 +71,7 @@ function example() {}`;
},
"/repo/path/file.ts",
);
- expect(displayText).toContain("// /repo/path/file.ts");
- expect(displayText).toContain("File: /repo/path/file.ts");
+ expect(displayText).toContain("// File: /repo/path/file.ts");
expect(displayText).toContain("code");
});
});
diff --git a/tests/expander.test.ts b/tests/expander.test.ts
new file mode 100644
index 00000000..ad91f56a
--- /dev/null
+++ b/tests/expander.test.ts
@@ -0,0 +1,524 @@
+import { describe, expect, it, vi, beforeEach } from "vitest";
+import type { ChunkType } from "../src/lib/store/types";
+
+// Mock VectorDB
+const mockQuery = vi.fn();
+const mockTable = {
+ query: vi.fn(() => ({
+ where: vi.fn(() => ({
+ limit: vi.fn(() => ({
+ toArray: mockQuery,
+ })),
+ })),
+ })),
+};
+
+const mockDb = {
+ ensureTable: vi.fn(async () => mockTable),
+};
+
+vi.mock("../src/lib/store/vector-db", () => ({
+ VectorDB: vi.fn(() => mockDb),
+}));
+
+import { Expander } from "../src/lib/search/expander";
+import type { ExpandOptions } from "../src/lib/search/expansion-types";
+
+describe("Expander", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockQuery.mockResolvedValue([]);
+ });
+
+ const createMockChunk = (overrides: Partial = {}): ChunkType => ({
+ type: "text",
+ text: "function test() { return 42; }",
+ score: 0.9,
+ metadata: {
+ path: "src/lib/utils.ts",
+ hash: "abc123",
+ },
+ generated_metadata: {
+ start_line: 10,
+ end_line: 15,
+ num_lines: 6,
+ },
+ defined_symbols: ["test"],
+ referenced_symbols: ["helper", "config"],
+ imports: [],
+ exports: [],
+ ...overrides,
+ });
+
+ describe("symbol resolution", () => {
+ it("resolves symbols to correct definitions", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Mock definition found for "helper" symbol
+ mockQuery.mockResolvedValueOnce([
+ {
+ id: "def-helper",
+ path: "src/lib/helper.ts",
+ content: "export function helper() {}",
+ display_text: "export function helper() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["helper"],
+ referenced_symbols: [],
+ is_anchor: false,
+ },
+ ]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ });
+
+ expect(expanded.expanded.length).toBeGreaterThan(0);
+ expect(expanded.expanded[0].relationship).toBe("symbols");
+ expect(expanded.expanded[0].via).toBe("helper");
+ expect(expanded.stats.symbolsResolved).toBeGreaterThan(0);
+ });
+
+ it("prefers same-directory definitions", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Return two definitions - one in same dir, one in different dir
+ mockQuery.mockResolvedValueOnce([
+ {
+ id: "def-distant",
+ path: "src/other/helper.ts",
+ content: "export function helper() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["helper"],
+ referenced_symbols: [],
+ },
+ {
+ id: "def-nearby",
+ path: "src/lib/helper.ts",
+ content: "export function helper() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["helper"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxExpanded: 1,
+ });
+
+ // Should prefer the same-directory definition
+ expect(expanded.expanded.length).toBe(1);
+ const expandedPath =
+ (expanded.expanded[0].chunk.metadata as any)?.path || "";
+ expect(expandedPath).toContain("src/lib");
+ });
+
+ it("respects maxExpanded limit", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Return many definitions for each symbol query
+ const manyDefs = Array.from({ length: 50 }, (_, i) => ({
+ id: `def-${i}`,
+ path: `src/file${i}.ts`,
+ content: `export function func${i}() {}`,
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: [`func${i}`],
+ referenced_symbols: [],
+ }));
+
+ // Mock returns subset of definitions for each query
+ mockQuery.mockImplementation(() => Promise.resolve(manyDefs.slice(0, 5)));
+
+ const results = [
+ createMockChunk({
+ referenced_symbols: manyDefs.map((_, i) => `func${i}`),
+ }),
+ ];
+
+ const maxExpanded = 5;
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxExpanded,
+ });
+
+ // Should respect the limit
+ expect(expanded.expanded.length).toBeLessThanOrEqual(maxExpanded);
+ });
+
+ it("handles circular references without infinite loop", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Chunk A refs B, query returns chunk that refs A
+ mockQuery.mockResolvedValueOnce([
+ {
+ id: "def-helper",
+ path: "src/lib/helper.ts",
+ content: "export function helper() { test(); }",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["helper"],
+ referenced_symbols: ["test"], // References back!
+ },
+ ]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxDepth: 2,
+ });
+
+ // Should complete without error and not include duplicates
+ const ids = new Set(
+ expanded.expanded.map(
+ (e) => (e.chunk.metadata as any)?.hash || e.chunk.text,
+ ),
+ );
+ expect(ids.size).toBe(expanded.expanded.length);
+ });
+
+ it("returns empty expansion gracefully when no definitions found", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // No definitions found
+ mockQuery.mockResolvedValue([]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ });
+
+ // Should return original results with empty expanded
+ expect(expanded.original).toEqual(results);
+ expect(expanded.expanded).toEqual([]);
+ expect(expanded.stats.symbolsResolved).toBe(0);
+ });
+ });
+
+ describe("caller expansion", () => {
+ it("finds callers that reference defined symbols", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Mock caller found
+ mockQuery.mockResolvedValueOnce([
+ {
+ id: "caller-1",
+ path: "src/routes/handler.ts",
+ content: "import { test } from '../lib/utils'; test();",
+ start_line: 5,
+ end_line: 10,
+ defined_symbols: ["handleRequest"],
+ referenced_symbols: ["test"],
+ is_anchor: false,
+ },
+ ]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["callers"],
+ });
+
+ expect(expanded.expanded.length).toBeGreaterThan(0);
+ expect(expanded.expanded[0].relationship).toBe("callers");
+ expect(expanded.expanded[0].via).toContain("uses");
+ expect(expanded.stats.callersFound).toBeGreaterThan(0);
+ });
+ });
+
+ describe("neighbor expansion", () => {
+ it("includes anchor chunks from same directory", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // First query is for symbols (returns nothing)
+ mockQuery.mockResolvedValueOnce([]);
+ // Second query for callers (returns nothing)
+ mockQuery.mockResolvedValueOnce([]);
+ // Third query for neighbors
+ mockQuery.mockResolvedValueOnce([
+ {
+ id: "neighbor-anchor",
+ path: "src/lib/other.ts",
+ content: "// File: src/lib/other.ts\nExports: helper",
+ start_line: 0,
+ end_line: 5,
+ defined_symbols: [],
+ referenced_symbols: [],
+ is_anchor: true,
+ },
+ ]);
+
+ const results = [createMockChunk()];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols", "callers", "neighbors"],
+ });
+
+ const neighbors = expanded.expanded.filter(
+ (e) => e.relationship === "neighbors",
+ );
+ expect(neighbors.length).toBeGreaterThanOrEqual(0);
+ if (neighbors.length > 0) {
+ expect(neighbors[0].via).toBe("same directory");
+ expect(expanded.stats.neighborsAdded).toBeGreaterThan(0);
+ }
+ });
+ });
+
+ describe("multi-hop expansion", () => {
+ it("follows chains of dependencies with depth > 1", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Depth 1: A -> B
+ mockQuery
+ .mockResolvedValueOnce([
+ {
+ id: "def-B",
+ path: "src/lib/b.ts",
+ content: "export function funcB() { funcC(); }",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["funcB"],
+ referenced_symbols: ["funcC"],
+ },
+ ])
+ // Depth 2: B -> C
+ .mockResolvedValueOnce([
+ {
+ id: "def-C",
+ path: "src/lib/c.ts",
+ content: "export function funcC() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["funcC"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [
+ createMockChunk({
+ referenced_symbols: ["funcB"],
+ }),
+ ];
+
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxDepth: 2,
+ });
+
+ // Should have found both B and C
+ expect(expanded.expanded.length).toBeGreaterThanOrEqual(1);
+ });
+
+ it("scores decay with depth", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Return definitions at different depths
+ mockQuery.mockResolvedValue([
+ {
+ id: "def-1",
+ path: "src/lib/dep.ts",
+ content: "export function dep() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["dep"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [createMockChunk({ referenced_symbols: ["dep"] })];
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxDepth: 1,
+ });
+
+ // All expanded chunks should have score < 1 (decayed from parent)
+ for (const node of expanded.expanded) {
+ expect(node.score).toBeLessThan(1.0);
+ }
+ });
+ });
+
+ describe("token budgeting", () => {
+ it("stops expanding when token budget exhausted", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // Return large chunks
+ const largeDefs = Array.from({ length: 10 }, (_, i) => ({
+ id: `def-${i}`,
+ path: `src/file${i}.ts`,
+ content: "x".repeat(1000), // ~250 tokens each
+ start_line: 1,
+ end_line: 100,
+ defined_symbols: [`func${i}`],
+ referenced_symbols: [],
+ }));
+
+ mockQuery.mockResolvedValue(largeDefs);
+
+ const results = [
+ createMockChunk({
+ referenced_symbols: largeDefs.map((_, i) => `func${i}`),
+ }),
+ ];
+
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxTokens: 500, // Should only fit 1-2 chunks
+ });
+
+ expect(expanded.stats.totalTokens).toBeLessThanOrEqual(500 + 100); // Some buffer
+ expect(expanded.stats.budgetRemaining).toBeDefined();
+ });
+
+ it("prioritizes high-value chunks within budget", async () => {
+ const expander = new Expander(mockDb as any);
+
+ mockQuery.mockResolvedValue([
+ {
+ id: "def-nearby",
+ path: "src/lib/nearby.ts",
+ content: "export function nearby() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["nearby"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [
+ createMockChunk({
+ referenced_symbols: ["nearby"],
+ }),
+ ];
+
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ maxTokens: 5000, // Generous budget
+ });
+
+ // Should include the nearby definition with high score
+ if (expanded.expanded.length > 0) {
+ expect(expanded.expanded[0].score).toBeGreaterThan(0);
+ }
+ });
+ });
+
+ describe("graceful degradation", () => {
+ it("returns original results when database unavailable", async () => {
+ const failingDb = {
+ ensureTable: vi.fn(async () => {
+ throw new Error("Database unavailable");
+ }),
+ };
+
+ const expander = new Expander(failingDb as any);
+ const results = [createMockChunk()];
+
+ const expanded = await expander.expand(results, "test query");
+
+ expect(expanded.original).toEqual(results);
+ expect(expanded.expanded).toEqual([]);
+ expect(expanded.truncated).toBe(false);
+ });
+
+ it("continues expansion when individual queries fail", async () => {
+ const expander = new Expander(mockDb as any);
+
+ // First query fails, second succeeds
+ mockQuery
+ .mockRejectedValueOnce(new Error("Query failed"))
+ .mockResolvedValueOnce([
+ {
+ id: "def-success",
+ path: "src/lib/success.ts",
+ content: "export function success() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["success"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [
+ createMockChunk({
+ referenced_symbols: ["failing", "success"],
+ }),
+ ];
+
+ const expanded = await expander.expand(results, "test query", {
+ strategies: ["symbols"],
+ });
+
+ // Should still have results from successful query
+ expect(expanded.expanded.length).toBeGreaterThanOrEqual(0);
+ });
+ });
+});
+
+describe("ExpandOptions", () => {
+ it("uses default options when none provided", async () => {
+ const expander = new Expander(mockDb as any);
+ mockQuery.mockResolvedValue([]);
+
+ const results = [
+ {
+ type: "text" as const,
+ text: "test",
+ score: 0.9,
+ metadata: { path: "test.ts", hash: "abc" },
+ generated_metadata: { start_line: 1, end_line: 5 },
+ defined_symbols: [],
+ referenced_symbols: [],
+ },
+ ];
+
+ const expanded = await expander.expand(results, "query");
+
+ // Should complete without error using defaults
+ expect(expanded.original).toEqual(results);
+ });
+
+ it("respects custom strategies", async () => {
+ const expander = new Expander(mockDb as any);
+
+ mockQuery.mockResolvedValue([
+ {
+ id: "def-1",
+ path: "src/lib/dep.ts",
+ content: "export function dep() {}",
+ start_line: 1,
+ end_line: 3,
+ defined_symbols: ["dep"],
+ referenced_symbols: [],
+ },
+ ]);
+
+ const results = [
+ {
+ type: "text" as const,
+ text: "function test() { dep(); }",
+ score: 0.9,
+ metadata: { path: "test.ts", hash: "abc" },
+ generated_metadata: { start_line: 1, end_line: 5 },
+ defined_symbols: ["test"],
+ referenced_symbols: ["dep"],
+ },
+ ];
+
+ // Only use symbols strategy
+ const expanded = await expander.expand(results, "query", {
+ strategies: ["symbols"],
+ });
+
+ // Should only have symbol expansions
+ for (const node of expanded.expanded) {
+ expect(node.relationship).toBe("symbols");
+ }
+ expect(expanded.stats.callersFound).toBe(0);
+ expect(expanded.stats.neighborsAdded).toBe(0);
+ });
+});
diff --git a/tests/git-worktree.test.ts b/tests/git-worktree.test.ts
new file mode 100644
index 00000000..3bab62fc
--- /dev/null
+++ b/tests/git-worktree.test.ts
@@ -0,0 +1,86 @@
+import * as fs from "node:fs";
+import * as os from "node:os";
+import * as path from "node:path";
+import { afterEach, describe, expect, it } from "vitest";
+import { getGitCommonDir, getMainRepoRoot, isWorktree } from "../src/lib/utils/git";
+
+function makeTempDir(prefix: string): string {
+ return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
+}
+
+function writeFile(filePath: string, content: string) {
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
+ fs.writeFileSync(filePath, content, "utf-8");
+}
+
+function makeWorktreeFixture() {
+ const root = makeTempDir("osgrep-worktree-fixture-");
+ const mainRepoRoot = path.join(root, "Documents", "GitHub", "repo");
+ const worktreeRoot = path.join(
+ root,
+ "conductor",
+ "workspaces",
+ "repo",
+ "feature-branch",
+ );
+ const mainGitDir = path.join(mainRepoRoot, ".git");
+ const worktreeGitDir = path.join(mainGitDir, "worktrees", "feature-branch");
+
+ fs.mkdirSync(mainGitDir, { recursive: true });
+ fs.mkdirSync(worktreeRoot, { recursive: true });
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
+ writeFile(path.join(worktreeGitDir, "commondir"), "../..");
+
+ return { root, mainRepoRoot, mainGitDir, worktreeRoot, worktreeGitDir };
+}
+
+describe("git worktree detection", () => {
+ const created: string[] = [];
+
+ afterEach(() => {
+ for (const dir of created.splice(0)) {
+ fs.rmSync(dir, { recursive: true, force: true });
+ }
+ });
+
+ it("does not treat a normal repo root as a worktree", () => {
+ const { root, mainRepoRoot } = makeWorktreeFixture();
+ created.push(root);
+
+ expect(isWorktree(mainRepoRoot)).toBe(false);
+ expect(getGitCommonDir(mainRepoRoot)).toBeNull();
+ expect(getMainRepoRoot(mainRepoRoot)).toBeNull();
+ });
+
+ it("detects a worktree via gitfile even when outside the main repo tree", () => {
+ const { root, mainRepoRoot, mainGitDir, worktreeRoot, worktreeGitDir } =
+ makeWorktreeFixture();
+ created.push(root);
+
+ writeFile(path.join(worktreeRoot, ".git"), `gitdir: ${worktreeGitDir}\n`);
+ const expectedMainGitDir = fs.realpathSync.native(mainGitDir);
+ const expectedMainRepoRoot = fs.realpathSync.native(mainRepoRoot);
+
+ expect(isWorktree(worktreeRoot)).toBe(true);
+ expect(getGitCommonDir(worktreeRoot)).toBe(expectedMainGitDir);
+ expect(getMainRepoRoot(worktreeRoot)).toBe(expectedMainRepoRoot);
+ });
+
+ if (process.platform === "win32") {
+ it.skip("detects a worktree when `.git` is a symlinked directory", () => {});
+ } else {
+ it("detects a worktree when `.git` is a symlinked directory", () => {
+ const { root, mainRepoRoot, mainGitDir, worktreeRoot, worktreeGitDir } =
+ makeWorktreeFixture();
+ created.push(root);
+
+ fs.symlinkSync(worktreeGitDir, path.join(worktreeRoot, ".git"));
+ const expectedMainGitDir = fs.realpathSync.native(mainGitDir);
+ const expectedMainRepoRoot = fs.realpathSync.native(mainRepoRoot);
+
+ expect(isWorktree(worktreeRoot)).toBe(true);
+ expect(getGitCommonDir(worktreeRoot)).toBe(expectedMainGitDir);
+ expect(getMainRepoRoot(worktreeRoot)).toBe(expectedMainRepoRoot);
+ });
+ }
+});