chore(LINT-0001) fix lint errors

This commit is contained in:
Giancarmine Salucci
2026-03-27 03:01:37 +01:00
parent 7f7d806172
commit da661efc91
24 changed files with 114 additions and 69 deletions

View File

@@ -86,10 +86,9 @@
</div>
{#if open}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
role="presentation"
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4"
class="fixed inset-0 z-60 flex items-center justify-center bg-black/50 p-4"
onclick={handleBackdropClick}
>
<div

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { resolve as resolveRoute } from '$app/paths';
import type { Repository } from '$lib/types';
let {
@@ -76,7 +77,7 @@
{repo.state === 'indexing' ? 'Indexing...' : 'Re-index'}
</button>
<a
href="/repos/{encodeURIComponent(repo.id)}"
href={resolveRoute('/repos/[id]', { id: repo.id })}
class="rounded-lg border border-gray-200 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
>
Details

View File

@@ -13,8 +13,6 @@
snippet.type === 'code' ? (snippet.codeList[0]?.code ?? '') : snippet.text
);
const language = $derived(snippet.type === 'code' ? (snippet.codeList[0]?.language ?? '') : null);
const tokenCount = $derived(snippet.tokenCount ?? 0);
</script>

View File

@@ -299,7 +299,6 @@ describe('LocalCrawler.crawl() — lock file and minified file exclusions', () =
'src/index.ts': 'export {};',
'dist/vendor.min.js': '!function(e,t){}()'
});
const result = await crawlRoot();
// dist/ is pruned by default — test via shouldIndexFile logic only if .gitignore present
// Use a custom path outside ignored dirs:
await fs.rm(root, { recursive: true, force: true });

View File

