# TRUEREF-0013 — `trueref.json` Config File Support **Priority:** P0 **Status:** Pending **Depends On:** TRUEREF-0003 **Blocks:** TRUEREF-0009 --- ## Overview Support `trueref.json` configuration files placed in the root of a repository. This file allows repository owners to control how their repo is indexed: which folders to include, which to exclude, best-practice rules to inject into LLM responses, and versioning information. Also support `context7.json` as an alias for backward compatibility. --- ## Acceptance Criteria - [ ] Parser for `trueref.json` and `context7.json` (same schema, `trueref.json` takes precedence) - [ ] Config applied during crawling to filter files (TRUEREF-0003, TRUEREF-0004) - [ ] Config stored in `repository_configs` table (TRUEREF-0001) - [ ] Config validated with descriptive error messages (invalid paths, oversized rules, etc.) - [ ] `rules` injected at the top of every `query-docs` response - [ ] `previousVersions` registered as `RepositoryVersion` records - [ ] JSON Schema published at `/api/v1/schema/trueref-config.json` - [ ] Unit tests for parser and validator --- ## Config File Schema ```typescript // src/lib/server/config/trueref-config.schema.ts export interface TrueRefConfig { /** * Override the display name for this library. * 1–100 characters. */ projectTitle?: string; /** * Description of the library for search ranking. * 10–500 characters. */ description?: string; /** * Folders to include in indexing (allowlist). * Each entry is a path prefix or regex string. * If empty/absent, all folders are included. * Examples: ["src/", "docs/", "^packages/core"] */ folders?: string[]; /** * Folders to exclude from indexing. * Applied after `folders` allowlist. * Examples: ["test/", "fixtures/", "__mocks__"] */ excludeFolders?: string[]; /** * Exact filenames to exclude (no path, no regex). * Examples: ["README.md", "CHANGELOG.md", "jest.config.ts"] */ excludeFiles?: string[]; /** * Best practices / rules to inject at the top of every query-docs response. * Each rule: 5–500 characters. * Maximum 20 rules. */ rules?: string[]; /** * Previously released versions to make available for versioned queries. */ previousVersions?: Array<{ tag: string; // git tag (e.g. "v1.2.3") title: string; // human-readable (e.g. "Version 1.2.3") }>; } ``` --- ## Validation Rules ```typescript const CONFIG_CONSTRAINTS = { projectTitle: { minLength: 1, maxLength: 100 }, description: { minLength: 10, maxLength: 500 }, folders: { maxItems: 50, maxLength: 200 }, // per entry excludeFolders: { maxItems: 50, maxLength: 200 }, excludeFiles: { maxItems: 100, maxLength: 200 }, rules: { maxItems: 20, minLength: 5, maxLength: 500 }, previousVersions: { maxItems: 50 }, versionTag: { pattern: /^v?\d+\.\d+(\.\d+)?(-.*)?$/ }, }; ``` --- ## Parser Implementation ```typescript // src/lib/server/config/config-parser.ts export interface ParsedConfig { config: TrueRefConfig; source: 'trueref.json' | 'context7.json'; warnings: string[]; } export function parseConfigFile(content: string, filename: string): ParsedConfig { let raw: unknown; try { raw = JSON.parse(content); } catch (e) { throw new ConfigParseError(`${filename} is not valid JSON: ${(e as Error).message}`); } if (typeof raw !== 'object' || raw === null) { throw new ConfigParseError(`${filename} must be a JSON object`); } const config = raw as Record; const validated: TrueRefConfig = {}; const warnings: string[] = []; // projectTitle if (config.projectTitle !== undefined) { if (typeof config.projectTitle !== 'string') { warnings.push('projectTitle must be a string, ignoring'); } else if (config.projectTitle.length > 100) { validated.projectTitle = config.projectTitle.slice(0, 100); warnings.push('projectTitle truncated to 100 characters'); } else { validated.projectTitle = config.projectTitle; } } // description if (config.description !== undefined) { if (typeof config.description === 'string') { validated.description = config.description.slice(0, 500); } } // folders / excludeFolders / excludeFiles — validated as string arrays for (const field of ['folders', 'excludeFolders', 'excludeFiles'] as const) { if (config[field] !== undefined) { if (!Array.isArray(config[field])) { warnings.push(`${field} must be an array, ignoring`); } else { validated[field] = (config[field] as unknown[]) .filter((item): item is string => { if (typeof item !== 'string') { warnings.push(`${field} entry must be a string, skipping: ${item}`); return false; } return true; }) .slice(0, field === 'excludeFiles' ? 100 : 50); } } } // rules if (config.rules !== undefined) { if (Array.isArray(config.rules)) { validated.rules = (config.rules as unknown[]) .filter((r): r is string => typeof r === 'string' && r.length >= 5) .map(r => r.slice(0, 500)) .slice(0, 20); } } // previousVersions if (config.previousVersions !== undefined) { if (Array.isArray(config.previousVersions)) { validated.previousVersions = (config.previousVersions as unknown[]) .filter((v): v is { tag: string; title: string } => typeof v === 'object' && v !== null && typeof (v as Record).tag === 'string' && typeof (v as Record).title === 'string' ) .slice(0, 50); } } return { config: validated, source: filename.startsWith('trueref') ? 'trueref.json' : 'context7.json', warnings, }; } ``` --- ## Integration with Crawlers During crawl, the crawler: 1. Looks for `trueref.json` first, then `context7.json` in the root. 2. Downloads and parses the config before downloading other files. 3. Applies `folders`, `excludeFolders`, `excludeFiles` to the file list. 4. Returns the parsed config alongside crawl results. The `IndexingPipeline` stores the config in `repository_configs` table and registers `previousVersions` as `RepositoryVersion` records (with state `pending`, triggering additional indexing jobs if configured). --- ## Rules Injection When `query-docs` returns results, `rules` from `repository_configs` are prepended: ```typescript // In formatters.ts function buildContextResponse( snippets: Snippet[], config: RepositoryConfig | null ): string { const parts: string[] = []; if (config?.rules?.length) { parts.push( '## Library Best Practices\n' + config.rules.map(r => `- ${r}`).join('\n') ); } // ... append snippet content return parts.join('\n\n---\n\n'); } ``` --- ## JSON Schema Endpoint `GET /api/v1/schema/trueref-config.json` Returns the full JSON Schema for `trueref.json` so IDE validation (VS Code, etc.) can provide autocomplete: ```json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TrueRef Repository Configuration", "type": "object", "properties": { "projectTitle": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Override the display name for this library" }, ... } } ``` --- ## Files to Create - `src/lib/server/config/config-parser.ts` - `src/lib/server/config/config-validator.ts` - `src/lib/server/config/trueref-config.schema.ts` — TypeScript types - `src/lib/server/config/trueref-config.json` — JSON Schema - `src/routes/api/v1/schema/trueref-config.json/+server.ts` - `src/lib/server/config/config-parser.test.ts`