feat: 增加 repo 许可识别(License 元数据/过滤/MCP搜索) - #254
Conversation
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>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRepository 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. ChangesRepository license support
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 liftInvalidate incremental vectors when
licensechanges.The new license payload and embedding text are only written when a repository is indexed. In incremental mode, Lines 513-526 compare only
last_editedandanalyzed_atagainstvector_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 winCover all supported no-license inputs in the sentinel test.
This test only uses
NOASSERTION, despite claiming coverage fornullandOther. Add those values, plus a mixed-casenoassertion, and assert that all normalize toNO_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
📒 Files selected for processing (20)
electron/mcpLocalServer.jsserver/src/db/schema.tsserver/src/mcp/provider.tsserver/src/mcp/repoSearch.tsserver/src/mcp/tools.tsserver/src/routes/repositories.tsserver/src/routes/sync.tssrc/components/RepositoryCard.tsxsrc/components/SearchBar.test.tsxsrc/components/SearchBar.tsxsrc/components/settings/DataManagementPanel.tsxsrc/services/aiService.tssrc/services/githubApi.tssrc/services/vectorSearchService.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/licenseFilter.test.tssrc/utils/licenseFilter.tssrc/utils/repoSearch.test.tssrc/utils/repoSearch.ts
#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>
There was a problem hiding this comment.
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 winAdd the missing
VALUESplaceholder forvector_indexed_license.The bulk upsert lists 28 columns and
stmt.run(...)supplies 28 positional values, but theVALUES (...)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 winExtract 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 whatindexAllReposactually 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 inlineunindexedCountpredicate with a call to the shared helper.src/components/settings/VectorSearchSettings.tsx#L445-L454: replace the inlineattemptedCountpredicate 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 winRequired
license: stringon vector metadata may not hold for pre-existing (format v2) vectors.Vectors indexed before this PR won't have a
licensekey 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, notlicense?: string) onVectorQueryResult.metadatacould receiveundefinedat 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
📒 Files selected for processing (15)
electron/mcpLocalServer.jsserver/src/db/schema.tsserver/src/mcp/repoSearch.tsserver/src/routes/repositories.tsserver/src/routes/sync.tssrc/components/SearchBar.tsxsrc/components/settings/VectorSearchSettings.tsxsrc/services/aiService.test.tssrc/services/aiService.tssrc/services/githubApi.tssrc/services/vectorSearchService.test.tssrc/services/vectorSearchService.tssrc/types/index.tssrc/utils/repoSearch.test.tssrc/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
(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>
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
electron/mcpLocalServer.jsserver/src/mcp/repoSearch.tssrc/services/aiService.test.tssrc/services/aiService.tssrc/services/vectorSearchService.tssrc/utils/licenseFilter.test.tssrc/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
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>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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
📒 Files selected for processing (23)
electron/mcpLocalServer.jsserver/src/db/schema.tsserver/src/mcp/provider.tsserver/src/mcp/repoSearch.tsserver/src/mcp/tools.tsserver/src/routes/repositories.tsserver/src/routes/sync.tssrc/components/RepositoryCard.tsxsrc/components/SearchBar.test.tsxsrc/components/SearchBar.tsxsrc/components/settings/DataManagementPanel.tsxsrc/components/settings/VectorSearchSettings.tsxsrc/services/aiService.test.tssrc/services/aiService.tssrc/services/githubApi.tssrc/services/vectorSearchService.test.tssrc/services/vectorSearchService.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/licenseFilter.test.tssrc/utils/licenseFilter.tssrc/utils/repoSearch.test.tssrc/utils/repoSearch.ts
| /** SPDX id 过滤;过滤面板可采用 `NO_LICENSE_SENTINEL` 表示「无/未声明 license」。 */ | ||
| licenses: string[]; // 新增:开源许可过滤 |
There was a problem hiding this comment.
🗄️ 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.
| 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); | ||
| }); |
There was a problem hiding this comment.
🎯 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.
- 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.
Summary
Closes #251 — 增加 repo 许可(license)识别,覆盖整条链路:GitHub API → DB → AI/embedding/搜索 → UI 过滤 → MCP server。
githubApi.ts/server routes/sync.ts用toLicenseSpdxId把 GitHub license 对象/字符串/null 统一为 SPDX id 字符串db/schema.ts给repositories加license列;导入兼容旧备份与 GitHub 对象形态Repository.license+SearchFilters.licensessrc/utils/licenseFilter.ts(normalizeLicense+NO_LICENSE_SENTINEL),SearchBar/过滤面板/repoSearch 接入 license 过滤项,文本搜索纳入 licenseRepositoryCard用Scale图标展示 SPDX idbuildEmbeddingText纳入License:行,EMBEDDING_FORMAT_VERSION递增到 3aiService.ts在 AI 上下文与本地回退检索中纳入 licensenormalizeLicense,新增licenses过滤参数、byLicense统计、projectedlicense字段审计修复(本次提交包含)
normalizeLicense的NOASSERTION比对改为大小写不敏感,避免历史备份/第三方源写入的小写变体(other/none/noassertion)无法归一到哨兵、导致统计分裂与过滤漏选。三处镜像同步,并补小写变体测试。Test plan
tsc --noEmit通过tsc --noEmit通过vitest run全量 129 tests 通过(含新增 licenseFilter / repoSearch license 用例)electron/mcpLocalServer.jsnode -c语法检查通过licenses参数过滤正确🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests