Skip to content

feat(semantic): local hybrid semantic_search MCP tool (+ filters, docs, deps) - #89

Merged
Max Golovanov (maxgolov) merged 23 commits into
mainfrom
proto/semantic-search
Sep 15, 2026
Merged

Max Golovanov (maxgolov) merged 23 commits into
mainfrom
proto/semantic-search

Conversation

@maxgolov

@maxgolov Max Golovanov (maxgolov) commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a local, offline semantic_search MCP tool for advisories, plus the
review-tier / web-app filters it builds on, and a round of doc/dependency hygiene.
Everything runs on-device — no external search engine or service.

What's included

Feature — semantic_search (prototype)

  • Hybrid retrieval: local ONNX embeddings (@huggingface/transformers, MiniLM
    384-dim) + Okapi BM25, fused with Reciprocal Rank Fusion, then field-aware +
    temporal reranking (a period in the query — "SSRF in August 2026", "recent" —
    is parsed out and used to weight by publish-date proximity).
  • Exposed on both stdio and HTTP servers via createAdvisoryServer(), alongside
    list_advisories / get_advisory. Index loads lazily; if absent, the tool returns
    build instructions (non-fatal).
  • Post-filters: web_app_only, severity, ecosystem, cwes.
  • Code under src/semantic/; build CLI dist/semantic/build-index.js.

Datasource filters (used by the tool + REST)

  • type review tier (reviewed default / unreviewed / all, gated by
    ADVISORY_INCLUDE_UNREVIEWED) and web_app_only (curated web-app CWE set).

Tests

  • 82 unit tests (bm25 / temporal / document / store / hybrid) with per-file coverage
    thresholds. npm test scoped to test/unit; Azure integration suite now
    describe.skipIf(!AZURE_OPENAI_ENDPOINT) so it skips cleanly without creds.

Docs / hygiene

  • New root AGENTS.md (build/bootstrap/run/test contract).
  • README.md trimmed 428→~340 lines; low-level REST/MCP recipes → docs/http-api.md,
    orchestrator/rate-limit examples → docs/integration.md; stale counts/CI facts fixed.
  • Semantic design consolidated in docs/semantic-search-design.md +
    docs/semantic-index-distribution.md; src/semantic/README.md points to them.

Security / deps

  • overrides: { "sharp": ">=0.35.4" } clears two transitive high-severity advisories
    (GHSA-rgj7-g3m4-5g8c, GHSA-f88m-g3jw-g9cj) pulled via @huggingface/transformers;
    npm audit = 0.
  • Dependencies resolved to match main (ai 7.0.99, zod 4.6.5, @types/node 26.5.1,
    @ai-sdk/azure 4.0.70) — lockfile regenerated on public npm, no downgrade on merge.

Testing

  • npm run build clean; npx vitest run test/unit → 82/82.
  • Resolved dependency set validated by CI (build + unit tests on Node 22 / public npm).

Semantic-index build workflow (manual, maintainer-gated)

.github/workflows/semantic-index.yml is a manual (workflow_dispatch) build that
rebuilds the index and uploads it as a downloadable artifact (semantic-index,
14-day retention). It writes nothing to the repo (contents: read, no Release, no
branch-protection interaction, no clone bloat). An authorize job restricts triggering
to admins/maintainers — on top of the write access workflow_dispatch already
requires; optional hard enforcement via an Environment with required reviewers is
documented. The weekly schedule is deferred (not enabled yet);
docs/semantic-index-distribution.md describes how to turn it on and how to distribute
a scheduled build's output (Release asset or a git-lfs branch).

Notes

  • semantic_search is a prototype; scaling beyond the ~35k reviewed corpus to the full
    ~370k needs ANN (hnswlib) — noted in the design doc.

- Add parseDateFilter + filterByDateRange to LocalRepositoryDataSource: single day (YYYY-MM-DD) and inclusive range (YYYY-MM-DD..YYYY-MM-DD) with validation for malformed/reversed ranges and array inputs

- Replace naive '>=' comparison that only supported open-ended dates

- Improve list_advisories schema descriptions (date format, examples, defaults)

- Add 14 unit tests covering parse/filter edge cases

Cleanly re-applies the feature from #25 onto current main (post-#76); supersedes that stale branch.
)

list_advisories/search ecosystem filter used exact string match against OSV data, so only 'npm' matched; composer/pip/maven/rust/etc. silently returned 0.

- Add ECOSYSTEM_ALIASES (GitHub enum -> OSV name) + case-insensitive ecosystemMatches()

- Apply at both filter sites (listAdvisories + filterResults/search)

- Add unit tests for all 12 ecosystems; make e2e assertions real regression guards (drop vacuous length>0 guard, use ecosystemMatches instead of fragile [0]===name)

Fixes #78
cwes filter did options.cwes.includes(cwe.cwe_id) i.e. checked if bare input like '89' contains 'CWE-89' - always false. Documented bare-number form never matched.

- Add normalizeCwe() + cweFilterMatches(): accept bare (89) or prefixed (CWE-89), comma-separated and/or array, case-insensitive; match if any requested CWE is present

- Add unit tests (test/unit/cwe-filter.test.ts)

