Skip to content

feat: 增加 repo 许可识别(License 元数据/过滤/MCP搜索) - #254

Merged
AmintaCCCP merged 7 commits into
mainfrom
feat/repo-license-251
Jul 30, 2026
Merged

feat: 增加 repo 许可识别(License 元数据/过滤/MCP搜索)#254
AmintaCCCP merged 7 commits into
mainfrom
feat/repo-license-251

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #251 — 增加 repo 许可(license)识别,覆盖整条链路:GitHub API → DB → AI/embedding/搜索 → UI 过滤 → MCP server。

  • 归一化githubApi.ts / server routes / sync.tstoLicenseSpdxId 把 GitHub license 对象/字符串/null 统一为 SPDX id 字符串
  • 存储db/schema.tsrepositorieslicense 列;导入兼容旧备份与 GitHub 对象形态
  • 类型Repository.license + SearchFilters.licenses
  • 过滤:新增 src/utils/licenseFilter.tsnormalizeLicense + NO_LICENSE_SENTINEL),SearchBar/过滤面板/repoSearch 接入 license 过滤项,文本搜索纳入 license
  • UIRepositoryCardScale 图标展示 SPDX id
  • 向量buildEmbeddingText 纳入 License: 行,EMBEDDING_FORMAT_VERSION 递增到 3
  • AIaiService.ts 在 AI 上下文与本地回退检索中纳入 license
  • MCP:服务端与 electron 本地版镜像 normalizeLicense,新增 licenses 过滤参数、byLicense 统计、projected license 字段

审计修复(本次提交包含)

normalizeLicenseNOASSERTION 比对改为大小写不敏感,避免历史备份/第三方源写入的小写变体(other/none/noassertion)无法归一到哨兵、导致统计分裂与过滤漏选。三处镜像同步,并补小写变体测试。

Test plan

  • 前端 tsc --noEmit 通过
  • 服务端 tsc --noEmit 通过
  • vitest run 全量 129 tests 通过(含新增 licenseFilter / repoSearch license 用例)
  • electron/mcpLocalServer.js node -c 语法检查通过
  • 手动验证:同步仓库后 RepositoryCard 显示 license 徽标、过滤面板 license chip 生效、MCP licenses 参数过滤正确

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added end-to-end repository license support, including “No license” handling, license badges, and license chips in advanced search.
    • Search and tool results now support optional license filtering, with stats showing a license-by-license breakdown.
  • Bug Fixes

    • Improved license normalization and persistence across editing and sync, including preserving existing vector indexing license fingerprints when appropriate.
    • AI and vector search relevance now accounts for repository license, with reindexing triggered on license changes.
  • Tests

    • Added/updated coverage for license normalization, filtering behavior, and license-change-triggered reindexing.

Closes #251 — recognize repo license (SPDX id) across the stack:
GitHub API → DB → AI/embedding/search → UI filter → MCP server.

- githubApi.ts / repositories.ts / sync.ts: normalize GitHub license
  object/string/null to SPDX id string via toLicenseSpdxId
- db/schema.ts: add `license` column to repositories
- types/index.ts: Repository.license + SearchFilters.licenses
- licenseFilter.ts: normalizeLicense (case-insensitive NOASSERTION
  collapse) + NO_LICENSE_SENTINEL for the "no license" facet
- repoSearch.ts / SearchBar.tsx / DataManagementPanel.tsx: license
  filter facet + basic text search includes license
- RepositoryCard.tsx: render Scale badge with SPDX id
- vectorSearchService.ts: embed License line + bump format v3
- aiService.ts: include license in AI context & local fallbacks
- mcp (server + electron): licenses filter param, byLicense stats,
  projected license field; normalizeLicense mirrored server-side

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Repository licenses are normalized from GitHub and sync data, stored in the database, exposed through APIs and MCP, searchable and filterable in the frontend, displayed on repository cards, and included in AI/vector search metadata.

Changes

Repository license support

