# TRUEREF-0002 — Repository Management Service & REST API **Priority:** P0 **Status:** Pending **Depends On:** TRUEREF-0001 **Blocks:** TRUEREF-0009, TRUEREF-0010, TRUEREF-0015 --- ## Overview Implement the core `RepositoryService` that handles CRUD operations for repositories, and expose those operations via SvelteKit API routes. This feature establishes the management plane for all library sources. --- ## Acceptance Criteria - [ ] `RepositoryService` class with full CRUD operations - [ ] `GET /api/v1/libs` — list all repositories with metadata - [ ] `POST /api/v1/libs` — add a new repository (GitHub URL or local path) - [ ] `GET /api/v1/libs/:id` — get single repository details - [ ] `PATCH /api/v1/libs/:id` — update repository metadata - [ ] `DELETE /api/v1/libs/:id` — delete repository and all associated data - [ ] `POST /api/v1/libs/:id/index` — trigger indexing job (queues job, returns job ID) - [ ] Input validation with descriptive error messages - [ ] All endpoints return JSON with consistent error shape - [ ] Unit tests for `RepositoryService` covering all operations --- ## Repository ID Generation GitHub repositories: - Input URL: `https://github.com/facebook/react` or `github.com/facebook/react` - Generated ID: `/facebook/react` Local repositories: - Input path: `/home/user/projects/my-sdk` - Generated ID: `/local/my-sdk` (basename of path, slugified) - Collision resolution: append `-2`, `-3`, etc. Version-specific IDs: `/facebook/react/v18.3.0` --- ## Service Interface ```typescript // src/lib/server/services/repository.service.ts export interface AddRepositoryInput { source: 'github' | 'local'; sourceUrl: string; // GitHub URL or absolute local path title?: string; // override auto-detected title description?: string; branch?: string; // GitHub: default branch; Local: n/a githubToken?: string; // for private GitHub repos } export interface UpdateRepositoryInput { title?: string; description?: string; branch?: string; githubToken?: string; } export class RepositoryService { constructor(private db: BetterSQLite3.Database) {} async list(options?: { state?: Repository['state']; limit?: number; offset?: number; }): Promise async get(id: string): Promise async add(input: AddRepositoryInput): Promise async update(id: string, input: UpdateRepositoryInput): Promise async remove(id: string): Promise async getStats(id: string): Promise<{ totalSnippets: number; totalTokens: number; totalDocuments: number; lastIndexedAt: Date | null; }> } ``` --- ## API Route Specifications ### `GET /api/v1/libs` Query parameters: - `state` (optional): filter by state (`pending`, `indexed`, `error`, etc.) - `limit` (optional, default 50): max results - `offset` (optional, default 0): pagination offset Response `200`: ```json { "libraries": [ { "id": "/facebook/react", "title": "React", "description": "...", "source": "github", "state": "indexed", "totalSnippets": 1234, "totalTokens": 98000, "trustScore": 8.5, "stars": 228000, "lastIndexedAt": "2026-03-22T10:00:00Z", "versions": ["v18.3.0", "v17.0.2"] } ], "total": 12, "limit": 50, "offset": 0 } ``` ### `POST /api/v1/libs` Request body: ```json { "source": "github", "sourceUrl": "https://github.com/facebook/react", "branch": "main", "githubToken": "ghp_...", "autoIndex": true } ``` Response `201`: ```json { "library": { ...Repository }, "job": { "id": "uuid", "status": "queued" } } ``` `autoIndex: true` (default) immediately queues an indexing job. Response `409` if repository already exists: ```json { "error": "Repository /facebook/react already exists" } ``` ### `GET /api/v1/libs/:id` `:id` must be URL-encoded (e.g., `%2Ffacebook%2Freact` for `/facebook/react`). Response `200`: single `Repository` object with versions array. Response `404`: `{ "error": "Repository not found" }` ### `PATCH /api/v1/libs/:id` Request body: partial `UpdateRepositoryInput`. Response `200`: updated `Repository`. ### `DELETE /api/v1/libs/:id` Cascades: deletes all documents, snippets, embeddings, jobs for this repository. Response `204`: no body. Response `404`: not found. ### `POST /api/v1/libs/:id/index` Triggers a new indexing job. If a job is already running for this repo, returns the existing job. Request body (optional): ```json { "version": "v18.3.0" } ``` Response `202`: ```json { "job": { "id": "uuid", "repositoryId": "/facebook/react", "status": "queued", "progress": 0, "createdAt": "2026-03-22T10:00:00Z" } } ``` --- ## Error Response Shape All error responses follow: ```json { "error": "Human-readable message", "code": "MACHINE_READABLE_CODE", "details": {} } ``` Error codes: - `NOT_FOUND` - `ALREADY_EXISTS` - `INVALID_INPUT` - `INVALID_URL` - `INDEXING_IN_PROGRESS` --- ## ID Resolution Logic ```typescript function resolveGitHubId(url: string): string { // Parse owner/repo from URL variants: // https://github.com/facebook/react // https://github.com/facebook/react.git // github.com/facebook/react const match = url.match(/github\.com\/([^/]+)\/([^/\s.]+)/); if (!match) throw new Error('Invalid GitHub URL'); return `/${match[1]}/${match[2]}`; } function resolveLocalId(path: string, existingIds: string[]): string { const base = slugify(path.split('/').at(-1)!); let id = `/local/${base}`; let counter = 2; while (existingIds.includes(id)) { id = `/local/${base}-${counter++}`; } return id; } ``` --- ## Files to Create - `src/lib/server/services/repository.service.ts` - `src/routes/api/v1/libs/+server.ts` — GET (list), POST (add) - `src/routes/api/v1/libs/[id]/+server.ts` — GET, PATCH, DELETE - `src/routes/api/v1/libs/[id]/index/+server.ts` — POST (trigger indexing) - `src/lib/server/utils/id-resolver.ts` — ID generation helpers - `src/lib/server/utils/validation.ts` — input validators - `src/lib/server/services/repository.service.test.ts`