chore(FEEDBACK-0001): linting

This commit is contained in:
Giancarmine Salucci
2026-03-27 02:23:01 +01:00
parent 16436bfab2
commit 5a3c27224d
102 changed files with 5108 additions and 4976 deletions

View File

@@ -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` (010) 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)));
}
```