An AI-powered product management workspace. Write PRDs, plan roadmaps, prioritise features and synthesise research in one place, with an AI co-pilot (Google Gemini) that streams its answer and writes it directly into your documents.
AI workflows (system prompts in lib/prompts.ts)
- PRD — problem statement, success metrics, user stories, scope, requirements, open questions
- User Stories — Gherkin acceptance criteria, story points, dependencies, definition of done
- Roadmap — Now / Next / Later plan with sequencing rationale and dependencies
- Prioritization — RICE scoring, MoSCoW classification and trade-off analysis
- Research — turns interview notes and feedback into themes, quotes and opportunities
- Data — funnel analysis, anomalies, falsifiable hypotheses and experiment design
- Free Chat — general PM co-pilot with no fixed output format
Editor
- Rich text editing with Tiptap, auto-saved 2 seconds after you stop typing
- PRD, Stories, Roadmap and Research answers are written straight into a document
- Diff toggle on each AI answer shows which lines were added or removed
- Copy or download a document as Markdown or plain text
Workspace
- Folder tree in the left sidebar: create, rename and delete folders and documents
- Roadmap board, RICE / MoSCoW prioritisation panel, research insights and data panel
- Everything is saved to PostgreSQL through small REST routes
Tasks and GitHub
- Tasks tab: each task gets a key like
PROD-42and a "copy branch name" button (prod-42-add-csv-import) - Connect a GitHub App: pull requests whose branch, title or description mention a task key are linked automatically, or paste a PR URL on a task
- PR badges on tasks (open / draft / merged / closed), a PR list in the task detail, and a Delivery strip on each PRD ("6 of 9 tasks shipped")
- Optional automation (off by default): PR opened → task to Review, PR merged → Done, always noted in the task's activity
Keyboard shortcuts
| Shortcut | Action |
|---|---|
Cmd+K |
Command palette |
Cmd+N |
New note in the current folder |
Cmd+Enter |
Send AI message |
Cmd+/ |
Cycle right panel tabs |
? |
Show all shortcuts |
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS, shadcn/ui |
| Rich text | Tiptap |
| State | Zustand |
| Database | PostgreSQL (Neon) via Prisma 5 |
| AI | Google Gemini via @google/genai, streamed |
| Layout | react-resizable-panels |
git clone https://github.com/Ratangulati/Prodify.git
cd Prodify
npm installcp .env.example .env| Variable | Required | Description |
|---|---|---|
GEMINI_API_KEY |
Yes | Gemini API key from Google AI Studio |
DATABASE_URL |
Yes | Postgres connection string used by the app (Neon pooled URL) |
DIRECT_URL |
Yes | Postgres connection string used by Prisma migrations (Neon direct URL) |
GEMINI_MODELS |
No | Comma-separated models to try in order. Default: gemini-3.8-flash,gemini-3.6-flash,gemini-3.5-flash-lite |
GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET |
No | Only for pull-request linking. See GitHub pull-request linking |
Use .env (not .env.local): the Prisma CLI only reads .env. Never commit it — it is in .gitignore.
npm run db:migrate # prisma migrate deploy: creates the tables
npm run db:seed # optional: demo documents, features and insights (run once, on an empty DB)npm run devOpen http://localhost:3000.
npm test # Vitest: task keys, GitHub signatures, key matching, webhooks, manual linkingGitHub is never called in tests: requests go through a fake fetch, and the
webhook tests use an in-memory stand-in for the database.
AIChatPanel ──POST /api/ai──▶ route.ts ──▶ lib/ai.ts ──▶ Gemini
▲ │ (system prompt from lib/prompts.ts)
└──── streamed text ◀──────┘
- You pick a workflow tab (PRD, Stories, …) and type a prompt.
AIChatPanelsends the prompt, the conversation history for that workflow and (for document workflows) the current document text to/api/ai.app/api/ai/route.tsbuilds the system prompt for the workflow and callsstreamChatinlib/ai.ts.lib/ai.tstries each Gemini model in order. A 429 / 500 / 503 gets one retry after 1 second, then the next model is tried. A bad key or bad request stops straight away.- The text is streamed back to the browser with a
ReadableStreamand shown as it arrives. - If anything fails, the route returns a JSON error (or an error marker mid-stream) and the chat shows a clear message instead of a broken answer.
- For PRD, Stories, Roadmap and Research, the finished Markdown is converted to HTML and written into the matching document (a new one is created if the open document is a different type). Prioritization, Data and Free Chat answer in the chat only.
After an answer is written into a document, the chat shows a card with a Diff toggle. It compares the document before and after line by line using a longest-common-subsequence (LCS) diff, and colours added lines green and removed lines red. Documents over 400 lines skip the LCS step and show everything as removed then added, to keep it fast.
Tasks show the pull requests that deliver them, and each PRD shows how much has shipped. Prodify uses a GitHub App: it only sees the repositories you pick, webhooks are built in, and no long-lived token is stored. The server signs a short-lived token with the app's private key and exchanges it for a one-hour installation token whenever it needs to call GitHub.
- Automatically: a PR whose branch name, title or description contains a
task key (
prod-42-add-csv-import,PROD-42: add CSV import, "Closes PROD-42") is linked to that task when GitHub sends the webhook. - Manually: open a task and paste the PR's URL. Prodify checks with GitHub that the PR exists in a tracked repository.
A task counts as shipped when it's Done or one of its PRs is merged.
GitHub → Settings → Developer settings → GitHub Apps → New GitHub App:
| Setting | Value |
|---|---|
| Homepage URL | Your app URL, e.g. http://localhost:3000 |
| Setup URL | <app URL>/api/github/setup and tick Redirect on update |
| Webhook URL | <public URL>/api/github/webhook (for local use, the smee.io URL from step 3) |
| Webhook secret | A long random string: openssl rand -hex 32 |
| Repository permissions | Pull requests: Read-only (Metadata: Read-only is added automatically) |
| Subscribe to events | Pull request (installation events are always sent) |
| Where can it be installed | Only on this account (or any account, for an organisation) |
After creating it, note the App ID and the app's URL name (the slug:
github.com/apps/<slug>), then Generate a private key (a .pem file).
Print the private key as one line, with \n for each line break:
awk 'NF {printf "%s\\n", $0}' prodify-yourname.private-key.pemThen add to .env (paste that output inside the quotes):
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=prodify-yourname
GITHUB_WEBHOOK_SECRET=the-secret-from-step-1
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----\n"Keep the key out of git (.env is ignored) and delete the downloaded .pem once it's in .env.
Then apply the new tables with npm run db:migrate and restart the app.
GitHub can't reach localhost, so forward webhooks with smee.io:
# 1. Open https://smee.io/new and copy the channel URL into the app's Webhook URL
# 2. Forward it to the dev server:
npx smee-client --url https://smee.io/<your-channel> --target http://localhost:3000/api/github/webhook(ngrok works too: ngrok http 3000, then use
https://<id>.ngrok-free.app/api/github/webhook as the Webhook URL.)
To test duplicate handling, open the app's Advanced tab on GitHub and
click Redeliver on any delivery: the second copy is answered with
{"status":"duplicate"}.
In Prodify, click the GitHub icon in the left sidebar → Connect GitHub, install the app on your repositories, and back in Prodify pick which repositories to track. Optionally turn on Move tasks automatically.
- Every delivery's
X-Hub-Signature-256is checked against the raw body with the webhook secret (constant-time compare); anything else gets a 401. - Each delivery id is stored in the same transaction as its changes: a repeated delivery is skipped, and a failed one rolls back so redelivering it (the app's Advanced tab) works. GitHub doesn't retry failed deliveries on its own. Events older than the stored PR data are ignored.
- A repository can be tracked by only one workspace, and the repository decides which workspace's tasks a webhook may touch.
- Prodify has no sign-in yet, so anyone who can open the app can change the GitHub settings. Only run it where that's acceptable.
WebhookDeliverykeeps every delivery id. Clear old ones occasionally:DELETE FROM "WebhookDelivery" WHERE "receivedAt" < now() - interval '30 days';- GitHub API errors, including rate limits, are shown in the UI with when to retry.
app/
api/ai/route.ts Gemini streaming endpoint
api/db/* REST routes per table (documents, features, filenodes, insights, messages)
api/tasks/* Tasks, and linking/unlinking PRs on a task
api/github/* Install + setup flow, repos, settings, webhook
page.tsx, layout.tsx
components/
AppShell.tsx Three-panel layout + global shortcuts
editor/ EditorPanel (title, export) and Editor (Tiptap)
sidebar/ FileExplorer, AIChatPanel (workflows, diff), Research and Data panels
tasks/ Tasks board, task detail, PR badge
roadmap/, prioritization/, modals/, ui/
lib/
ai.ts Gemini client, model fallback, error messages (server only)
ai-client.ts Browser-side AI error helpers
prompts.ts System prompt per workflow
store.ts Zustand store; updates the UI first, then syncs to the API
prisma.ts Prisma client singleton
tasks.ts, task-dto.ts Task keys, branch names, "shipped" rule
use-tasks.ts Browser store for tasks (polls while visible)
github/ App auth + API client, signature check, key matching, webhook, manual linking
types.ts
prisma/
schema.prisma, seed.ts, migrations/
- Import the repo in Vercel (framework: Next.js).
- Add
GEMINI_API_KEY,DATABASE_URLandDIRECT_URLin Project Settings → Environment Variables (plus the fourGITHUB_*variables if you use pull-request linking, with the app's Setup and Webhook URLs pointing at your Vercel domain). - Run
npm run db:migrateonce against the production database.
The build runs prisma generate && next build.