Layer / File(s) Summary
License normalization and persistence
src/utils/licenseFilter.ts, src/types/index.ts, src/services/githubApi.ts, server/src/db/schema.ts, server/src/routes/*, server/src/routes/sync.ts
License inputs are normalized to SPDX-like IDs or __NO_LICENSE__, persisted in repository records, and returned through API and sync data.
License search and MCP filtering
src/utils/repoSearch.ts, server/src/mcp/*, electron/mcpLocalServer.js
Text search, license filters, MCP schemas, projected repository payloads, and license statistics support normalized license values.
Frontend license filtering and display
src/components/SearchBar.tsx, src/components/RepositoryCard.tsx, src/store/useAppStore.ts, src/components/settings/DataManagementPanel.tsx, src/components/SearchBar.test.tsx
Search state and advanced filters expose available licenses, repository cards display license badges, and reset flows clear license selections.
Semantic and vector license metadata
src/services/aiService.ts, src/services/vectorSearchService.ts, src/components/settings/VectorSearchSettings.tsx, src/utils/licenseFilter.test.ts, src/services/*test.ts
AI search, embeddings, vector metadata, and incremental indexing account for license values and license changes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubAPI
  participant SyncAPI
  participant RepositoryDB
  participant SearchService
  participant VectorIndexer
  GitHubAPI->>SyncAPI: repository license metadata
  SyncAPI->>RepositoryDB: normalized license value
  SearchService->>RepositoryDB: license-aware search and filters
  RepositoryDB-->>SearchService: repositories with license data
  VectorIndexer->>RepositoryDB: compare license fingerprint
  VectorIndexer->>RepositoryDB: store indexed license fingerprint
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds unrelated masked-secret preservation logic in sync upserts, which is outside license recognition scope. Split the masked-secret handling into a separate PR and keep this one focused on license metadata, filtering, and MCP search.
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: repo license recognition with metadata, filtering, and MCP search.
Linked Issues check ✅ Passed The code covers the linked feature by adding license metadata and normalization across MCP, AI, search, UI, and storage.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/repo-license-251

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/vectorSearchService.ts (1)

588-592: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Invalidate incremental vectors when license changes.

The new license payload and embedding text are only written when a repository is indexed. In incremental mode, Lines 513-526 compare only last_edited and analyzed_at against vector_indexed_at; a license-only GitHub sync can therefore be skipped, leaving both semantic embeddings and vector metadata stale. Persist a license/content fingerprint, or update a tracked content timestamp when the license changes, and include it in the skip predicate.

This follows the supplied incremental-indexing code and the PR objective that license data flows into vector metadata and embeddings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/vectorSearchService.ts` around lines 588 - 592, Update the
incremental indexing logic around the `last_edited`/`analyzed_at` comparison to
track a license/content fingerprint or updated content timestamp, persist it
with the vector metadata, and include it in the skip predicate. Ensure
license-only repository changes trigger re-indexing so both embedding text and
the `license` field in the metadata are refreshed.
🧹 Nitpick comments (1)
src/utils/repoSearch.test.ts (1)

103-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover all supported no-license inputs in the sentinel test.

This test only uses NOASSERTION, despite claiming coverage for null and Other. Add those values, plus a mixed-case noassertion, and assert that all normalize to NO_LICENSE_SENTINEL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/repoSearch.test.ts` around lines 103 - 107, Expand the no-license
sentinel test around normalizeLicense to cover null, Other, and mixed-case
noassertion in addition to NOASSERTION, asserting each normalizes to
NO_LICENSE_SENTINEL. Preserve the existing applyRepoFilters aggregation
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/mcpLocalServer.js`:
- Around line 278-282: Add the normalized byLicense count output to the
Electron-local gsm_stats response, matching the contract and values returned by
server/src/mcp/provider.ts while preserving the existing byLanguage statistics
and licenses schema.

In `@server/src/routes/repositories.ts`:
- Around line 180-181: Update the upsert logic around the license assignment and
its corresponding assignment near the alternate path to distinguish an omitted
license from an explicitly provided null. Preserve the existing stored license
when the payload omits the field, while allowing explicit null values to clear
it; avoid relying solely on toLicenseSpdxId(undefined), which produces null for
both cases.

In `@server/src/routes/sync.ts`:
- Around line 119-128: Update the license extraction in the sync route around
rawLicense and licenseValue to validate object candidates at runtime: accept
spdx_id or key only when each is a string, otherwise bind null, and apply the
canonical license normalization before SQLite binding. Preserve support for
legacy missing values, GitHub-shaped objects, and already-normalized SPDX
strings while ensuring licenseValue is always a string or null.

In `@src/components/SearchBar.tsx`:
- Around line 652-659: Update the search effect that invokes applyFilters to
include searchFilters.licenses in its dependency list. Ensure license changes
from handleLicenseToggle and clearFilters rerun the effect so displayed
repositories stay synchronized with the filter state.

In `@src/services/aiService.ts`:
- Around line 1319-1320: Update performEnhancedBasicSearch so its scoring loop
reads searchableFields.license and applies a deliberate license-match weight,
ensuring license terms affect ranking alongside other query terms. Add a
regression test covering a license query combined with other terms and verify
repositories matching the license are ordered appropriately.

In `@src/services/githubApi.ts`:
- Around line 319-327: Update the shared GitHub repository response mapping in
githubApi.ts so every method returning Repository objects converts license
through toLicenseSpdxId, not only the /user/starred path. Ensure raw license
objects, strings, and null values are normalized before any result reaches
normalizeLicense, while preserving the existing response fields and behavior.

In `@src/utils/repoSearch.ts`:
- Line 31: Use the normalized license vocabulary in all three text-search
corpora: update the license fields in src/utils/repoSearch.ts at line 31,
server/src/mcp/repoSearch.ts at line 82, and electron/mcpLocalServer.js at line
26 to append normalizeLicense(repo.license) rather than the raw license value,
reusing each file’s existing normalization symbol or import.

---

Outside diff comments:
In `@src/services/vectorSearchService.ts`:
- Around line 588-592: Update the incremental indexing logic around the
`last_edited`/`analyzed_at` comparison to track a license/content fingerprint or
updated content timestamp, persist it with the vector metadata, and include it
in the skip predicate. Ensure license-only repository changes trigger
re-indexing so both embedding text and the `license` field in the metadata are
refreshed.

---

Nitpick comments:
In `@src/utils/repoSearch.test.ts`:
- Around line 103-107: Expand the no-license sentinel test around
normalizeLicense to cover null, Other, and mixed-case noassertion in addition to
NOASSERTION, asserting each normalizes to NO_LICENSE_SENTINEL. Preserve the
existing applyRepoFilters aggregation assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 17ff908e-69f7-4eb0-bf88-a479e5fc0eaf

📥 Commits

Reviewing files that changed from the base of the PR and between 940cf48 and 88494e2.

📒 Files selected for processing (20)
  • electron/mcpLocalServer.js
  • server/src/db/schema.ts
  • server/src/mcp/provider.ts
  • server/src/mcp/repoSearch.ts
  • server/src/mcp/tools.ts
  • server/src/routes/repositories.ts
  • server/src/routes/sync.ts
  • src/components/RepositoryCard.tsx
  • src/components/SearchBar.test.tsx
  • src/components/SearchBar.tsx
  • src/components/settings/DataManagementPanel.tsx
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/services/vectorSearchService.ts
  • src/store/useAppStore.ts
  • src/types/index.ts
  • src/utils/licenseFilter.test.ts
  • src/utils/licenseFilter.ts
  • src/utils/repoSearch.test.ts
  • src/utils/repoSearch.ts

Comment thread electron/mcpLocalServer.js
Comment thread server/src/routes/repositories.ts Outdated
Comment thread server/src/routes/sync.ts Outdated
Comment thread src/components/SearchBar.tsx
Comment thread src/services/aiService.ts Outdated
Comment thread src/services/githubApi.ts
Comment thread src/utils/repoSearch.ts Outdated
#1 Electron-local MCP: track byLicense in gsm_stats, normalize via sentinel
#2 repositories.ts UPSERT: preserve stored license when payload omits the
    field (旧客户端/旧备份), only overwrite when explicitly provided (incl. null)
#3 sync.ts import: validate spdx_id/key are strings at runtime before bind,
    so malformed object values can't poison the import transaction
#4 SearchBar: add searchFilters.licenses to the search effect deps array
#5 aiService enhanced basic search: score license matches (+0.2) so a
    license-matching repo outranks a higher-star non-match on mixed queries
#6 githubApi: normalize license (toLicenseSpdxId) for watched-repo returns,
    consistent with /user/starred path
#7 Text-search corpora (repoSearch ×3: src/ + server MCP + electron local):
    use normalizeLicense instead of raw repo.license
#8 Vector incremental invalidation: add vector_indexed_license fingerprint
    column (schema + Repository type); skip predicate + UI mirrors reindex when
    license changes (normalizeLicense sentinel-stable); indexers stamp it
    alongside vector_indexed_at; sync/preserve semantics on bulk upsert
#9 Expand no-license sentinel tests to cover null/'Other'/'none'/'NOASSERTION'
    and mixed-case 'noassertion'

Tests: vitest 134 passed (adds aiService license-ranking regression + 3 vector
license-invalidation regressions). tsc clean for frontend and server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/routes/repositories.ts (1)

147-155: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add the missing VALUES placeholder for vector_indexed_license.

The bulk upsert lists 28 columns and stmt.run(...) supplies 28 positional values, but the VALUES (...) clause only contains 27 anonymous ? placeholders. Add the final placeholder so the parameter count matches the column/value count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/routes/repositories.ts` around lines 147 - 155, Add the missing
final positional placeholder in the INSERT statement’s VALUES clause used by the
bulk upsert, so it matches the 28 columns and the 28 arguments passed to
stmt.run(...), including vector_indexed_license.
🧹 Nitpick comments (2)
src/services/vectorSearchService.ts (2)

518-531: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the "needs reindex" predicate (content-time + license fingerprint) into a shared helper.

The same non-trivial predicate — !vector_indexed_at || contentTime > vector_indexed_at || normalizeLicense(vector_indexed_license) !== normalizeLicense(license) — is implemented three times across two files. It already changed once (to add the license fingerprint check) and had to be updated in all three places; a future change risks updating only one or two, silently desyncing the UI's displayed unindexed/attempted counts from what indexAllRepos actually reindexes.

  • src/services/vectorSearchService.ts#L518-L531: extract this block (the authoritative version, format-version-change handling aside) into an exported helper, e.g. needsReindex(repo, formatVersionChanged).
  • src/components/settings/VectorSearchSettings.tsx#L228-L240: replace the inline unindexedCount predicate with a call to the shared helper.
  • src/components/settings/VectorSearchSettings.tsx#L445-L454: replace the inline attemptedCount predicate with the same shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/vectorSearchService.ts` around lines 518 - 531, Extract the
shared content-time and normalized-license predicate from needs-reindex
filtering in src/services/vectorSearchService.ts lines 518-531 into an exported
helper such as needsReindex(repo, formatVersionChanged), retaining
format-version handling there. Update the unindexedCount predicate in
src/components/settings/VectorSearchSettings.tsx lines 228-240 and
attemptedCount predicate at lines 445-454 to call this helper instead of
duplicating the logic.

182-193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Required license: string on vector metadata may not hold for pre-existing (format v2) vectors.

Vectors indexed before this PR won't have a license key in their stored metadata at all; the format-version bump forces a full reindex, but only once the user actually triggers it. Until then, any code trusting the type (license: string, not license?: string) on VectorQueryResult.metadata could receive undefined at runtime for stale vectors. Consider making the field optional to reflect reality, or defaulting it defensively where consumed.

Also applies to: 196-206

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/vectorSearchService.ts` around lines 182 - 193, Update the
VectorizeVector metadata contract so license is optional, matching pre-existing
vectors that lack this key. Apply the same optional typing to the corresponding
VectorQueryResult metadata definition around the referenced section, and
preserve existing handling for vectors that include a license.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/src/routes/repositories.ts`:
- Around line 147-155: Add the missing final positional placeholder in the
INSERT statement’s VALUES clause used by the bulk upsert, so it matches the 28
columns and the 28 arguments passed to stmt.run(...), including
vector_indexed_license.

---

Nitpick comments:
In `@src/services/vectorSearchService.ts`:
- Around line 518-531: Extract the shared content-time and normalized-license
predicate from needs-reindex filtering in src/services/vectorSearchService.ts
lines 518-531 into an exported helper such as needsReindex(repo,
formatVersionChanged), retaining format-version handling there. Update the
unindexedCount predicate in src/components/settings/VectorSearchSettings.tsx
lines 228-240 and attemptedCount predicate at lines 445-454 to call this helper
instead of duplicating the logic.
- Around line 182-193: Update the VectorizeVector metadata contract so license
is optional, matching pre-existing vectors that lack this key. Apply the same
optional typing to the corresponding VectorQueryResult metadata definition
around the referenced section, and preserve existing handling for vectors that
include a license.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 75258956-aadb-4fee-841d-eaeca7b78d1a

📥 Commits

Reviewing files that changed from the base of the PR and between 88494e2 and 965c01e.

📒 Files selected for processing (15)
  • electron/mcpLocalServer.js
  • server/src/db/schema.ts
  • server/src/mcp/repoSearch.ts
  • server/src/routes/repositories.ts
  • server/src/routes/sync.ts
  • src/components/SearchBar.tsx
  • src/components/settings/VectorSearchSettings.tsx
  • src/services/aiService.test.ts
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/services/vectorSearchService.test.ts
  • src/services/vectorSearchService.ts
  • src/types/index.ts
  • src/utils/repoSearch.test.ts
  • src/utils/repoSearch.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/services/githubApi.ts
  • src/utils/repoSearch.test.ts
  • server/src/routes/sync.ts
  • electron/mcpLocalServer.js
  • src/utils/repoSearch.ts
  • src/services/aiService.ts
  • server/src/mcp/repoSearch.ts
  • src/components/SearchBar.tsx

AmintaCCCP and others added 2 commits July 29, 2026 22:59
(Outside-diff, 🔴 Critical) repositories.ts bulk upsert: add the missing 28th
    VALUES placeholder for vector_indexed_license. Adding the column without its
    '?' made the placeholder count (27) mismatch the 28 positional args bound by
    stmt.run, which would throw "Too few parameter values" on every upsert.

(Nitpick, 🔵 Trivial) vectorSearchService.ts: make license optional on
    VectorizeVector.metadata and VectorQueryResult.metadata. Pre-PR (embedding
    v2) vectors lack the license key in stored metadata until reindexed, so the
    typed `license: string` did not reflect runtime reality. Consumers already
    read metadata defensively (`r.metadata?.full_name || ''`), so this is a
    type-fidelity fix.

(Nitpick, 🔵 Trivial) Extract the needs-reindex predicate into an exported
    needsReindex(repo, formatVersionChanged) helper — the same content-time +
    normalized-license fingerprint check was duplicated across indexAllRepos
    and two UI mirrors (unindexedCount, attemptedCount). All three now call the
    single shared helper, preventing future desync between what the UI displays
    and what indexAllRepos actually reindexes. Added a 6-test suite covering the
    helper's branches including the null/NOASSERTION sentinel-equivalence case.

Tests: vitest 140 passed (adds 6 needsReindex tests). tsc clean for frontend
and server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g license

Root cause: normalizeLicense assumed its argument was a string and called
v.toLowerCase() unconditionally. A repo whose license never went through
toLicenseSpdxId can carry a raw GitHub license object { key, spdx_id, name,
url } — legacy persisted store, third-party backup import, or GitHub API returns
not yet normalized (only /user/starred and getWatchedRepositories are). On render,
performBasicTextSearch / performEnhancedBasicSearch / RepositoryCard invoke
normalizeLicense(repo.license), where the object had no .toLowerCase → crash.
aiService additionally did (repo.license || '').toLowerCase(), the same failure.

Fix:
- normalizeLicense(v: unknown) is now defensive across all 3 mirrors
  (src/utils/licenseFilter.ts, server/src/mcp/repoSearch.ts, electron/mcpLocalServer.js):
  GitHub-shaped objects resolve to spdx_id ?? key, then sentinel-normalize;
  any other non-string collapes to the no-license sentinel; never call
  .toLowerCase() on a non-string.
- aiService.performEnhancedBasicSearch/BasicSearch/static searchRepositories and
  vectorSearchService metadata construction route license through normalizeLicense
  instead of raw .toLowerCase() / `repo.license || ''`, so object licenses both
  stop crashing and stop polluting the searchable document / vector metadata with
  "[object Object]".

Regression coverage:
- licenseFilter.test.ts: object (spdx_id/key), object missing fields, number,
  boolean, sentinel-pass-through — all return sentinel or resolved string, no throw.
- aiService.test.ts: searchRepositoriesWithReranking with a raw GitHub license
  object resolves to 'MIT' and ranks — previously threw.

Tests: vitest 142 passed. tsc clean for frontend and server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils/licenseFilter.ts`:
- Around line 42-46: Update the license resolution logic around spdx, key, and
resolved so blank or whitespace-only spdx_id values are ignored and the first
non-empty trimmed field is selected, allowing key such as MIT to be used as
fallback. Preserve the existing NO_LICENSE_SENTINEL handling and
NOASSERTION_KEYS normalization behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed6759b6-1d36-4693-b92d-89fa7bde668c

📥 Commits

Reviewing files that changed from the base of the PR and between b0fad9e and 882d3b8.

📒 Files selected for processing (7)
  • electron/mcpLocalServer.js
  • server/src/mcp/repoSearch.ts
  • src/services/aiService.test.ts
  • src/services/aiService.ts
  • src/services/vectorSearchService.ts
  • src/utils/licenseFilter.test.ts
  • src/utils/licenseFilter.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/utils/licenseFilter.test.ts
  • src/services/aiService.test.ts
  • server/src/mcp/repoSearch.ts
  • electron/mcpLocalServer.js
  • src/services/vectorSearchService.ts
  • src/services/aiService.ts

Comment thread src/utils/licenseFilter.ts Outdated
CodeRabbit r3675689914: `{ spdx_id: '', key: 'MIT' }` resolved via
`spdx_id ?? key` to the empty string (?? does not skip blank), hit the
`!resolved` branch, and returned NO_LICENSE_SENTINEL — discarding a valid
license key. Switch all three normalizeLicense mirrors to trim-first + `||`
so the first non-empty field (spdx_id preferred, else key) is selected:
  - src/utils/licenseFilter.ts
  - server/src/mcp/repoSearch.ts
  - electron/mcpLocalServer.js

Regression: licenseFilter.test.ts covers blank/whitespace spdx_id → key
fallback, both-blank → sentinel, and blank-spdx_id + 'Other' key → sentinel.

Tests: vitest 143 passed. tsc clean for frontend and server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AmintaCCCP

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/routes/sync.ts`:
- Around line 111-113: Update the repository INSERT statement near
repoStmt.run() to include 28 VALUES placeholders matching the 28 listed columns
and arguments. Preserve the existing column and argument order, adding only the
missing placeholder.
- Around line 122-130: Update the license normalization logic in the sync route
so both licenseValue and vector_indexed_license use the same canonical
SPDX-or-null rule. Trim candidate strings, ignore whitespace-only values and
case-insensitive NOASSERTION, Other, and none, and for object licenses choose
the first valid trimmed value from spdx_id then key rather than a blank value.
Apply this shared normalization before persistence.

In `@src/services/githubApi.ts`:
- Around line 48-55: Update toLicenseSpdxId in src/services/githubApi.ts (lines
48-55) and the corresponding license handling in
server/src/routes/repositories.ts (lines 20-27) to accept only string spdx_id or
key values, trim them, and return null for missing or non-string values before
persisting or binding license data.

In `@src/services/vectorSearchService.ts`:
- Around line 366-367: Normalize repo.license with normalizeLicense before
adding license text, and append it only when the normalized value is not the
no-license sentinel in src/services/vectorSearchService.ts lines 366-367. Apply
the same normalized, non-sentinel license value to reranking metadata in
src/services/aiService.ts line 742.

In `@src/types/index.ts`:
- Around line 351-352: Normalize legacy search filters in the
DataManagementPanel import flow before storing them: when assigning
importedData.searchFilters, ensure the licenses field defaults to an empty array
via the existing imported filters value when it is missing. Preserve provided
licenses values and the remaining filter fields unchanged.

In `@src/utils/licenseFilter.test.ts`:
- Around line 37-48: Update normalizeLicense to trim scalar string inputs before
matching sentinel values, preserving existing object-field trimming behavior.
Add assertions covering whitespace-padded sentinel variants such as “ Other ”
and equivalent forms, ensuring they normalize to NO_LICENSE_SENTINEL
consistently.

In `@src/utils/licenseFilter.ts`:
- Around line 50-51: Update the direct string handling in the license
normalization logic to trim the value before lowercasing and checking
NOASSERTION_KEYS. Return NO_LICENSE_SENTINEL for trimmed empty or no-license
values, while preserving the trimmed license value for valid strings; keep the
non-string branch unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 543a6808-7058-406c-8950-42dde2022b11

📥 Commits

Reviewing files that changed from the base of the PR and between 940cf48 and 2a93dd7.

📒 Files selected for processing (23)
  • electron/mcpLocalServer.js
  • server/src/db/schema.ts
  • server/src/mcp/provider.ts
  • server/src/mcp/repoSearch.ts
  • server/src/mcp/tools.ts
  • server/src/routes/repositories.ts
  • server/src/routes/sync.ts
  • src/components/RepositoryCard.tsx
  • src/components/SearchBar.test.tsx
  • src/components/SearchBar.tsx
  • src/components/settings/DataManagementPanel.tsx
  • src/components/settings/VectorSearchSettings.tsx
  • src/services/aiService.test.ts
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/services/vectorSearchService.test.ts
  • src/services/vectorSearchService.ts
  • src/store/useAppStore.ts
  • src/types/index.ts
  • src/utils/licenseFilter.test.ts
  • src/utils/licenseFilter.ts
  • src/utils/repoSearch.test.ts
  • src/utils/repoSearch.ts

Comment thread server/src/routes/sync.ts
Comment thread server/src/routes/sync.ts Outdated
Comment thread src/services/githubApi.ts
Comment thread src/services/vectorSearchService.ts Outdated
Comment thread src/types/index.ts
Comment on lines +351 to +352
/** SPDX id 过滤;过滤面板可采用 `NO_LICENSE_SENTINEL` 表示「无/未声明 license」。 */
licenses: string[]; // 新增:开源许可过滤

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Default licenses when importing legacy search filters.