@@ -266,7 +266,7 @@ describe('Migration — embedding_profiles', () => {
const { client } = createTestDb();
const row = client
.prepare("SELECT * FROM embedding_profiles WHERE id = 'local-default'")
.get() as any;
.get() as Record<string, unknown>;
expect(row).toBeDefined();
expect(row.is_default).toBe(1);
expect(row.provider_kind).toBe('local-transformers');

View File

@@ -9,6 +9,19 @@
import { EmbeddingError, type EmbeddingProvider, type EmbeddingVector } from './provider.js';
type LocalEmbeddingResult = {
data: ArrayLike<number>;
};
type LocalEmbeddingPipeline = (
text: string,
options: Record<string, unknown>
) => Promise<LocalEmbeddingResult>;
type TransformersModule = {
pipeline: (task: string, model: string) => Promise<LocalEmbeddingPipeline>;
};
export class LocalEmbeddingProvider implements EmbeddingProvider {
readonly name = 'local';
readonly model = 'Xenova/all-MiniLM-L6-v2';
@@ -20,13 +33,11 @@ export class LocalEmbeddingProvider implements EmbeddingProvider {
async embed(texts: string[]): Promise<EmbeddingVector[]> {
if (!this.pipeline) {
let transformers: { pipeline: Function };
let transformers: TransformersModule;
try {
// Dynamic import — only succeeds when @xenova/transformers is installed.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transformers = (await import('@xenova/transformers' as any)) as {
pipeline: Function;
};
transformers = (await import('@xenova/transformers' as any)) as TransformersModule;
} catch {
throw new EmbeddingError(
'@xenova/transformers is not installed. Install it to use the local embedding provider.'

View File

@@ -37,7 +37,8 @@ export class NoopEmbeddingProvider implements EmbeddingProvider {
readonly dimensions = 0;
readonly model = 'none';
async embed(_texts: string[]): Promise<EmbeddingVector[]> {
async embed(texts: string[]): Promise<EmbeddingVector[]> {
void texts;
return [];
}

View File

@@ -14,7 +14,7 @@ import type { EmbeddingProfile } from '../db/schema.js';
export type ProviderFactory = (config: Record<string, unknown>) => EmbeddingProvider;
const PROVIDER_REGISTRY: Record<string, ProviderFactory> = {
'local-transformers': (_config) => new LocalEmbeddingProvider(),
'local-transformers': () => new LocalEmbeddingProvider(),
'openai-compatible': (config) =>
new OpenAIEmbeddingProvider({
baseUrl: config.baseUrl as string,

View File

@@ -124,7 +124,7 @@ export class CodeListItemDto {
}
export class CodeSnippetJsonDto {
type: 'code' = 'code';
readonly type = 'code' as const;
title: string | null;
description: string | null;
language: string | null;
@@ -147,7 +147,7 @@ export class CodeSnippetJsonDto {
}
export class InfoSnippetJsonDto {
type: 'info' = 'info';
readonly type = 'info' as const;
text: string;
breadcrumb: string | null;
pageId: string;

View File

@@ -32,9 +32,9 @@ export const BOUNDARY_PATTERNS: Record<string, RegExp> = {
python: /^(async\s+)?(def|class)\s+\w+/,
go: /^(func|type|var|const)\s+\w+/,
rust: /^(pub(\s*\(crate\))?\s+)?(async\s+)?(fn|impl|struct|enum|trait|type|const|static)\s+\w+/,
java: /^(\s*(public|private|protected|static|final|abstract|synchronized)\s+)+[\w<>\[\]]+\s+\w+\s*[({]/,
java: /^(\s*(public|private|protected|static|final|abstract|synchronized)\s+)+[\w<>[\]]+\s+\w+\s*[({]/,
csharp:
/^(\s*(public|private|protected|internal|static|override|virtual|abstract|sealed)\s+)+[\w<>\[\]]+\s+\w+\s*[({]/,
/^(\s*(public|private|protected|internal|static|override|virtual|abstract|sealed)\s+)+[\w<>[\]]+\s+\w+\s*[({]/,
kotlin:
/^(\s*(public|private|protected|internal|override|suspend|inline|open|abstract|sealed)\s+)*(fun|class|object|interface|data class|sealed class|enum class)\s+\w+/,
swift:
@@ -111,7 +111,7 @@ function slidingWindowChunks(content: string, filePath: string, language: string
* followed by colon/equals/brace) and treat each as a boundary.
*/
function parseConfigFile(content: string, filePath: string, language: string): RawSnippet[] {
const topLevelKey = /^[\w"'\-]+\s*[:=\[{]/;
const topLevelKey = /^[\w"'-]+\s*[:=[{]/;
const lines = content.split('\n');
const segments: string[] = [];
let current: string[] = [];

View File

@@ -13,7 +13,7 @@
* - With embeddings : crawl+parse = 80 %, embeddings = 20 %
*/
import { createHash } from 'node:crypto';
import { createHash, randomUUID } from 'node:crypto';
import type Database from 'better-sqlite3';
import type { Document, NewDocument, NewSnippet } from '$lib/types';
import type { crawl as GithubCrawlFn } from '$lib/server/crawler/github.crawler.js';
@@ -92,7 +92,7 @@ export class IndexingPipeline {
this.updateRepo(repo.id, { state: 'indexing' });
// ---- Stage 1: Crawl -------------------------------------------------
const crawlResult = await this.crawl(repo, normJob);
const crawlResult = await this.crawl(repo);
const totalFiles = crawlResult.totalFiles;
this.updateJob(job.id, { totalFiles });
@@ -140,7 +140,7 @@ export class IndexingPipeline {
const checksum = file.sha || sha256(file.content);
// Create new document record.
const documentId = crypto.randomUUID();
const documentId = randomUUID();
const now = new Date();
const newDoc: NewDocument = {
id: documentId,
@@ -247,10 +247,7 @@ export class IndexingPipeline {
// Private — crawl
// -------------------------------------------------------------------------
private async crawl(
repo: Repository,
job: IndexingJob
): Promise<{
private async crawl(repo: Repository): Promise<{
files: Array<{ path: string; content: string; sha: string; size: number; language: string }>;
totalFiles: number;
}> {

View File

@@ -153,7 +153,7 @@ function makeNoopProvider(): EmbeddingProvider {
name: 'noop',
dimensions: 0,
model: 'none',
async embed(_texts: string[]): Promise<EmbeddingVector[]> {
async embed(): Promise<EmbeddingVector[]> {
return [];
},
async isAvailable(): Promise<boolean> {
@@ -738,7 +738,7 @@ describe('HybridSearchService', () => {
const repoId = seedRepo(client);
const docId = seedDocument(client, repoId);
const snippetId = seedSnippet(client, {
seedSnippet(client, {
repositoryId: repoId,
documentId: docId,
content: 'keyword only test'
@@ -860,7 +860,7 @@ describe('HybridSearchService', () => {
it('searchMode=semantic returns empty array for blank query', async () => {
const client = createTestDb();
const repoId = seedRepo(client);
const docId = seedDocument(client, repoId);
seedDocument(client, repoId);
const mockProvider = makeMockProvider([[1, 0, 0, 0]]);
@@ -879,7 +879,7 @@ describe('HybridSearchService', () => {
it('searchMode=semantic falls back to empty when provider fails', async () => {
const client = createTestDb();
const repoId = seedRepo(client);
const docId = seedDocument(client, repoId);
seedDocument(client, repoId);
const noopProvider = makeNoopProvider();
const searchService = new SearchService(client);

View File

@@ -46,8 +46,8 @@ export function preprocessQuery(raw: string): string {
.replace(/[()[\]{}]/g, ' ') // Remove grouping characters
.replace(/[;:,!?]/g, ' ') // Remove punctuation that breaks FTS
.replace(/[<>|]/g, ' ') // Remove comparison/pipe chars
.replace(/[\-+*/%]/g, ' ') // Remove operators (but keep underscores)
.replace(/[@#$&^\\~\`]/g, ' '); // Remove special chars
.replace(/[-+*/%]/g, ' ') // Remove operators (but keep underscores)
.replace(/[@#$&^~`\\]/g, ' '); // Remove special chars
// Split on remaining punctuation (like dots and slashes) but preserve alphanumeric/underscore.
const parts = sanitized.split(/[./\s]+/).filter(Boolean);

View File

@@ -62,21 +62,6 @@ interface RawRepo {
updated_at: number;
}
// Raw row shape returned by better-sqlite3 SELECT * FROM indexing_jobs.
interface RawJob {
id: string;
repository_id: string;
version_id: string | null;
status: string;
progress: number;
total_files: number;
processed_files: number;
error: string | null;
started_at: number | null;
completed_at: number | null;
created_at: number;
}
function makeService(client: Database.Database): RepositoryService {
return new RepositoryService(client);
}

View File

@@ -6,7 +6,7 @@
* output — the same pattern used by repository.service.test.ts.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect } from 'vitest';
import Database from 'better-sqlite3';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

View File

@@ -12,6 +12,7 @@ import {
RepositoryVersionEntity
} from '$lib/server/models/repository-version.js';
import { AlreadyExistsError, NotFoundError } from '$lib/server/utils/validation';
import { resolveTagToCommit, discoverVersionTags } from '$lib/server/utils/git.js';
export class VersionService {
constructor(private readonly db: Database.Database) {}
@@ -62,7 +63,6 @@ export class VersionService {
let resolvedCommitHash = commitHash;
if (!resolvedCommitHash && repo.source === 'local') {
try {
const { resolveTagToCommit } = require('$lib/server/utils/git.js');
resolvedCommitHash = resolveTagToCommit({ repoPath: repo.source_url, tag });
} catch (error) {
console.warn(
@@ -178,7 +178,6 @@ export class VersionService {
throw new Error('Tag discovery is only supported for local repositories');
}
const { discoverVersionTags, resolveTagToCommit } = require('$lib/server/utils/git.js');
const tags = discoverVersionTags({ repoPath: repo.source_url });
return tags.map((tag: string) => {

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import './layout.css';
import { resolve as resolveRoute } from '$app/paths';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
@@ -15,7 +16,7 @@
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div class="flex h-14 items-center justify-between">
<div class="flex items-center gap-6">
<a href="/" class="flex items-center gap-2 font-semibold text-gray-900">
<a href={resolveRoute('/')} class="flex items-center gap-2 font-semibold text-gray-900">
<svg
class="h-6 w-6 text-blue-600"
viewBox="0 0 24 24"
@@ -31,9 +32,15 @@
</svg>
<span>TrueRef</span>
</a>
<a href="/" class="text-sm text-gray-600 hover:text-gray-900"> Repositories </a>
<a href="/search" class="text-sm text-gray-600 hover:text-gray-900"> Search </a>
<a href="/settings" class="text-sm text-gray-600 hover:text-gray-900"> Settings </a>
<a href={resolveRoute('/')} class="text-sm text-gray-600 hover:text-gray-900">
Repositories
</a>
<a href={resolveRoute('/search')} class="text-sm text-gray-600 hover:text-gray-900">
Search
</a>
<a href={resolveRoute('/settings')} class="text-sm text-gray-600 hover:text-gray-900">
Settings
</a>
</div>
<span class="text-xs text-gray-400">Self-hosted documentation intelligence</span>
</div>

View File

@@ -1,18 +1,15 @@
<script lang="ts">
import type { PageData } from './$types';
import type { Repository } from '$lib/types';
import RepositoryCard from '$lib/components/RepositoryCard.svelte';
import AddRepositoryModal from '$lib/components/AddRepositoryModal.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import IndexingProgress from '$lib/components/IndexingProgress.svelte';
let { data }: { data: PageData } = $props();
type Repository = NonNullable<PageData['repositories']>[number];
// Initialized empty; $effect syncs from data prop on every navigation/reload.
let repositories = $state<Repository[]>([]);
$effect(() => {
repositories = data.repositories ?? [];
});
// Syncs from data prop on navigation while still allowing temporary local reassignment.
let repositories: Repository[] = $derived(data.repositories ?? []);
let showAddModal = $state(false);
let confirmDeleteId = $state<string | null>(null);
let activeJobIds = $state<Record<string, string>>({});
@@ -25,7 +22,7 @@
async function refreshRepositories() {
try {
const res = await fetch('/api/v1/libs');
const fetched = await res.json();
const fetched = (await res.json()) as { libraries?: Repository[] };
repositories = fetched.libraries ?? [];
} catch {
// keep existing list on network error

View File

@@ -11,7 +11,6 @@
* type (optional) — "json" (default) or "txt"
*/
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getClient } from '$lib/server/db/client';
import { dtoJsonResponse } from '$lib/server/api/dto-response';

View File

@@ -141,7 +141,8 @@ export const PUT: RequestHandler = POST;
function sanitizeProfile(profile: EmbeddingProfile): EmbeddingProfile {
const config = profile.config as Record<string, unknown>;
if (config && config.apiKey) {
const { apiKey: _apiKey, ...rest } = config;
const rest = { ...config };
delete rest.apiKey;
return { ...profile, config: rest };
}
return profile;

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { resolve as resolveRoute } from '$app/paths';
import type { PageData } from './$types';
import type { Repository, RepositoryVersion, IndexingJob } from '$lib/types';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@@ -79,7 +81,7 @@
const d = await res.json();
throw new Error(d.error ?? 'Failed to delete repository');
}
window.location.href = '/';
goto(resolveRoute('/'));
} catch (e) {
errorMessage = (e as Error).message;
}
@@ -101,7 +103,7 @@
</svelte:head>
<div class="mb-6">
<a href="/" class="text-sm text-blue-600 hover:underline">← Repositories</a>
<a href={resolveRoute('/')} class="text-sm text-blue-600 hover:underline">← Repositories</a>
</div>
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
@@ -124,7 +126,7 @@
<a
href={repo.sourceUrl}
target="_blank"
rel="noopener noreferrer"
rel="external noopener noreferrer"
class="mt-1 block text-sm text-blue-600 hover:underline"
>
{repo.sourceUrl}

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { resolve as resolveRoute } from '$app/paths';
import { page } from '$app/state';
import { untrack } from 'svelte';
import LibraryResult from '$lib/components/search/LibraryResult.svelte';
@@ -106,7 +107,9 @@
if (syncUrl) {
goto(
`/search?lib=${encodeURIComponent(selectedLibraryId)}&q=${encodeURIComponent(query)}`,
resolveRoute(
`/search?lib=${encodeURIComponent(selectedLibraryId)}&q=${encodeURIComponent(query)}`
),
{ replaceState: true, keepFocus: true }
);
}
@@ -133,7 +136,7 @@
snippetError = null;
query = '';
// Reset URL.
goto('/search', { replaceState: true, keepFocus: true });
goto(resolveRoute('/search'), { replaceState: true, keepFocus: true });
}
// ---------------------------------------------------------------------------

View File

@@ -188,7 +188,7 @@
<form class="space-y-4" onsubmit={handleSubmit}>
<!-- Provider selector -->
<div class="mb-4 flex gap-2">
{#each ['none', 'openai', 'local'] as p}
{#each ['none', 'openai', 'local'] as p (p)}
<button
type="button"
onclick={() => {
@@ -224,7 +224,7 @@
<div class="space-y-3">
<!-- Preset buttons -->
<div class="flex flex-wrap gap-2">
{#each PROVIDER_PRESETS as preset}
{#each PROVIDER_PRESETS as preset (preset.name)}
<button
type="button"
onclick={() => applyPreset(preset)}