Dieses Tool soll Entwicklern in einem Produkt-Team ermöglichen, schnell herauszufinden, in welchen von bis zu 140 GitLab-Projekt-Repos eine bestimmte Komponente, Methode oder Anforderung umgesetzt wird – ohne manuell durch GitLab zu navigieren.
Zwei Suchmodi:
- Syntaktische Suche – Wo wird
ComponentXodermethodYkonkret verwendet? (exakte/strukturelle Suche) - Semantische Suche – Wo wird eine bestimmte Anforderung oder User Story umgesetzt? (KI-basierte Ähnlichkeitssuche)
| Bereich | Technologie |
|---|---|
| Backend | Node.js + TypeScript + Fastify |
| Frontend | React + TypeScript + Vite |
| Embeddings | Ollama (nomic-embed-text) – läuft lokal |
| Vektordatenbank | Qdrant (Docker) |
| GitLab API | REST API mit axios |
| Echtzeit-Streaming | Server-Sent Events (SSE) |
| Infrastruktur | Docker Compose |
| Monorepo | pnpm Workspaces |
gitlab-search-tool/
├── apps/
│ ├── frontend/ # React + Vite App
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── SearchBar.tsx
│ │ │ │ ├── ResultList.tsx
│ │ │ │ └── ResultItem.tsx
│ │ │ ├── hooks/
│ │ │ │ └── useSearch.ts
│ │ │ └── App.tsx
│ │ └── package.json
│ │
│ └── backend/ # Fastify API
│ ├── src/
│ │ ├── routes/
│ │ │ ├── search.syntax.ts
│ │ │ ├── search.semantic.ts
│ │ │ └── webhook.ts
│ │ ├── services/
│ │ │ ├── gitlab.service.ts
│ │ │ ├── qdrant.service.ts
│ │ │ ├── ollama.service.ts
│ │ │ └── indexer.service.ts
│ │ └── index.ts
│ └── package.json
│
├── packages/
│ └── shared/ # Gemeinsame TypeScript Types
│ ├── src/
│ │ └── types.ts
│ └── package.json
│
├── scripts/
│ └── index-all.ts # Einmaliges initiales Indexing aller Repos
│
├── docker-compose.yml
├── .env.example
└── pnpm-workspace.yaml
# GitLab
GITLAB_BASE_URL=https://your-gitlab.company.com
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
GITLAB_GROUP_ID=123
# Ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=nomic-embed-text
# Qdrant
QDRANT_URL=http://localhost:6333
QDRANT_COLLECTION=gitlab-code
# Backend
PORT=3001
# File extensions to index (comma-separated)
INDEX_EXTENSIONS=.ts,.tsx,.js,.jsxservices:
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
volumes:
- qdrant_data:/qdrant/storage
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
volumes:
qdrant_data:
ollama_data:GET /api/search/syntax?q=MyComponent
- Durchsucht alle 140 Repos parallel über die GitLab Search API
- Streamt Treffer per SSE ins Frontend sobald sie reinkommen
- Response-Format:
text/event-stream
SSE Event Format:
{
"repo": "project-customer-abc",
"repoUrl": "https://gitlab.../project-customer-abc",
"file": "src/components/Dashboard.tsx",
"line": 42,
"preview": "import { MyComponent } from '@company/standard-app'",
"matchType": "import"
}GET /api/search/semantic?q=SSO+Authentifizierung+umsetzen&limit=20
- Embedded die Query mit Ollama
- Sucht in Qdrant nach ähnlichen Code-Chunks
- Gibt Top-N Treffer mit Score zurück
Response Format:
{
"results": [
{
"repo": "project-customer-xyz",
"file": "src/auth/SSOHandler.tsx",
"score": 0.89,
"preview": "// Handles SAML-based SSO login flow...",
"repoUrl": "https://gitlab.../..."
}
]
}POST /api/webhook/gitlab
- Empfängt Push-Events vom GitLab System Hook
- Re-embedded nur geänderte/neue Dateien
- Löscht Embeddings von gelöschten Dateien aus Qdrant
// Wichtige Methoden:
getGroupProjects(groupId: string): Promise<GitLabProject[]>
searchInRepo(projectId: number, query: string): Promise<SearchResult[]>
searchAllRepos(query: string): AsyncGenerator<SearchResult> // für SSE
getFileContent(projectId: number, filePath: string): Promise<string>
getChangedFiles(projectId: number, commitBefore: string, commitAfter: string): Promise<string[]>// Wichtige Methoden:
indexAllRepos(): Promise<void> // initialer Bulk-Index
indexRepo(projectId: number): Promise<void>
indexFile(projectId: number, filePath: string): Promise<void>
deleteFileFromIndex(projectId: number, filePath: string): Promise<void>
chunkFile(content: string, filePath: string): Chunk[]Chunking-Strategie: Dateien werden in Chunks von ~500 Tokens aufgeteilt, mit 50 Token Overlap. Jeder Chunk speichert als Payload: { repo, repoId, filePath, startLine, endLine, language }.
embed(text: string): Promise<number[]>
embedBatch(texts: string[]): Promise<number[][]>ensureCollection(): Promise<void>
upsertVectors(points: QdrantPoint[]): Promise<void>
search(queryVector: number[], limit: number): Promise<ScoredPoint[]>
deleteByFilter(filter: QdrantFilter): Promise<void>pnpm run index:all
Das Script scripts/index-all.ts:
- Holt alle Projekte der GitLab-Gruppe
- Iteriert über alle relevanten Dateien (gefiltert nach
INDEX_EXTENSIONS) - Chunked und embedded jede Datei
- Schreibt alles in Qdrant
- Zeigt Fortschritt mit einer Progress-Bar (z.B.
cli-progress)
Hinweis: Dieser Prozess kann beim ersten Mal mehrere Stunden dauern, je nach Größe der Repos. Empfehlung: Über Nacht laufen lassen.
- Einzelnes Suchfeld
- Toggle zwischen "Syntaktisch" und "Semantisch"
- Debounced Input (300ms)
- Zeigt Ergebnisse gruppiert nach Repo
- Bei syntaktischer Suche: Ergebnisse erscheinen live per SSE
- Bei semantischer Suche: Ergebnisse erscheinen nach Abschluss, sortiert nach Score
- Repo-Name mit Link zu GitLab
- Dateipfad mit Link zur Datei
- Code-Preview mit Syntax-Highlighting (
prism-react-rendererodershiki) - Bei semantischer Suche: Ähnlichkeits-Score als Badge
export interface SearchResult {
repo: string
repoId: number
repoUrl: string
file: string
fileUrl: string
line?: number
preview: string
matchType: 'syntax' | 'semantic'
score?: number // nur bei semantic
}
export interface SearchRequest {
query: string
mode: 'syntax' | 'semantic'
limit?: number
}
export interface IndexStatus {
totalRepos: number
indexedRepos: number
totalChunks: number
lastUpdated: string
}Damit der Index bei jedem Push automatisch aktualisiert wird, muss in der selbst-gehosteten GitLab-Instanz ein System Hook eingerichtet werden:
Admin Area → System Hooks → URL: http://your-backend:3001/api/webhook/gitlab
Push events: ✓
Secret Token: (optional, empfohlen)
- Monorepo initialisieren mit pnpm Workspaces
packages/sharedaufsetzen mit Types- Docker Compose aufsetzen und starten (
docker compose up -d) - Ollama Model pullen (
ollama pull nomic-embed-text) - Backend aufsetzen:
- Fastify Grundstruktur
- GitLab Service (erst nur
getGroupProjects+searchInRepo) - Syntaktische Such-Route mit SSE
- Frontend aufsetzen:
- Vite React Grundstruktur
- SearchBar + ResultList Komponenten
- SSE Hook für live Ergebnisse
- Syntaktische Suche end-to-end testen
- Ollama + Qdrant Services implementieren
- Indexing Script bauen und initialen Index erstellen
- Semantische Such-Route implementieren
- Webhook Handler implementieren
- Rate Limiting: Wie viele parallele GitLab API Calls sind auf eurer Instanz okay? Startpunkt: 20 parallele Requests, anpassbar per Env-Variable.
- Embedding-Modell:
nomic-embed-textist ein guter Start. Falls die semantische Qualität nicht reicht, kann aufmxbai-embed-largegewechselt werden. - Code-only oder auch Kommentare/Commit-Messages? Aktuell: nur Code-Dateien. Könnte später erweitert werden.
- Authentifizierung der Web-App: Erstmal kein Auth (internes Tool). Kann später per GitLab OAuth ergänzt werden.