chore(FEEDBACK-0001): linting
This commit is contained in:
@@ -33,42 +33,37 @@ Implement the full-text search engine using SQLite's built-in FTS5 extension. Th
|
||||
// src/lib/server/search/search.service.ts
|
||||
|
||||
export interface SnippetSearchOptions {
|
||||
repositoryId: string;
|
||||
versionId?: string;
|
||||
type?: 'code' | 'info';
|
||||
limit?: number; // default: 20
|
||||
offset?: number; // default: 0
|
||||
repositoryId: string;
|
||||
versionId?: string;
|
||||
type?: 'code' | 'info';
|
||||
limit?: number; // default: 20
|
||||
offset?: number; // default: 0
|
||||
}
|
||||
|
||||
export interface SnippetSearchResult {
|
||||
snippet: Snippet;
|
||||
score: number; // BM25 rank (negative, lower = better)
|
||||
repository: Pick<Repository, 'id' | 'title'>;
|
||||
snippet: Snippet;
|
||||
score: number; // BM25 rank (negative, lower = better)
|
||||
repository: Pick<Repository, 'id' | 'title'>;
|
||||
}
|
||||
|
||||
export interface LibrarySearchOptions {
|
||||
libraryName: string;
|
||||
query?: string; // semantic relevance hint
|
||||
limit?: number; // default: 10
|
||||
libraryName: string;
|
||||
query?: string; // semantic relevance hint
|
||||
limit?: number; // default: 10
|
||||
}
|
||||
|
||||
export interface LibrarySearchResult {
|
||||
repository: Repository;
|
||||
versions: RepositoryVersion[];
|
||||
score: number; // composite relevance score
|
||||
repository: Repository;
|
||||
versions: RepositoryVersion[];
|
||||
score: number; // composite relevance score
|
||||
}
|
||||
|
||||
export class SearchService {
|
||||
constructor(private db: BetterSQLite3.Database) {}
|
||||
constructor(private db: BetterSQLite3.Database) {}
|
||||
|
||||
searchSnippets(
|
||||
query: string,
|
||||
options: SnippetSearchOptions
|
||||
): SnippetSearchResult[]
|
||||
searchSnippets(query: string, options: SnippetSearchOptions): SnippetSearchResult[];
|
||||
|
||||
searchRepositories(
|
||||
options: LibrarySearchOptions
|
||||
): LibrarySearchResult[]
|
||||
searchRepositories(options: LibrarySearchOptions): LibrarySearchResult[];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -101,21 +96,21 @@ The FTS5 MATCH query uses the porter stemmer and unicode61 tokenizer (configured
|
||||
|
||||
```typescript
|
||||
function preprocessQuery(raw: string): string {
|
||||
// 1. Trim and normalize whitespace
|
||||
let q = raw.trim().replace(/\s+/g, ' ');
|
||||
// 1. Trim and normalize whitespace
|
||||
let q = raw.trim().replace(/\s+/g, ' ');
|
||||
|
||||
// 2. Escape FTS5 special characters that aren't intended as operators
|
||||
// Keep: * (prefix), " " (phrase), AND, OR, NOT
|
||||
q = q.replace(/[()]/g, ' ');
|
||||
// 2. Escape FTS5 special characters that aren't intended as operators
|
||||
// Keep: * (prefix), " " (phrase), AND, OR, NOT
|
||||
q = q.replace(/[()]/g, ' ');
|
||||
|
||||
// 3. Add prefix wildcard to last token for "typing as you go" feel
|
||||
const tokens = q.split(' ');
|
||||
const lastToken = tokens.at(-1) ?? '';
|
||||
if (lastToken.length >= 3 && !lastToken.endsWith('*')) {
|
||||
tokens[tokens.length - 1] = lastToken + '*';
|
||||
}
|
||||
// 3. Add prefix wildcard to last token for "typing as you go" feel
|
||||
const tokens = q.split(' ');
|
||||
const lastToken = tokens.at(-1) ?? '';
|
||||
if (lastToken.length >= 3 && !lastToken.endsWith('*')) {
|
||||
tokens[tokens.length - 1] = lastToken + '*';
|
||||
}
|
||||
|
||||
return tokens.join(' ');
|
||||
return tokens.join(' ');
|
||||
}
|
||||
```
|
||||
|
||||
@@ -174,56 +169,65 @@ searchRepositories(options: LibrarySearchOptions): LibrarySearchResult[] {
|
||||
The search results must be formatted for the REST API and MCP tool responses:
|
||||
|
||||
### Library search response (for `resolve-library-id`):
|
||||
|
||||
```typescript
|
||||
function formatLibraryResults(results: LibrarySearchResult[]): string {
|
||||
if (results.length === 0) {
|
||||
return 'No libraries found matching your search.';
|
||||
}
|
||||
if (results.length === 0) {
|
||||
return 'No libraries found matching your search.';
|
||||
}
|
||||
|
||||
return results.map((r, i) => {
|
||||
const repo = r.repository;
|
||||
const versions = r.versions.map(v => v.tag).join(', ') || 'default branch';
|
||||
return [
|
||||
`${i + 1}. ${repo.title}`,
|
||||
` Library ID: ${repo.id}`,
|
||||
` Description: ${repo.description ?? 'No description'}`,
|
||||
` Snippets: ${repo.totalSnippets} | Trust Score: ${repo.trustScore.toFixed(1)}/10`,
|
||||
` Available Versions: ${versions}`,
|
||||
].join('\n');
|
||||
}).join('\n\n');
|
||||
return results
|
||||
.map((r, i) => {
|
||||
const repo = r.repository;
|
||||
const versions = r.versions.map((v) => v.tag).join(', ') || 'default branch';
|
||||
return [
|
||||
`${i + 1}. ${repo.title}`,
|
||||
` Library ID: ${repo.id}`,
|
||||
` Description: ${repo.description ?? 'No description'}`,
|
||||
` Snippets: ${repo.totalSnippets} | Trust Score: ${repo.trustScore.toFixed(1)}/10`,
|
||||
` Available Versions: ${versions}`
|
||||
].join('\n');
|
||||
})
|
||||
.join('\n\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Snippet search response (for `query-docs`):
|
||||
|
||||
```typescript
|
||||
function formatSnippetResults(
|
||||
results: SnippetSearchResult[],
|
||||
rules?: string[]
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
function formatSnippetResults(results: SnippetSearchResult[], rules?: string[]): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
// Prepend repository rules if present
|
||||
if (rules?.length) {
|
||||
parts.push('## Library Rules\n' + rules.map(r => `- ${r}`).join('\n'));
|
||||
}
|
||||
// Prepend repository rules if present
|
||||
if (rules?.length) {
|
||||
parts.push('## Library Rules\n' + rules.map((r) => `- ${r}`).join('\n'));
|
||||
}
|
||||
|
||||
for (const { snippet } of results) {
|
||||
if (snippet.type === 'code') {
|
||||
parts.push([
|
||||
snippet.title ? `### ${snippet.title}` : '',
|
||||
snippet.breadcrumb ? `*${snippet.breadcrumb}*` : '',
|
||||
`\`\`\`${snippet.language ?? ''}\n${snippet.content}\n\`\`\``,
|
||||
].filter(Boolean).join('\n'));
|
||||
} else {
|
||||
parts.push([
|
||||
snippet.title ? `### ${snippet.title}` : '',
|
||||
snippet.breadcrumb ? `*${snippet.breadcrumb}*` : '',
|
||||
snippet.content,
|
||||
].filter(Boolean).join('\n'));
|
||||
}
|
||||
}
|
||||
for (const { snippet } of results) {
|
||||
if (snippet.type === 'code') {
|
||||
parts.push(
|
||||
[
|
||||
snippet.title ? `### ${snippet.title}` : '',
|
||||
snippet.breadcrumb ? `*${snippet.breadcrumb}*` : '',
|
||||
`\`\`\`${snippet.language ?? ''}\n${snippet.content}\n\`\`\``
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
);
|
||||
} else {
|
||||
parts.push(
|
||||
[
|
||||
snippet.title ? `### ${snippet.title}` : '',
|
||||
snippet.breadcrumb ? `*${snippet.breadcrumb}*` : '',
|
||||
snippet.content
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join('\n\n---\n\n');
|
||||
return parts.join('\n\n---\n\n');
|
||||
}
|
||||
```
|
||||
|
||||
@@ -235,26 +239,26 @@ Compute `trustScore` (0–10) when a repository is first indexed:
|
||||
|
||||
```typescript
|
||||
function computeTrustScore(repo: Repository): number {
|
||||
let score = 0;
|
||||
let score = 0;
|
||||
|
||||
// Stars (up to 4 points): log scale, 10k stars = 4 pts
|
||||
if (repo.stars) {
|
||||
score += Math.min(4, Math.log10(repo.stars + 1));
|
||||
}
|
||||
// Stars (up to 4 points): log scale, 10k stars = 4 pts
|
||||
if (repo.stars) {
|
||||
score += Math.min(4, Math.log10(repo.stars + 1));
|
||||
}
|
||||
|
||||
// Documentation coverage (up to 3 points)
|
||||
score += Math.min(3, repo.totalSnippets / 500);
|
||||
// Documentation coverage (up to 3 points)
|
||||
score += Math.min(3, repo.totalSnippets / 500);
|
||||
|
||||
// Source type (1 point for GitHub, 0 for local)
|
||||
if (repo.source === 'github') score += 1;
|
||||
// Source type (1 point for GitHub, 0 for local)
|
||||
if (repo.source === 'github') score += 1;
|
||||
|
||||
// Successful indexing (1 point)
|
||||
if (repo.state === 'indexed') score += 1;
|
||||
// Successful indexing (1 point)
|
||||
if (repo.state === 'indexed') score += 1;
|
||||
|
||||
// Has description (1 point)
|
||||
if (repo.description) score += 1;
|
||||
// Has description (1 point)
|
||||
if (repo.description) score += 1;
|
||||
|
||||
return Math.min(10, parseFloat(score.toFixed(1)));
|
||||
return Math.min(10, parseFloat(score.toFixed(1)));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user