Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to unsight.dev.
Important
Please be respectful and constructive in all interactions. We aim to maintain a welcoming environment for all contributors. Read our Code of Conduct
unsight.dev helps detect duplicate GitHub issues, areas of concern, and more across related repositories. It uses AI-powered embeddings and clustering to surface patterns that are hard to spot manually.
- Getting started
- Project structure
- Development workflow
- Tech stack
- Code style
- Submitting changes
- Pre-commit hooks
- Browser extension
- Using AI
- Questions
- License
If you just want to work on the frontend/UI, you can use the deployed API as a backend:
corepack enable
pnpm install
pnpm dev --ui-onlyThis starts a local dev server at http://localhost:3000 using the remote API at https://unsight.dev to populate it.
A full development environment requires a GitHub App and Cloudflare integration. See the Development environment section in the README for the complete walkthrough, which covers:
- Starting a local tunnel (ngrok)
- Creating a GitHub App for development
- Configuring secrets (
.envfile) - Linking your Cloudflare account via NuxtHub
unsight.dev is a pnpm monorepo with two packages:
unsight.dev/
├── packages/
│ ├── web/ # Nuxt 4 web application
│ │ ├── app/
│ │ │ ├── components/ # Vue components
│ │ │ ├── composables/ # Vue composables
│ │ │ ├── pages/ # File-based routing
│ │ │ └── app.vue # Root component
│ │ ├── server/
│ │ │ ├── api/ # API routes (Nitro)
│ │ │ ├── db/ # Drizzle schema + migrations
│ │ │ ├── routes/ # Server routes (webhooks, auth)
│ │ │ ├── tasks/ # Background tasks (indexing, cleanup)
│ │ │ └── utils/ # Server utilities (AI, clustering, etc.)
│ │ ├── modules/ # Custom Nuxt modules
│ │ ├── nuxt.config.ts
│ │ └── uno.config.ts
│ │
│ └── extension/ # Browser extension (Chrome + Firefox)
│ ├── components/ # Vue components
│ ├── entrypoints/ # WXT content script entry
│ └── wxt.config.ts
│
├── eslint.config.js # Root ESLint config
├── vite.config.ts # vite-plus config (pre-commit hooks)
└── package.json # Workspace root
Tip
For more about the Nuxt directory conventions, check out the Nuxt directory structure docs.
From the repository root:
# Development
pnpm dev # Start web app dev server
pnpm dev --ui-only # Start with remote API (no GitHub App needed)
pnpm dev:ext # Start browser extension dev server
# Code quality
pnpm lint # Run ESLint across the monorepo
pnpm test:types # TypeScript type checking (all packages)
pnpm test:unit # Run unit tests (all packages)From packages/web/:
pnpm build # Production build
pnpm preview # Preview production build
pnpm db:generate # Generate Drizzle ORM migrationsFrom packages/extension/:
pnpm dev # Dev mode (Chrome)
pnpm dev:firefox # Dev mode (Firefox)
pnpm build # Build for Chrome
pnpm build:firefox # Build for Firefox
pnpm zip # Zip for Chrome Web Store
pnpm zip:firefox # Zip for Firefox Add-onsBy default, the dev server auto-indexes nuxt/module-builder and nuxt/cli so you can see clusters without installing the GitHub App on any repositories.
You can customise this with the DEV_REPOS_TO_INDEX environment variable:
# disable entirely
DEV_REPOS_TO_INDEX=false
# specify custom repos
DEV_REPOS_TO_INDEX=unjs/h3,vuejs/coreSee the README for the full list of required environment variables. The key ones are:
| Variable | Purpose |
|---|---|
NUXT_WEBHOOK_GITHUB_SECRET_KEY |
GitHub webhook secret |
NUXT_GITHUB_APP_ID |
GitHub App ID |
NUXT_GITHUB_PRIVATE_KEY |
GitHub App private key (PKCS#8 format) |
NUXT_PUBLIC_GITHUB_APP_SLUG |
Your GitHub App slug |
NUXT_OAUTH_GITHUB_CLIENT_ID |
GitHub OAuth client ID |
NUXT_OAUTH_GITHUB_CLIENT_SECRET |
GitHub OAuth client secret |
NUXT_SESSION_PASSWORD |
Session encryption password |
NUXT_CLOUDFLARE_ACCOUNT_ID |
Cloudflare account ID (for local dev AI without Workers bindings) |
NUXT_CLOUDFLARE_API_TOKEN |
Cloudflare API token (for local dev AI without Workers bindings) |
NUXT_HUB_PROJECT_KEY |
NuxtHub project key (auto-generated) |
DEV_REPOS_TO_INDEX |
Preset repos for local dev |
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 (Vue 3) |
| Server engine | Nitro |
| Styling | UnoCSS (with Tailwind compat reset) |
| UI primitives | Reka UI |
| Database | Cloudflare D1 (SQLite) via Drizzle ORM |
| Vector search | Cloudflare Vectorize |
| AI / Embeddings | Cloudflare Workers AI |
| Clustering | ml-kmeans + ml-distance |
| GitHub integration | GitHub App via @octokit/rest |
| Authentication | GitHub OAuth via nuxt-auth-utils |
| Deployment | Cloudflare Workers via NuxtHub |
| Extension framework | WXT + Vue 3 |
| Package manager | pnpm (monorepo workspaces) |
| Task runner | vite-plus (vp) |
| Linting | ESLint 9 (flat config) with @antfu/eslint-config |
| Type checking | TypeScript + vue-tsc |
| CI | GitHub Actions |
Formatting is handled entirely by ESLint's stylistic rules via @antfu/eslint-config -- there is no Prettier in this project.
When committing, a pre-commit hook runs ESLint --fix on staged files automatically (via vite-plus). If you want to lint everything ahead of time:
pnpm lint- Avoid
any-- use proper types - Validate inputs rather than just asserting types
- Use Composition API with
<script setup lang="ts"> - Define props with TypeScript:
defineProps<{ text: string }>() - Keep components focused and functions short
<script setup lang="ts">
defineProps<{
title: string
score: number
}>()
</script>Server API routes live in packages/web/server/api/ and follow Nitro conventions:
- File names include the HTTP method:
repos.get.ts,dashboards.post.ts - Use
defineEventHandlerfor route handlers - Background work is handled via Nitro tasks in
server/tasks/
| Type | Convention | Example |
|---|---|---|
| Vue components | PascalCase | GitHubIssue.vue |
| Pages | kebab-case | index.vue, [issue].vue |
| Composables | camelCase + use prefix |
useRepos.ts |
| Server routes | kebab-case + method | repos.get.ts |
| Functions | camelCase | indexRepo, fetchClusters |
| Constants | SCREAMING_SNAKE_CASE | DEV_REPOS_TO_INDEX |
| Types/Interfaces | PascalCase | Issue, Cluster |
- Run linting:
pnpm lint - Run type checking:
pnpm test:types - Make sure the dev server starts without errors
- Fork the repository and create a branch from
main - Make your changes with clear, descriptive commits
- Push your branch and open a pull request
- Ensure CI checks pass (lint, type-check, provenance)
- Request review from maintainers
We use Conventional Commits. Since we squash on merge, the PR title becomes the commit message in main.
Format: type(scope): description
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
Examples:
fix: resolve clustering accuracy for small reposfeat: add dashboard sharingdocs: update contributing guidechore(deps): update nuxt to v4.1
Note
Use lowercase in your PR title. Individual commits within the PR don't need to follow this format since they'll be squashed.
The project uses vite-plus to manage pre-commit hooks. On every commit, ESLint --fix runs automatically on staged files matching *.{js,ts,tsx,vue,mjs,cjs,json,.*rc}.
You don't need to configure anything -- this is set up in vite.config.ts at the repo root.
The browser extension is in packages/extension/. It's a WXT content script that injects a "Similar Issues" panel into GitHub issue pages.
- Content script injects into
github.comissue pages - Fetches similar issues from the unsight.dev API
- Built separately from the web app
To work on the extension:
pnpm dev:ext # Start extension dev (Chrome)Extension releases are automated: bump the version with bumpp in packages/extension/, merge to main, and CI handles Chrome Web Store and Firefox Add-ons submission.
You're welcome to use AI tools to help you contribute, with two ground rules:
- Never let an LLM speak for you. When writing comments, issues, or PR descriptions, use your own words. Clarity beats polish.
- Never let an LLM think for you. If AI writes code for you, understand it before contributing it. Take personal responsibility for your contributions.
For more context, see Using AI in open source.
If you have questions or need help, feel free to open an issue for discussion.
By contributing to unsight.dev, you agree that your contributions will be licensed under the MIT License.