DataManagementPanel.tsx directly assigns importedData.searchFilters at Lines [672-674]. Older backups can omit the newly required licenses field, leaving runtime state inconsistent with this interface. Normalize imported filters with licenses: importedFilters.licenses ?? [] before storing them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/index.ts` around lines 351 - 352, Normalize legacy search filters
in the DataManagementPanel import flow before storing them: when assigning
importedData.searchFilters, ensure the licenses field defaults to an empty array
via the existing imported filters value when it is missing. Preserve provided
licenses values and the remaining filter fields unchanged.

Comment on lines +37 to +48
it('collapses GitHub "no assertion" forms to the sentinel', () => {
expect(normalizeLicense('NOASSERTION')).toBe(NO_LICENSE_SENTINEL);
expect(normalizeLicense('Other')).toBe(NO_LICENSE_SENTINEL);
expect(normalizeLicense('NONE')).toBe(NO_LICENSE_SENTINEL);
expect(normalizeLicense('no-license')).toBe(NO_LICENSE_SENTINEL);
});

it('collapses lowercase variants (legacy backup / third-party sources)', () => {
expect(normalizeLicense('noassertion')).toBe(NO_LICENSE_SENTINEL);
expect(normalizeLicense('other')).toBe(NO_LICENSE_SENTINEL);
expect(normalizeLicense('none')).toBe(NO_LICENSE_SENTINEL);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Trim scalar license values before sentinel matching.

normalizeLicense(' Other ') remains a distinct license instead of the no-license sentinel, while object fields are trimmed. This splits legacy/imported values across filters, stats, and vector fingerprints. Trim string inputs and add whitespace assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/licenseFilter.test.ts` around lines 37 - 48, Update
normalizeLicense to trim scalar string inputs before matching sentinel values,
preserving existing object-field trimming behavior. Add assertions covering
whitespace-padded sentinel variants such as “ Other ” and equivalent forms,
ensuring they normalize to NO_LICENSE_SENTINEL consistently.

Comment thread src/utils/licenseFilter.ts Outdated
- sync.ts: fix INSERT VALUES placeholder count (28 cols / 28 ?)
- sync import: canonicalize license + vector_indexed_license to SPDX-or-null
- toLicenseSpdxId: trim and accept only string spdx_id/key fields
- embedding/rerank: normalizeLicense before writing License text
- DataManagementPanel: default licenses to [] on legacy import
- normalizeLicense: trim scalar strings before sentinel matching
- tests: cover whitespace-padded sentinel variants
Reduce the language/stars/license meta row from text-sm (14px) to
text-xs (12px), shrink Star/Scale icons to w-3.5/h-3.5, color dot to
w-2.5/h-2.5, and add min-w-0/flex-shrink-0 + truncate caps so the
license SPDX id no longer overflows the card container when present.
@AmintaCCCP
AmintaCCCP merged commit 41b4b76 into main Jul 30, 2026
5 checks passed
@AmintaCCCP
AmintaCCCP deleted the feat/repo-license-251 branch July 30, 2026 11:39
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.

[Feature] 增加repo许可识别

1 participant