From cd4ea7112ca6997523b58e61825424ab8c6eb86b Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Sat, 28 Mar 2026 17:27:53 +0100 Subject: [PATCH 1/4] fix(MULTIVERSION-0001): surface pre-parsed config in CrawlResult to fix rules persistence When trueref.json specifies a `folders` allowlist (e.g. ["src/"]), shouldIndexFile() excludes trueref.json itself because it lives at the repo root. The indexing pipeline then searches crawlResult.files for the config file, finds nothing, and never writes rules to repository_configs. Fix (Option B): add a `config` field to CrawlResult so LocalCrawler returns the pre-parsed config directly. The indexing pipeline now reads crawlResult.config first instead of scanning files[], which resolves the regression for all repos with a folders allowlist. - Add `config?: RepoConfig` to CrawlResult in crawler/types.ts - Return `config` from LocalCrawler.crawlDirectory() - Update IndexingPipeline.crawl() to propagate CrawlResult.config - Update IndexingPipeline.run() to prefer crawlResult.config over files - Add regression tests covering the folders-allowlist exclusion scenario Co-Authored-By: Claude Sonnet 4.6 --- src/lib/server/crawler/local.crawler.test.ts | 53 +++++++++++++++ src/lib/server/crawler/local.crawler.ts | 6 +- src/lib/server/crawler/types.ts | 7 ++ .../server/pipeline/indexing.pipeline.test.ts | 68 +++++++++++++++++++ src/lib/server/pipeline/indexing.pipeline.ts | 34 +++++++--- 5 files changed, 157 insertions(+), 11 deletions(-) diff --git a/src/lib/server/crawler/local.crawler.test.ts b/src/lib/server/crawler/local.crawler.test.ts index fe41bdc..aa08967 100644 --- a/src/lib/server/crawler/local.crawler.test.ts +++ b/src/lib/server/crawler/local.crawler.test.ts @@ -413,6 +413,59 @@ describe('LocalCrawler.crawl() — config file detection', () => { const result = await crawlRoot(); expect(result.files.some((f) => f.path === 'src/index.ts')).toBe(true); }); + + it('populates CrawlResult.config with the parsed trueref.json even when folders allowlist excludes the root', async () => { + // Regression test for MULTIVERSION-0001: + // When folders: ["src/"] is set, trueref.json at the root is excluded from + // files[] by shouldIndexFile(). The config must still be returned in + // CrawlResult.config so the indexing pipeline can persist rules. + root = await makeTempRepo({ + 'trueref.json': JSON.stringify({ + folders: ['src/'], + rules: ['Always document public APIs.'] + }), + 'src/index.ts': 'export {};', + 'docs/guide.md': '# Guide' + }); + const result = await crawlRoot(); + + // trueref.json must NOT appear in files (excluded by folders allowlist). + expect(result.files.some((f) => f.path === 'trueref.json')).toBe(false); + // docs/guide.md must NOT appear (outside src/). + expect(result.files.some((f) => f.path === 'docs/guide.md')).toBe(false); + // src/index.ts must appear (inside src/). + expect(result.files.some((f) => f.path === 'src/index.ts')).toBe(true); + // CrawlResult.config must carry the parsed config. + expect(result.config).toBeDefined(); + expect(result.config?.rules).toEqual(['Always document public APIs.']); + }); + + it('populates CrawlResult.config with the parsed context7.json', async () => { + root = await makeTempRepo({ + 'context7.json': JSON.stringify({ rules: ['Rule from context7.'] }), + 'src/index.ts': 'export {};' + }); + const result = await crawlRoot(); + expect(result.config).toBeDefined(); + expect(result.config?.rules).toEqual(['Rule from context7.']); + }); + + it('CrawlResult.config is undefined when no config file is present', async () => { + root = await makeTempRepo({ 'src/index.ts': 'export {};' }); + const result = await crawlRoot(); + expect(result.config).toBeUndefined(); + }); + + it('CrawlResult.config is undefined when caller supplies config (caller-provided takes precedence, no auto-detect)', async () => { + root = await makeTempRepo({ + 'trueref.json': JSON.stringify({ rules: ['From file.'] }), + 'src/index.ts': 'export {};' + }); + // Caller-supplied config prevents auto-detection; CrawlResult.config + // should carry the caller config (not the file content). + const result = await crawlRoot({ config: { rules: ['From caller.'] } }); + expect(result.config?.rules).toEqual(['From caller.']); + }); }); // --------------------------------------------------------------------------- diff --git a/src/lib/server/crawler/local.crawler.ts b/src/lib/server/crawler/local.crawler.ts index 6dfd01a..ad00ca7 100644 --- a/src/lib/server/crawler/local.crawler.ts +++ b/src/lib/server/crawler/local.crawler.ts @@ -230,7 +230,11 @@ export class LocalCrawler { totalFiles: filteredPaths.length, skippedFiles: allRelPaths.length - filteredPaths.length, branch, - commitSha + commitSha, + // Surface the pre-parsed config so the indexing pipeline can read rules + // without needing to find trueref.json inside crawledFiles (which fails + // when a `folders` allowlist excludes the repo root). + config: config ?? undefined }; } diff --git a/src/lib/server/crawler/types.ts b/src/lib/server/crawler/types.ts index a40eba6..da386ec 100644 --- a/src/lib/server/crawler/types.ts +++ b/src/lib/server/crawler/types.ts @@ -35,6 +35,13 @@ export interface CrawlResult { branch: string; /** HEAD commit SHA */ commitSha: string; + /** + * Pre-parsed trueref.json / context7.json configuration found at the repo + * root during crawling. Carried here so the indexing pipeline can consume it + * directly without having to locate the config file in `files` — which fails + * when a `folders` allowlist excludes the repo root. + */ + config?: RepoConfig; } export interface CrawlOptions { diff --git a/src/lib/server/pipeline/indexing.pipeline.test.ts b/src/lib/server/pipeline/indexing.pipeline.test.ts index 59c0078..51a1ba7 100644 --- a/src/lib/server/pipeline/indexing.pipeline.test.ts +++ b/src/lib/server/pipeline/indexing.pipeline.test.ts @@ -268,6 +268,8 @@ describe('IndexingPipeline', () => { crawlResult: { files: Array<{ path: string; content: string; sha: string; language: string }>; totalFiles: number; + /** Optional pre-parsed config — simulates LocalCrawler returning CrawlResult.config. */ + config?: Record; } = { files: [], totalFiles: 0 }, embeddingService: EmbeddingService | null = null ) { @@ -885,4 +887,70 @@ describe('IndexingPipeline', () => { const rules = JSON.parse(versionRow!.rules); expect(rules).toEqual(['This is v2. Use the new Builder API.']); }); + + it('persists rules from CrawlResult.config even when trueref.json is absent from files (folders allowlist bug)', async () => { + // Regression test for MULTIVERSION-0001: + // When trueref.json specifies a `folders` allowlist (e.g. ["src/"]), + // shouldIndexFile() excludes trueref.json itself because it lives at the + // repo root. The LocalCrawler now carries the pre-parsed config in + // CrawlResult.config so the pipeline no longer needs to find the file in + // crawlResult.files[]. + const pipeline = makePipeline({ + // trueref.json is NOT in files — simulates it being excluded by folders allowlist. + files: [ + { + path: 'src/index.ts', + content: 'export const x = 1;', + sha: 'sha-src', + language: 'typescript' + } + ], + totalFiles: 1, + // The pre-parsed config is carried here instead (set by LocalCrawler). + config: { rules: ['Use strict TypeScript.', 'Avoid any.'] } + }); + const job = makeJob(); + + await pipeline.run(job as never); + + const row = db + .prepare( + `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL` + ) + .get() as { rules: string } | undefined; + + expect(row).toBeDefined(); + const rules = JSON.parse(row!.rules); + expect(rules).toEqual(['Use strict TypeScript.', 'Avoid any.']); + }); + + it('persists version-specific rules from CrawlResult.config when trueref.json is excluded by folders allowlist', async () => { + const versionId = insertVersion(db, { tag: 'v3.0.0', state: 'pending' }); + + const pipeline = makePipeline({ + files: [ + { + path: 'src/index.ts', + content: 'export const x = 1;', + sha: 'sha-src', + language: 'typescript' + } + ], + totalFiles: 1, + config: { rules: ['v3: use the streaming API.'] } + }); + const job = makeJob('/test/repo', versionId); + + await pipeline.run(job as never); + + const versionRow = db + .prepare( + `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id = ?` + ) + .get(versionId) as { rules: string } | undefined; + + expect(versionRow).toBeDefined(); + const rules = JSON.parse(versionRow!.rules); + expect(rules).toEqual(['v3: use the streaming API.']); + }); }); diff --git a/src/lib/server/pipeline/indexing.pipeline.ts b/src/lib/server/pipeline/indexing.pipeline.ts index ed01062..6056e0e 100644 --- a/src/lib/server/pipeline/indexing.pipeline.ts +++ b/src/lib/server/pipeline/indexing.pipeline.ts @@ -15,14 +15,14 @@ import { createHash, randomUUID } from 'node:crypto'; import type Database from 'better-sqlite3'; -import type { Document, NewDocument, NewSnippet } from '$lib/types'; +import type { Document, NewDocument, NewSnippet, TrueRefConfig } from '$lib/types'; import type { crawl as GithubCrawlFn } from '$lib/server/crawler/github.crawler.js'; import type { LocalCrawler } from '$lib/server/crawler/local.crawler.js'; import type { EmbeddingService } from '$lib/server/embeddings/embedding.service.js'; import { RepositoryMapper } from '$lib/server/mappers/repository.mapper.js'; import { IndexingJob } from '$lib/server/models/indexing-job.js'; import { Repository, RepositoryEntity } from '$lib/server/models/repository.js'; -import { resolveConfig } from '$lib/server/config/config-parser.js'; +import { resolveConfig, type ParsedConfig } from '$lib/server/config/config-parser.js'; import { parseFile } from '$lib/server/parser/index.js'; import { computeTrustScore } from '$lib/server/search/trust-score.js'; import { computeDiff } from './diff.js'; @@ -101,13 +101,25 @@ export class IndexingPipeline { : undefined; const crawlResult = await this.crawl(repo, versionTag); - // Parse trueref.json / context7.json if present in the crawl results. - const configFile = crawlResult.files.find( - (f) => f.path === 'trueref.json' || f.path === 'context7.json' - ); - const parsedConfig = configFile - ? resolveConfig([{ filename: configFile.path, content: configFile.content }]) - : null; + // Resolve trueref.json / context7.json configuration. + // Prefer the pre-parsed config carried in the CrawlResult (set by + // LocalCrawler so it is available even when a `folders` allowlist + // excludes the repo root and trueref.json never appears in files[]). + // Fall back to locating the file in crawlResult.files for GitHub crawls + // which do not yet populate CrawlResult.config. + let parsedConfig: ReturnType | null = null; + if (crawlResult.config) { + // Config was pre-parsed by the crawler — wrap it in a ParsedConfig + // shell so the rest of the pipeline can use it uniformly. + parsedConfig = { config: crawlResult.config, source: 'trueref.json', warnings: [] } satisfies ParsedConfig; + } else { + const configFile = crawlResult.files.find( + (f) => f.path === 'trueref.json' || f.path === 'context7.json' + ); + parsedConfig = configFile + ? resolveConfig([{ filename: configFile.path, content: configFile.content }]) + : null; + } const excludeFiles: string[] = parsedConfig?.config.excludeFiles ?? []; // Filter out excluded files before diff computation. @@ -304,6 +316,8 @@ export class IndexingPipeline { private async crawl(repo: Repository, ref?: string): Promise<{ files: Array<{ path: string; content: string; sha: string; size: number; language: string }>; totalFiles: number; + /** Pre-parsed trueref.json / context7.json, or undefined when absent. */ + config?: TrueRefConfig; }> { if (repo.source === 'github') { // Parse owner/repo from the canonical ID: "/owner/repo" @@ -330,7 +344,7 @@ export class IndexingPipeline { ref: ref ?? (repo.branch !== 'main' ? (repo.branch ?? undefined) : undefined) }); - return { files: result.files, totalFiles: result.totalFiles }; + return { files: result.files, totalFiles: result.totalFiles, config: result.config }; } } From bbc67f80649332e3800d1f00edebd043e8b2e06d Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Sun, 29 Mar 2026 01:15:58 +0100 Subject: [PATCH 2/4] fix(MULTIVERSION-0001): prevent version jobs from overwriting repo-wide NULL rules entry Version jobs now write rules only to the version-specific (repo, versionId) row. Previously every version job unconditionally wrote to the (repo, NULL) row as well, causing whichever version indexed last to contaminate the repo-wide rules that the context API merges into every query response. Adds a regression test (Bug5b) that indexes the main branch, then indexes a version with different rules, and asserts the NULL row still holds the main-branch rules. Co-Authored-By: Claude Sonnet 4.6 --- .../server/pipeline/indexing.pipeline.test.ts | 71 ++++++++++++++++++- src/lib/server/pipeline/indexing.pipeline.ts | 11 +-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/lib/server/pipeline/indexing.pipeline.test.ts b/src/lib/server/pipeline/indexing.pipeline.test.ts index 51a1ba7..f057d0c 100644 --- a/src/lib/server/pipeline/indexing.pipeline.test.ts +++ b/src/lib/server/pipeline/indexing.pipeline.test.ts @@ -869,15 +869,17 @@ describe('IndexingPipeline', () => { await pipeline.run(job as never); - // Repo-wide row (version_id IS NULL) must exist. + // Repo-wide row (version_id IS NULL) must NOT be written by a version job — + // writing it here would contaminate the NULL entry with version-specific rules + // (Bug 5b regression guard). const repoRow = db .prepare( `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL` ) .get() as { rules: string } | undefined; - expect(repoRow).toBeDefined(); + expect(repoRow).toBeUndefined(); - // Version-specific row must also exist. + // Version-specific row must exist with the correct rules. const versionRow = db .prepare( `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id = ?` @@ -888,6 +890,69 @@ describe('IndexingPipeline', () => { expect(rules).toEqual(['This is v2. Use the new Builder API.']); }); + it('regression(Bug5b): version job does not overwrite the repo-wide NULL rules entry', async () => { + // Arrange: index the main branch first to establish a repo-wide rules entry. + const mainBranchRules = ['Always use TypeScript strict mode.']; + const mainPipeline = makePipeline({ + files: [ + { + path: 'trueref.json', + content: JSON.stringify({ rules: mainBranchRules }), + sha: 'sha-main-config', + language: 'json' + } + ], + totalFiles: 1 + }); + const mainJob = makeJob('/test/repo'); // no versionId → main-branch job + await mainPipeline.run(mainJob as never); + + // Confirm the repo-wide entry was written. + const afterMain = db + .prepare( + `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL` + ) + .get() as { rules: string } | undefined; + expect(afterMain).toBeDefined(); + expect(JSON.parse(afterMain!.rules)).toEqual(mainBranchRules); + + // Act: index a version with different rules. + const versionId = insertVersion(db, { tag: 'v3.0.0', state: 'pending' }); + const versionRules = ['v3 only: use the streaming API.']; + const versionPipeline = makePipeline({ + files: [ + { + path: 'trueref.json', + content: JSON.stringify({ rules: versionRules }), + sha: 'sha-v3-config', + language: 'json' + } + ], + totalFiles: 1 + }); + const versionJob = makeJob('/test/repo', versionId); + await versionPipeline.run(versionJob as never); + + // Assert: the repo-wide NULL entry must still contain the main-branch rules, + // not the version-specific ones. + const afterVersion = db + .prepare( + `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL` + ) + .get() as { rules: string } | undefined; + expect(afterVersion).toBeDefined(); + expect(JSON.parse(afterVersion!.rules)).toEqual(mainBranchRules); + + // And the version-specific row must contain the version rules. + const versionRow = db + .prepare( + `SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id = ?` + ) + .get(versionId) as { rules: string } | undefined; + expect(versionRow).toBeDefined(); + expect(JSON.parse(versionRow!.rules)).toEqual(versionRules); + }); + it('persists rules from CrawlResult.config even when trueref.json is absent from files (folders allowlist bug)', async () => { // Regression test for MULTIVERSION-0001: // When trueref.json specifies a `folders` allowlist (e.g. ["src/"]), diff --git a/src/lib/server/pipeline/indexing.pipeline.ts b/src/lib/server/pipeline/indexing.pipeline.ts index 6056e0e..9e8e9ff 100644 --- a/src/lib/server/pipeline/indexing.pipeline.ts +++ b/src/lib/server/pipeline/indexing.pipeline.ts @@ -276,10 +276,13 @@ export class IndexingPipeline { // ---- Stage 6: Persist rules from config ---------------------------- if (parsedConfig?.config.rules?.length) { - // Repo-wide rules (versionId = null). - this.upsertRepoConfig(repo.id, null, parsedConfig.config.rules); - // Version-specific rules stored separately when indexing a version. - if (normJob.versionId) { + if (!normJob.versionId) { + // Main-branch job: write the repo-wide entry only. + this.upsertRepoConfig(repo.id, null, parsedConfig.config.rules); + } else { + // Version job: write only the version-specific entry. + // Writing to the NULL row here would overwrite repo-wide rules + // with whatever the last-indexed version happened to carry. this.upsertRepoConfig(repo.id, normJob.versionId, parsedConfig.config.rules); } } From 09c6f9f7c1a07dc7a79a0ccfdaded853987dc173 Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Sun, 29 Mar 2026 11:47:31 +0200 Subject: [PATCH 3/4] fix(MULTIVERSION-0001): eliminate NULL-row contamination in getRules When a versioned query is made, getRules() now returns only the version-specific repository_configs row. The NULL (HEAD/repo-wide) row is no longer merged in, preventing v4 rules from bleeding into v1/v2/v3 versioned context responses. Tests updated to assert the isolation: versioned queries return only their own rules row; a new test verifies that a version with no config row returns an empty rules array even when a NULL row exists. Co-Authored-By: Claude Sonnet 4.6 --- .../api/v1/api-contract.integration.test.ts | 44 ++++++++++++++++--- src/routes/api/v1/context/+server.ts | 36 ++++++--------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/routes/api/v1/api-contract.integration.test.ts b/src/routes/api/v1/api-contract.integration.test.ts index 82ae634..342bed2 100644 --- a/src/routes/api/v1/api-contract.integration.test.ts +++ b/src/routes/api/v1/api-contract.integration.test.ts @@ -446,7 +446,11 @@ describe('API contract integration', () => { const repositoryId = seedRepo(db); const versionId = seedVersion(db, repositoryId, 'v18.3.0'); const documentId = seedDocument(db, repositoryId, versionId); - seedRules(db, repositoryId, ['Prefer hooks over classes']); + // Insert version-specific rules (versioned queries no longer inherit the NULL row). + db.prepare( + `INSERT INTO repository_configs (repository_id, version_id, rules, updated_at) + VALUES (?, ?, ?, ?)` + ).run(repositoryId, versionId, JSON.stringify(['Prefer hooks over classes']), NOW_S); seedSnippet(db, { documentId, repositoryId, @@ -497,12 +501,12 @@ describe('API contract integration', () => { }); }); - it('GET /api/v1/context returns merged repo-wide and version-specific rules', async () => { + it('GET /api/v1/context returns only version-specific rules for versioned queries (no NULL row contamination)', async () => { const repositoryId = seedRepo(db); const versionId = seedVersion(db, repositoryId, 'v2.0.0'); const documentId = seedDocument(db, repositoryId, versionId); - // Insert repo-wide rules (version_id IS NULL). + // Insert repo-wide rules (version_id IS NULL) — these must NOT appear in versioned queries. db.prepare( `INSERT INTO repository_configs (repository_id, version_id, rules, updated_at) VALUES (?, NULL, ?, ?)` @@ -529,8 +533,8 @@ describe('API contract integration', () => { expect(response.status).toBe(200); const body = await response.json(); - // Both repo-wide and version-specific rules should appear (deduped). - expect(body.rules).toEqual(['Repo-wide rule', 'Version-specific rule']); + // Only the version-specific rule should appear — NULL row must not contaminate. + expect(body.rules).toEqual(['Version-specific rule']); }); it('GET /api/v1/context returns only repo-wide rules when no version is requested', async () => { @@ -556,12 +560,13 @@ describe('API contract integration', () => { expect(body.rules).toEqual(['Repo-wide rule only']); }); - it('GET /api/v1/context deduplicates rules that appear in both repo-wide and version config', async () => { + it('GET /api/v1/context versioned query returns only the version-specific rules row', async () => { const repositoryId = seedRepo(db); const versionId = seedVersion(db, repositoryId, 'v3.0.0'); const documentId = seedDocument(db, repositoryId, versionId); const sharedRule = 'Use TypeScript strict mode'; + // Insert repo-wide NULL row — must NOT bleed into versioned query results. db.prepare( `INSERT INTO repository_configs (repository_id, version_id, rules, updated_at) VALUES (?, NULL, ?, ?)` @@ -582,10 +587,35 @@ describe('API contract integration', () => { expect(response.status).toBe(200); const body = await response.json(); - // sharedRule appears once, version-only rule appended. + // Returns only the version-specific row as stored — no NULL row merge. expect(body.rules).toEqual([sharedRule, 'Version-only rule']); }); + it('GET /api/v1/context versioned query returns empty rules when only NULL row exists (no NULL contamination)', async () => { + const repositoryId = seedRepo(db); + const versionId = seedVersion(db, repositoryId, 'v1.0.0'); + const documentId = seedDocument(db, repositoryId, versionId); + + // Only a repo-wide NULL row exists — no version-specific config. + db.prepare( + `INSERT INTO repository_configs (repository_id, version_id, rules, updated_at) + VALUES (?, NULL, ?, ?)` + ).run(repositoryId, JSON.stringify(['HEAD rules that must not contaminate v1']), NOW_S); + + seedSnippet(db, { documentId, repositoryId, versionId, content: 'v1 content' }); + + const response = await getContext({ + url: new URL( + `http://test/api/v1/context?libraryId=${encodeURIComponent(`${repositoryId}/v1.0.0`)}&query=${encodeURIComponent('v1 content')}` + ) + } as never); + + expect(response.status).toBe(200); + const body = await response.json(); + // No version-specific config row → empty rules. NULL row must not bleed in. + expect(body.rules).toEqual([]); + }); + it('GET /api/v1/context returns 404 with VERSION_NOT_FOUND when version does not exist', async () => { const repositoryId = seedRepo(db); diff --git a/src/routes/api/v1/context/+server.ts b/src/routes/api/v1/context/+server.ts index ed711dc..214713c 100644 --- a/src/routes/api/v1/context/+server.ts +++ b/src/routes/api/v1/context/+server.ts @@ -69,35 +69,25 @@ function getRules( repositoryId: string, versionId?: string ): string[] { - // Repo-wide rules (version_id IS NULL). - const repoRow = db - .prepare< - [string], - RawRepoConfig - >(`SELECT rules FROM repository_configs WHERE repository_id = ? AND version_id IS NULL`) - .get(repositoryId); + if (!versionId) { + // Unversioned query: return repo-wide (HEAD) rules only. + const row = db + .prepare< + [string], + RawRepoConfig + >(`SELECT rules FROM repository_configs WHERE repository_id = ? AND version_id IS NULL`) + .get(repositoryId); + return parseRulesJson(row?.rules); + } - const repoRules = parseRulesJson(repoRow?.rules); - - if (!versionId) return repoRules; - - // Version-specific rules. - const versionRow = db + // Versioned query: return only version-specific rules (no NULL row merge). + const row = db .prepare< [string, string], RawRepoConfig >(`SELECT rules FROM repository_configs WHERE repository_id = ? AND version_id = ?`) .get(repositoryId, versionId); - - const versionRules = parseRulesJson(versionRow?.rules); - - // Merge: repo-wide first, then version-specific (deduped by content). - const seen = new Set(repoRules); - const merged = [...repoRules]; - for (const r of versionRules) { - if (!seen.has(r)) merged.push(r); - } - return merged; + return parseRulesJson(row?.rules); } interface RawRepoState { From 0bf01e305734b4bacaed65430372cb048022ff71 Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Sun, 29 Mar 2026 12:44:06 +0200 Subject: [PATCH 4/4] last fix --- src/hooks.server.ts | 8 +- src/lib/components/IndexingProgress.svelte | 7 +- .../components/RepositoryCard.svelte.test.ts | 7 +- src/lib/server/db/index.ts | 1 + .../db/migrations/meta/0003_snapshot.json | 835 ++++++++++++++++++ src/routes/repos/[id]/+page.svelte | 23 + 6 files changed, 876 insertions(+), 5 deletions(-) create mode 100644 src/lib/server/db/migrations/meta/0003_snapshot.json diff --git a/src/hooks.server.ts b/src/hooks.server.ts index d66593b..1d25e06 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -24,7 +24,12 @@ import type { Handle } from '@sveltejs/kit'; try { initializeDatabase(); +} catch (err) { + console.error('[hooks.server] FATAL: database initialisation failed:', err); + process.exit(1); +} +try { const db = getClient(); const activeProfileRow = db .prepare<[], EmbeddingProfileEntityProps>( @@ -46,7 +51,8 @@ try { console.log('[hooks.server] Indexing pipeline initialised.'); } catch (err) { console.error( - `[hooks.server] Failed to initialise server: ${err instanceof Error ? err.message : String(err)}` + '[hooks.server] Failed to initialise pipeline:', + err instanceof Error ? err.message : String(err) ); } diff --git a/src/lib/components/IndexingProgress.svelte b/src/lib/components/IndexingProgress.svelte index f77f383..40080b5 100644 --- a/src/lib/components/IndexingProgress.svelte +++ b/src/lib/components/IndexingProgress.svelte @@ -1,13 +1,14 @@