RepoNarrate is a full-stack AI changelog intelligence app. It connects to a GitHub repository, syncs commits and releases, uses AI to turn raw commit history into readable changelog notes, and presents the results through commits, releases, semantic search, and analytics dashboards.
In simple terms: connect a repo, let the backend sync it, then review clean AI-generated explanations of what changed.
| Landing Page | Repository Dashboard |
![]() |
![]() |
| Repository Workspace | Commit Intelligence |
![]() |
![]() |
| AI Commit Summary | Analytics |
![]() |
![]() |
- GitHub OAuth login
- Connect public or private GitHub repositories available to the signed-in user
- Background repository sync with live progress
- AI-generated commit changelog entries
- AI-generated diff summaries
- AI commit categories:
breaking,feature,fix,chore,docs,refactor - AI-generated release summaries grouped by change type
- Natural-language semantic search over commits
- Repository analytics with category and monthly commit charts
- Protected dashboard using httpOnly cookie authentication
- Live sync progress over Server-Sent Events, with polling as an automatic fallback
- Incremental GitHub sync (conditional ETag +
since+ pagination) for cheap re-syncs - Optional single-call AI analysis that cuts ~3 LLM calls/commit down to 1
- Horizontally scalable: split web/worker processes with distributed rate limiting
- Prometheus-style
/metricsendpoint and TypeORM migrations - Swagger API docs in development/local mode
All scaling and cost features are behind environment flags that default to the original single-process behavior — see Scaling & Feature Flags.
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | Next.js 14, React 18, TypeScript | App Router pages, SSR, client components, typed UI |
| Styling | Tailwind CSS, custom CSS variables, Geist fonts | Dark dashboard design system |
| Charts | Recharts | Commit analytics visualizations |
| API client | Axios and server-side fetch helpers | Browser and SSR API calls |
| Backend | NestJS 11, TypeScript | Modular REST API |
| Database | PostgreSQL with pgvector | Relational data plus vector search |
| ORM | TypeORM | Entity mapping and repository access |
| Queue | Bull with Redis | Background repository sync jobs |
| Realtime | Redis pub/sub + SSE | Live sync progress push (poll fallback) |
| Rate limiting | Redis token bucket | Distributed per-provider AI throttling |
| Observability | Prometheus-style /metrics |
Queue depth, AI/GitHub call counters |
| Migrations | TypeORM CLI + runtime | Additive schema deltas over a frozen baseline |
| Auth | GitHub OAuth, Passport, JWT | Login and protected API routes |
| AI text | OpenAI SDK against Kilo Code gateway | Diff summaries, categories, changelog entries, release summaries |
| AI embeddings | Google Gemini embeddings | Semantic commit search |
| Security | Helmet, CORS, rate limiting, CSRF origin checks | Safer cookie-based web app |
github-ai-changelog/
backend/ NestJS API
src/
ai/ AI prompts and provider calls (incl. single-call analyzeCommit)
analytics/ Commit analytics API
auth/ GitHub OAuth and JWT auth
bootstrap/ Frozen ensureSchema() baseline (shared by web + worker)
commits/ Commit list, detail, filters, semantic search
common/ Guards, filters, DTOs, middleware, utilities
metrics/ Prometheus-style /metrics endpoint
ratelimit/ Distributed Redis rate limiter
pubsub/ Redis pub/sub for SSE status push
jobs/ Bull queue and sync processor (ROLE/concurrency aware)
migrations/ TypeORM migration deltas
releases/ Release list and detail API
repos/ Repo connection, GitHub API client, ingestion pipeline
users/ User storage and encrypted GitHub token access
main.ts Web entrypoint (HTTP API)
worker.ts Worker entrypoint (sync processor, no HTTP)
data-source.ts Standalone DataSource for the TypeORM CLI
frontend/ Next.js app
src/
app/ App Router pages and layouts
components/ UI, dashboard, repo, commit, release, chart components
lib/ API helpers, types, formatting, config
docker-compose.yml Local PostgreSQL + Redis services
dev.ps1 Windows development startup helper
dev.sh Unix-style development startup helper
User
-> signs in with GitHub
-> connects a repository
-> backend verifies the repo with GitHub
-> backend queues a sync job
-> worker fetches commits, diffs, and releases
-> AI summarizes and categorizes changes
-> PostgreSQL stores repo, commit, release, and embedding data
-> frontend shows commits, release notes, search, and analytics
Important flow:
- The user signs in through
GET /auth/github. - GitHub redirects back to
GET /auth/github/callback. - The backend stores/updates the user and encrypts the GitHub access token.
- The backend creates a JWT and sets it as an httpOnly
tokencookie. - The dashboard reads protected data using that cookie.
- When a repo is connected, the backend creates a repo record and queues a Bull job.
- The job syncs commits/releases and stores AI-enriched results (in the web process by default, or a separate worker process when split).
- The frontend shows live progress via SSE when enabled, otherwise polls repo status while sync is running.
The backend is a NestJS app. It is organized by feature modules:
| Module | Responsibility |
|---|---|
AuthModule |
GitHub OAuth, JWT cookie auth, logout, current user |
UsersModule |
User lookup, upsert, encrypted GitHub access token storage |
ReposModule |
Connect/list/delete repos, queue sync, get status, GitHub API client |
JobsModule |
Bull queue setup and repo sync processor |
AiModule |
Diff summaries, categories, changelog entries, release summaries, embeddings |
CommitsModule |
Commit list/detail/filtering and semantic search |
ReleasesModule |
Release list and release detail lookup |
AnalyticsModule |
SQL aggregation for dashboard charts |
MetricsModule |
Prometheus-style /metrics (queue depth, AI/GitHub counters) |
RatelimitModule |
Distributed Redis rate limiter for AI providers |
RedisPubSubModule |
Pub/sub channel powering the SSE status stream |
The web entry point is backend/src/main.ts. It sets up:
- schema bootstrap (
ensureSchema()) then TypeORM migration deltas - cookie parsing
- GitHub OAuth session state support
- CORS for the frontend URL
- Swagger in development/local mode
- rate limiting (skips
/health,/metrics, status/stream endpoints) - CSRF origin/referer checks for mutating requests
- global DTO validation
- global exception filtering
backend/src/worker.ts is the worker entry point. It boots the same modules
without an HTTP server so Bull processors run in a separate process. The
ROLE env var (all default | web | worker) selects the mode; all
keeps the original single-process behavior.
The app uses PostgreSQL. The frozen idempotent baseline is ensureSchema() in
backend/src/bootstrap/ensure-schema.ts; all later schema changes are additive
TypeORM migrations in backend/src/migrations/ that run automatically on web
boot (after the baseline). For example, repos.commits_etag is added by a
migration, not the baseline.
| Table | Purpose |
|---|---|
users |
GitHub user profile and encrypted GitHub access token |
repos |
Connected repositories and sync status |
commits |
Raw commit metadata plus AI summary/category/changelog |
commit_embeddings |
pgvector embeddings for semantic search |
releases |
GitHub releases plus AI summaries and grouped change arrays |
release_commits |
Many-to-many link between releases and commits |
Commit SHA uniqueness is scoped by repository using repo_id + sha, because the same commit SHA can exist across different repositories.
Most routes are protected with JWT cookie auth. The main endpoints are:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
Backend health check |
GET |
/metrics |
Prometheus-style metrics (unauthenticated) |
GET |
/auth/github |
Start GitHub OAuth |
GET |
/auth/github/callback |
Finish OAuth, set cookie, redirect |
GET |
/auth/me |
Get current user |
POST |
/auth/logout |
Clear auth cookie |
POST |
/repos |
Connect a repository |
GET |
/repos |
List connected repositories |
GET |
/repos/:id |
Get one repository |
DELETE |
/repos/:id |
Remove a repository |
POST |
/repos/:id/sync |
Queue a repository sync |
GET |
/repos/:id/status |
Get sync status and progress |
GET |
/repos/:id/status/stream |
SSE stream of live sync status |
GET |
/repos/:repoId/commits |
List commits with filters |
GET |
/repos/:repoId/commits/:sha |
Get one commit |
POST |
/repos/:repoId/commits/search |
Semantic commit search |
GET |
/repos/:repoId/releases |
List releases |
GET |
/repos/:repoId/releases/tag/:tagName |
Get release by tag |
GET |
/repos/:repoId/analytics |
Get commit analytics |
In development/local mode, Swagger is available at:
http://localhost:3001/api
- Node.js LTS
- npm
- Docker Desktop or Docker Engine
- GitHub OAuth App
- Gemini API key
- Kilo Code API key
Clone into a path without spaces (e.g.
C:\dev\github-ai-changelog, notC:\My Projects\...). The dev scripts usenest start --no-shell, which fails when the project path contains spaces — see Troubleshooting.
Create a GitHub OAuth App with:
Homepage URL: http://localhost:3000
Authorization callback URL: http://localhost:3001/auth/github/callback
The backend currently requests these scopes:
user:email
repo
The repo scope is broad, but classic GitHub OAuth does not provide a private-repository read-only scope. A GitHub App or fine-grained token flow would be a better production option.
Create these files from the examples:
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env.localOn Windows PowerShell:
Copy-Item backend/.env.example backend/.env
Copy-Item frontend/.env.example frontend/.env.localRequired backend variables:
PORT=3001
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_NAME=changelog_db
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=your_64_char_hex_jwt_secret_here
JWT_EXPIRES_IN=7d
SESSION_SECRET=your_64_char_hex_session_secret_here
ENCRYPTION_KEY=your_64_char_hex_encryption_key_here
GITHUB_CLIENT_ID=your_github_oauth_client_id
GITHUB_CLIENT_SECRET=your_github_oauth_secret
GITHUB_CALLBACK_URL=http://localhost:3001/auth/github/callback
GEMINI_API_KEY=your_gemini_api_key
KILOCODE_API_KEY=your_kilo_code_key
KILOCODE_MODEL=nvidia/nemotron-3-super-120b-a12b:free
FRONTEND_URL=http://localhost:3000Generate secrets with:
openssl rand -hex 32ENCRYPTION_KEY must be exactly 64 hex characters because it is used as a 32-byte AES-256-GCM key.
NEXT_PUBLIC_API_URL=http://localhost:3001
BACKEND_URL=http://localhost:3001
NEXT_PUBLIC_STATUS_TRANSPORT=pollNEXT_PUBLIC_API_URL is used by browser-side Axios calls. BACKEND_URL is used by server-side Next.js fetch helpers. NEXT_PUBLIC_STATUS_TRANSPORT (poll default | sse) selects how the dashboard tracks sync progress; set to sse only together with the backend's STATUS_TRANSPORT=sse, and rebuild the frontend after changing it.
Every flag below is optional and defaults to the original single-process
behavior, so the app runs unchanged with none of them set. They are
documented in backend/.env.example.
| Variable | Default | Effect when changed |
|---|---|---|
ROLE |
all |
web (API only) or worker (sync only, no HTTP). Set per process at launch, not in shared .env. |
SYNC_CONCURRENCY |
1 |
Concurrent repo syncs per worker. Only safe with RATE_LIMITER=redis. |
RATE_LIMITER |
memory |
redis enables distributed per-provider AI throttling (required for >1 process or concurrency >1). |
KILO_MIN_INTERVAL_MS / GEMINI_MIN_INTERVAL_MS |
400 / 800 |
Min ms between AI calls per provider. |
SYNC_RATE_MAX / SYNC_RATE_DURATION_MS |
unset / 1000 |
Optional coarse Bull job-rate cap. |
AI_COMBINED_ANALYSIS |
off |
on collapses 3 LLM calls/commit into 1 structured call (per-field fallback on parse failure, so never worse). |
INCREMENTAL_SYNC |
off |
on enables conditional ETag + since + Link pagination; lifts the 100-commit cap up to MAX_COMMITS_PER_SYNC. |
MAX_COMMITS_PER_SYNC |
500 |
Upper bound when INCREMENTAL_SYNC=on. |
STATUS_TRANSPORT |
poll |
sse pushes progress over Redis pub/sub + SSE. Pair with frontend NEXT_PUBLIC_STATUS_TRANSPORT=sse. |
Rules of thumb:
- The horizontal-scale payoff only lands when you actually run split
web+workerprocesses withRATE_LIMITER=redis. - Never raise
SYNC_CONCURRENCYor run multiple workers on the in-memory limiter. - SSE needs both sides set; the frontend must be rebuilt after changing
NEXT_PUBLIC_*. If the stream errors, the dashboard auto-falls back to polling.
cd backend
npm run build
# terminal 1 — API only
ROLE=web node dist/main
# terminal 2 — sync worker only
ROLE=worker SYNC_CONCURRENCY=4 node dist/workerOn Windows PowerShell, set the variable first: $env:ROLE='worker'; node dist/worker.
Install backend dependencies:
cd backend
npm installInstall frontend dependencies:
cd frontend
npm installFrom the repository root, start PostgreSQL and Redis:
docker compose up -dStart the backend:
cd backend
npm run start:devStart the frontend in another terminal:
cd frontend
npm run devOpen:
Frontend: http://localhost:3000
Backend: http://localhost:3001
Swagger: http://localhost:3001/api
This repo also includes startup helpers.
Windows PowerShell:
.\dev.ps1Unix-style shell:
./dev.shThe helpers start Docker services, backend, frontend, and write logs to .dev-logs/.
Useful PowerShell preflight:
.\dev.ps1 -CheckOnlyStart and stop after both services become ready:
.\dev.ps1 -ExitAfterReadyBackend:
cd backend
npm run start:dev
npm run start:worker # sync worker, no HTTP (ROLE=worker)
npm run build
npm run test
npm run test:e2e
npm run lint
npm run migration:generate -- src/migrations/Name
npm run migration:run
npm run migration:revertFrontend:
cd frontend
npm run dev
npm run build
npm run lint- Auth uses GitHub OAuth.
- The backend stores the app session as a JWT in an httpOnly cookie.
- The GitHub access token is encrypted before being saved in the database.
- Cookie-based auth is protected with SameSite cookies and Origin/Referer checks for mutating requests.
- CORS allows only the configured frontend origin.
- Helmet adds common security headers on the backend.
- The frontend adds basic security response headers through Next.js config.
- Swagger is disabled outside development/local mode.
- The global exception filter avoids returning raw server stack traces for unexpected errors.
Repository sync is intentionally asynchronous:
- The API validates and stores the repository.
- A Bull job is queued in Redis.
- The worker fetches GitHub commits and releases.
- AI providers summarize, categorize, and embed commit data.
- PostgreSQL stores the results.
- The repo status becomes
readyorerror.
If the browser is closed during sync, the backend job continues as long as the backend, Redis, and PostgreSQL are still running. If the whole dev stack is stopped, the sync may be interrupted. Running sync again continues by skipping complete commits and reprocessing incomplete AI fields.
With INCREMENTAL_SYNC=on, re-syncing an unchanged repo does almost no work: the GitHub since filter (and conditional ETag) return nothing new, so no commits are reprocessed and no AI calls are made. With AI_COMBINED_ANALYSIS=on, each newly processed commit makes one combined AI call instead of three. With STATUS_TRANSPORT=sse, the worker publishes progress that the dashboard receives as a live push.
Symptom: npm run start:dev / npm run start:debug (and dev.ps1 / dev.sh,
which call them) fail to launch the backend with a quoting / "cannot find
module" style error when the repository lives in a path that contains spaces
(e.g. C:\Users\me\My Projects\...).
Cause: those scripts pass --no-shell to nest start. The NestJS CLI quotes
the compiled entry path for the shell, but in --no-shell mode there is no
shell to strip the quotes, so the quote characters become part of the path and
Node cannot resolve it. This is a NestJS CLI limitation, not an app bug.
Fixes, in order of preference:
- Clone the repository into a path without spaces (simplest, recommended).
- Or use the tsc-based watcher, which does not pass
--no-shell:cd backend && npm run start:dev:tsc(slower rebuilds than swc). - Or build and run without the watcher:
npm run build && npm run start:prod(no hot reload). The split commandsnode dist/main/node dist/workerare also unaffected.
Only the two --no-shell dev scripts are impacted; production start and the
web/worker split commands work regardless of path.
Check that:
backend/.envexistsJWT_SECRETis at least 32 charactersENCRYPTION_KEYis 64 hex characters- PostgreSQL and Redis are running
DB_*andREDIS_*values match your local ports
Check that:
- GitHub OAuth callback URL is exactly
http://localhost:3001/auth/github/callback GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRETare correctFRONTEND_URL=http://localhost:3000
Check backend logs in .dev-logs/backend.log or the backend terminal. Common causes are:
- GitHub token expired or revoked
- GitHub API rate limit
- missing AI provider key
- Redis not running
- backend stopped during the job
The frontend tracks /repos/:id/status while a repo is pending or syncing — by polling, or over SSE when STATUS_TRANSPORT=sse is set on both sides. If SSE errors, it automatically falls back to polling. If the backend has finished but the dashboard looks old, refresh the dashboard or open the repo page to force a fresh server fetch. The dashboard also auto-resumes tracking for in-flight repos.
- By default sync fetches the latest 100 commits per repository;
INCREMENTAL_SYNC=onlifts this up toMAX_COMMITS_PER_SYNC. - TypeORM migrations exist for additive deltas; the frozen
ensureSchema()baseline remains the bootstrap and is intentionally not migrated. - AI output can vary and should be reviewed before using it as official release communication.
AI_COMBINED_ANALYSISeffectiveness depends on the configured model returning parseable JSON (it safely falls back to per-field calls otherwise). - Classic GitHub OAuth requires the broad
reposcope for private repository access.
This repository includes a LICENSE file. Review it before reusing or distributing the project.






