AshwinSathian/github-issue-analyzer
Fastify + TypeScript service that caches GitHub issues locally (SQLite) and lets you analyze the cached data with a local LLM (Ollama by default).
Fastify + TypeScript service that caches GitHub issues locally (SQLite) and lets you analyze the cached data with a local LLM (Ollama by default).
POST /scan: fetch open issues for owner/repo, strip pull requests, and cache them in SQLite with simple upserts so repeated scans keep all repos synchronized.POST /analyze: run prompt-driven LLM summarization over cached issues. The analysis task uses map-reduce-style chunking to stay within configured budgets../data/cache.db (or another STORAGE_PATH), and the default LLM provider targets a locally hosted Ollama instance (LLM_PROVIDER=ollama), so you can run the service without cloud costs.better-sqlite3) for durable, inspectable cachingnpm install.env.example to .env and adjust any overrides (see the Configuration section below).npm run dev to start the Fastify dev server and watch for changes. Health lives at http://localhost:3000/health.npm run build && npm startLLM_PROVIDER/OLLAMA_BASE_URL to another provider) before hitting /analyze.Environment variables are validated with zod at startup, so the server fails fast on missing or malformed values.
PORT: HTTP port (default 3000).LOG_LEVEL: Fastify/Pino log level (trace|debug|info|warn|error|fatal).GITHUB_TOKEN: Optional GitHub PAT to raise rate limits while scanning.STORAGE_PATH: Path to the SQLite file (./data/cache.db by default).LLM_PROVIDER: Provider identifier (ollama by default, but pluggable adapters can honor other values).OLLAMA_BASE_URL: Target URL when using the Ollama provider (http://localhost:11434 default).OLLAMA_MODEL: Ollama model to send prompts to (default llama3.1:8b).LLM_TEMPERATURE: Sampling temperature (0..2, default 0.2).LLM_MAX_OUTPUT_TOKENS: Maximum token budget for a single LLM output (default 900).CONTEXT_MAX_TOKENS: Total token budget for the prompt context (8192 default).PROMPT_MAX_CHARS: Maximum characters of prompt text before truncation (8000 default).ANALYZE_MAX_ISSUES: Max number of cached issues the analyzer will accept (200 default).ISSUE_BODY_MAX_CHARS: Cap on characters pulled from each issue body before budgeting (4000 default).POST /scanPOST http://localhost:3000/scan
{
"repo": "vercel/turbo"
}
Responses look like:
{
"repo": "vercel/turbo",
"issues_fetched": 42,
"cached_successfully": true
}
repo must be owner/name (single slash, no spaces); invalid formats return 400 INVALID_REPO.filterPullRequests ensures only issues remain)./analyze, so run a scan once per repo before analyzing.GITHUB_TOKEN helps avoid rate limits; /scan returns 429 GITHUB_RATE_LIMIT if unauthenticated requests trigger GitHub rate throttling.POST /analyzePOST http://localhost:3000/analyze
{
"repo": "vercel/turbo",
"prompt": "Summarize the top blockers and quick wins for this repo."
}
{
"analysis": "..."
}
/analyze before /scan returns 404 REPO_NOT_SCANNED.runAnalysis builds a budget plan (buildBudgetPlan) based on CONTEXT_MAX_TOKENS, ANALYZE_MAX_ISSUES, ISSUE_BODY_MAX_CHARS, and PROMPT_MAX_CHARS. When issues overflow the budget, the endpoint switches to map-reduce chunking (Log metadata includes mode and chunkCount).400 with PromptTooLongError or ContextBudgetError messages and suggests lowering ANALYZE_MAX_ISSUES, ISSUE_BODY_MAX_CHARS, or increasing CONTEXT_MAX_TOKENS.503 (LLMConnectionError/LLMModelError) or 502 (LLMResponseError); ensure Ollama is running and the specified model is pulled locally.analysis string). The service does not call GitHub again during /analyze.analysis string escapes line breaks as \n. Add ?format=text or send Accept: text/plain to get a CLI-friendly text/plain response with actual newlines (plus a trailing newline for terminal readability)../data/cache.db by default), easy to inspect with CLI tools when debugging.issueRepository performs batched upserts, so re-scanning repositories replaces stale issue data cleanly.Use scripts/demo.sh to exercise the service end-to-end.
bash scripts/demo.sh
The script hits /health, /scan (defaulting to vercel/turbo), and /analyze (asking for top blockers). You can override the base URL, repo, and prompt via BASE_URL, REPO, and PROMPT environment variables or positional arguments.
Manual commands (copy-paste):
curl -sSf http://localhost:3000/health
curl -sSf http://localhost:3000/scan \
-H "Content-Type: application/json" \
-d '{"repo":"nestjs/nest"}'
curl -sSf http://localhost:3000/analyze \
-H "Content-Type: application/json" \
-d '{"repo":"nestjs/nest","prompt":"Summarize blockers and quick wins."}'
curl -sSf "http://localhost:3000/analyze?format=text" \
-H "Content-Type: application/json" \
-H "Accept: text/plain" \
-d '{"repo":"nestjs/nest","prompt":"Summarize blockers and quick wins."}'