271 lines
7.3 KiB
Markdown
271 lines
7.3 KiB
Markdown
# 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<string, unknown>;
|
||
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<string, unknown>).tag === 'string' &&
|
||
typeof (v as Record<string, unknown>).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`
|