A lightweight service that collects feedback from anywhere via API and automatically creates well-formatted GitHub issues on your target repository.
Any Source (website, app, Slack bot, form, etc.)
│
▼
POST /api/feedback ──→ Validate ──→ AI Rewrite (OpenRouter) ──→ GitHub Issue
│ │ │
│ ▼ ▼
│ Structured issue Created on your
│ (title, body, labels) target GitHub repo
▼
201 { issueNumber, issueUrl, title }
- Any client sends feedback via
POST /api/feedback - Input is validated with Zod
- AI (via OpenRouter) rewrites the raw feedback into a professional GitHub issue — using project context from
PROJECT_CONTEXT.mdto match your project's domain, tech stack, and terminology - The issue is created on the configured GitHub repository via GitHub App authentication
- Returns the issue number and URL
Issue Bridge reads PROJECT_CONTEXT.md at startup to understand your project. The AI uses this context to write issues that reference your actual components, architecture, and conventions — instead of generic descriptions.
Place a PROJECT_CONTEXT.md file in the project root with your project's README or documentation. The content is truncated to ~2000 chars for token efficiency.
- Runtime: Node.js 18+ (native fetch)
- Framework: Express 5 + TypeScript
- AI: OpenRouter API (any model — Claude, GPT, etc.)
- GitHub: Octokit + GitHub App authentication
- Validation: Zod 4
- Testing: Vitest + Supertest
src/
├── index.ts # Server entry point
├── app.ts # Express app factory
├── config/
│ ├── env.ts # Environment validation (fail-fast)
│ └── project-context.ts # Loads PROJECT_CONTEXT.md at startup
├── types/
│ ├── feedback.ts # Request/response types
│ └── issue.ts # StructuredIssue + ProjectContext interfaces
├── prompts/
│ └── feedback-to-issue.ts # AI system prompt + context builder
├── services/
│ ├── feedback.service.ts # Orchestrator: context → AI → GitHub
│ ├── openrouter.service.ts # AI rewrite via OpenRouter
│ └── github.service.ts # GitHub App auth + issue creation
├── middleware/
│ ├── validate-feedback.ts # Zod request validation
│ └── error-handler.ts # Global error handler
├── routes/
│ └── feedback.route.ts # POST /api/feedback
└── __tests__/
├── openrouter.service.test.ts
├── github.service.test.ts
└── feedback.integration.test.ts
- Node.js 18+
- pnpm
- A GitHub App (setup guide)
- An OpenRouter API key (openrouter.ai)
pnpm install
cp .env.example .env
# Fill in your credentials in .env| Variable | Description |
|---|---|
PORT |
Server port (default: 3000) |
OPENROUTER_API_KEY |
OpenRouter API key |
OPENROUTER_MODEL |
AI model to use (default: anthropic/claude-sonnet-4) |
GITHUB_APP_ID |
GitHub App ID |
GITHUB_APP_PRIVATE_KEY |
GitHub App private key (PEM content, use \n for newlines) |
GITHUB_APP_INSTALLATION_ID |
GitHub App installation ID |
GITHUB_DEFAULT_REPO_OWNER |
Default repo owner (used when repo not in request) |
GITHUB_DEFAULT_REPO_NAME |
Default repo name |
# Development (hot reload)
pnpm dev
# Production
pnpm build
pnpm start
# Type check
pnpm typecheck
# Tests
pnpm testCreate a GitHub issue from feedback. The feedback can come from any source — a website widget, mobile app, Slack bot, internal tool, or manual curl.
Request:
{
"feedback": "Login page is very slow on mobile Safari, sometimes shows blank screen",
"repo": "owner/repo"
}| Field | Type | Required | Description |
|---|---|---|---|
feedback |
string | Yes | Raw feedback text (1-5000 chars) |
repo |
string | No | Target repo in owner/repo format. Falls back to env defaults. |
Response (201):
{
"success": true,
"data": {
"issueNumber": 42,
"issueUrl": "https://github.com/owner/repo/issues/42",
"title": "Fix login page performance on mobile Safari"
}
}Error Response (400/502/500):
{
"success": false,
"error": "Validation failed",
"details": "..."
}{ "status": "ok" }- Go to GitHub → Settings → Developer settings → GitHub Apps → New GitHub App
- Fill in:
- App name:
issue-bridge(or any name) - Homepage URL:
http://localhost:3000 - Webhook: uncheck Active
- Permissions → Repository → Issues: Read & Write
- App name:
- Click Create GitHub App
- Copy the App ID from the app settings page
- Scroll down and click Generate a private key (downloads a
.pemfile) - Go to Install App tab → install on your account/org → select target repo(s)
- After installation, get the Installation ID from the URL:
github.com/settings/installations/12345678 ^^^^^^^^ Installation ID - Convert the
.pemfile to a single line for.env:awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' private-key.pem
ISC