Fixes #80
The MCP tools read ADVISORY_API_BASE (hardcoded :18005) while the server binds ADVISORY_API_PORT, so a custom API port broke every tool with 'fetch failed', and two servers collided on 18005 (serving each other's data - the source of the E2E flakiness).

- Derive base URL from ADVISORY_API_HOST/PORT; keep ADVISORY_API_BASE as override

- CI E2E now runs on a non-default API port (18055) to guard against regressions

Fixes #81
Distinct from the test defaults (18005/18006) so the dev MCP server and the test suite can run simultaneously without colliding. Relies on the tool port fix in this PR.
…ral rerank)

Local-only, advisory-specific prototype on branch proto/semantic-search. No external engine/service.

- Local ONNX embeddings via @huggingface/transformers (MiniLM 384-dim), offline (reuse a cached model dir)

- Compact Okapi BM25 with identifier-preserving tokenizer

- RRF fusion + field-aware rerank (exact GHSA/CVE/package/CWE/phrase boosts)

- Temporal-aware rerank: parse a period from the query, run recall on the residual text, boost by publish-date proximity (in-window 1.0, exp decay half-life 45d)

- File-backed index (embeddings.bin/bm25.json/docs.json/meta.json), CLI build+query. Not wired into MCP tools yet.
Registers semantic_search in createAdvisoryServer (stdio + HTTP): hybrid local search with optional web_app_only/severity/ecosystem/cwes post-filters and temporal reranking. Returns build instructions if the index is absent.
…CI feasibility)

Covers what/why to redistribute, git-lfs channel, cross-platform/ABI portability (LE + model pinning), and GitHub Actions scheduling feasibility (full rebuild ~borderline in 30m; incremental = seconds).
…ribution, weekly refresh, sparse checkout

Covers the semantic_search tool + pipeline, empirical build/size numbers (35k in ~22m local; index ~96MB), git-lfs on a dedicated semantic-index branch, weekly scheduled refresh, and sparse-checkout/partial-clone recipes to avoid clone bloat.
- 82 unit tests across bm25, temporal, document, store (save/load round-trip incl. embeddings byte-exactness), and hybrid (RRF + field/temporal rerank via injected query embedding — no model needed)

- Add @vitest/coverage-v8 + test:coverage script; per-file coverage thresholds for the deterministic semantic modules

- Hygiene: store index dir resolved at runtime (indexDir()) not import time; hybridSearch accepts an injectable query embedding for tests; drop unused import
# Conflicts:
#	package-lock.json
#	package.json
#	src/datasources/local-repository.ts
#	src/tools/advisories.ts
- Add root AGENTS.md: build/bootstrap/run/test contract for agents (generic, no internal infra)

- CONTRIBUTING: Node 20+, branch from main (no dev branch), unit-test command

- README: correct advisory counts (~370K/~35K), CI Node matrix 20.x/22.x, main-only triggers, drop stale CI notes
- Move raw REST/MCP JSON-RPC recipes to docs/http-api.md and orchestrator/rate-limit examples to docs/integration.md; README 428->341 lines (M2)

- Scope 'npm test' to unit; add test:all; skip Azure integration suite via describe.skipIf when AZURE_OPENAI_ENDPOINT unset (M3)

- src/semantic/README.md: scope to how-to-run and link canonical docs/semantic-search-design.md; fix stale 'not wired in' note (M4)
@huggingface/transformers pins a vulnerable sharp ^0.34.1; add an overrides entry (sharp>=0.35.4) to resolve GHSA-rgj7-g3m4-5g8c (libheif) and GHSA-f88m-g3jw-g9cj (libvips). npm audit: 0 vulnerabilities. Rationale documented in src/semantic/embeddings.ts (our usage is text-only).
…dk/azure)

Lockfile regenerated on public npm (ai 7.0.99, zod 4.6.5, @types/node 26.5.1, @ai-sdk/azure 4.0.70), keeping sharp>=0.35.4 override + transformers/coverage-v8. Validated by CI build+unit tests; avoids downgrading main on merge. NOTE: this device's CFS proxy still lags these versions, so local npm ci needs the proxy to catch up; build/tests run on the already-installed modules.
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Dependency Review

The following issues were found:

  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 4 package(s) with unknown licenses.
  • ⚠️ 8 packages with OpenSSF Scorecard issues.

View full job summary

Adds .github/workflows/semantic-index.yml: weekly (cron 0 6 * * 1) + manual rebuild of the reviewed-tier index, published as the rolling 'semantic-index-latest' Release asset (~100 MB/week). Release (not LFS-to-main) because main is protected; keeps the blob off clones. Docs updated with consumer steps.
…edule/release yet)

workflow_dispatch only; authorize job restricts to admin/maintain; uploads .semantic-index as a downloadable artifact (contents: read, no repo write, no Release). Weekly schedule + distribution deferred and documented. Addresses: run on demand, download as artifact without releasing, and maintainer-only trigger.
@maxgolov
Max Golovanov (maxgolov) merged commit bc4a02f into main Sep 15, 2026
10 checks passed
@maxgolov
Max Golovanov (maxgolov) deleted the proto/semantic-search branch September 15, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants