@AGENTS.md
Brand: "Cut the noise. Pick your AI stack." Domain: aichitect.dev | Stack: Next.js 16 + React Flow + Three.js + Tailwind v4 + TypeScript
This is a greenfield project. Always build clean:
- No adapter layers — if the DB schema or TypeScript type is designed a certain way, the rest of the code conforms to it. Never write a mapping/transform function to bridge an old shape to a new one just to avoid touching other files.
- No backwards-compat shims — if a type changes, update all call sites. If a field is renamed, rename it everywhere. The cost of a clean sweep is low at this stage.
- No workarounds — if something is wrong, fix the root cause. Don't patch around it with defensive code, re-exports, or fallback casts that exist only to preserve a broken old shape.
The exception: once we have production data that can't be migrated cheaply, or a public API contract we can't break, we'll revisit. Until then, always reach for the clean solution.
- Docker only — never run
npmornodelocally. All dev runs through docker-compose. - Hot reload via volume mounts. Rebuild image only when adding new packages to
package.json.
| Command | When to use |
|---|---|
make run |
Start dev server (applies local migrations + seeds, then tails logs) |
make down |
Stop and remove containers |
make rebuild |
After adding packages to package.json — full reinstall + rebuild |
make restart |
Restart containers without rebuilding |
make shell |
Open a shell inside the running container |
make logs |
Tail logs without restarting |
make typecheck |
Run tsc --noEmit inside the container |
make lint |
Run ESLint |
make format |
Run Prettier |
make test |
Run Vitest test suite |
make check |
Run lint + typecheck + test in one shot |
make sync-counts |
After modifying data files — syncs tool/stack/relationship counts in README and CLAUDE.md |
make seed-validate |
Validate JSON data integrity without a DB connection |
make db-push-local |
Apply pending migrations to local Postgres |
make db-push |
Promote verified migrations to remote Supabase |
make db-migrate name=X |
Scaffold a new migration file |
make db-reset-local |
Wipe and re-apply all migrations locally (destructive) |
Branching:
- Always branch from the current active branch:
git checkout -b feat/AIC-N - Ramy ensures the active branch is the correct base before starting — no merging or rebasing needed.
Commits (Ramy signs — never run git commit):
- Stage specific files:
git add <files> - Print the commit message for Ramy to copy and run himself
- Ramy runs
git commit— his signing config applies automatically - After Ramy confirms the commit, open the PR with
gh pr create
Pre-PR gate — always run make check before opening a PR. It runs lint + typecheck + tests in one shot. Do not open a PR if make check fails.
PR format (concise):
- Title:
feat(scope): AIC-N — short description(≤60 chars) - Body: 3–5 bullet summary of what changed and why, plus a short test plan checklist
- No walls of text, no restating every file changed
app/
page.tsx → landing page (hero, view cards, OSS banner, footer)
layout.tsx → root metadata (OG, JSON-LD, robots, favicon)
opengraph-image.tsx → root OG image (1200×630, edge runtime)
error.tsx → root error boundary
robots.ts → /robots.txt
sitemap.ts → /sitemap.xml
explore/ → full tool graph (FilterPanel + ExploreGraph + DetailPanel)
og/route.tsx → OG image for /explore
stacks/
StacksClient.tsx → layout shell: wires state + delegates to sub-components
components/
StackSidebar.tsx → cluster tabs + stack list aside
StackDetailHeader.tsx → detail header; owns killOpen state internally
MobileStackPicker.tsx → full-screen mobile stack overlay
og/route.tsx → OG image for /stacks
opengraph-image.tsx → stacks OG image generator
builder/
BuilderClient.tsx → wires useBuilderState + renders BuilderSlotList / BuilderGraph / MobileSlotPicker
components/
BuilderSlotList.tsx → desktop aside: slot accordion + compare buttons + StackHealthPanel
MobileSlotPicker.tsx → BottomSheet wrapper for mobile slot picking
og/route.tsx → OG image for /builder
opengraph-image.tsx → builder OG image (edge runtime, reads ?s= param)
compare/
CompareClient.tsx → wires tool pair + relationships, renders ComparisonPanel
[toolA]/[toolB]/page.tsx → side-by-side comparison view
og/ → (route exists but no OG image defined yet)
genome/
GenomeClient.tsx → state machine (step, detectedIds, workflowIds, URL sync) + context provider + Suspense
GenomeContext.tsx → GenomeData interface, GenomeDataCtx, useGenomeData hook
error.tsx → genome-specific error boundary
components/
ScanStep.tsx → dependency file input tabs + detect step
WorkflowStep.tsx → workflow group selection step
ResultsView.tsx → composes FitnessGauge + Stat + SlotGrid + MissingPanel
FitnessGauge.tsx → SVG arc gauge for genome tier
Stat.tsx → single stat display (label + value)
SlotGrid.tsx → grid of recommended tools per slot
MissingPanel.tsx → list of unfilled slots grouped by priority
ProgressDots.tsx → step progress indicator dots
ChallengePanel.tsx → genome challenge/quiz panel
GraduationBanner.tsx → banner shown when genome score is high
RoastPanel.tsx → AI-powered stack roast panel
og/route.tsx → OG image for /genome
match/ → stack quiz / match flow
profile/[username]/ → public user profile page
changelog/ → product changelog
api/
roast/route.ts → GET — AI stack roast (Google AI)
challenge/route.ts → POST — genome challenge submission
cron/sync-health/route.ts → GET — nightly GitHub health sync (cron, requires CRON_SECRET)
badge/
route.ts → GET /badge?s=cursor,langgraph → shields.io-style SVG (edge runtime)
tool/[toolId]/route.ts → GET /badge/tool/:id → per-tool badge (edge runtime)
auth/
callback/route.ts → GitHub OAuth callback handler (Supabase Auth)
hooks/
useComparisonMode.ts → selectedTool, compareMode, comparisonTools, URL sync for ExploreGraph
useBuilderState.ts → all URL-backed state for the Builder (selected tools, compare A/B, collapsed slots)
useIsMobile.ts → responsive breakpoint detection
useUser.ts → Supabase Auth session + profile state
components/
graph/
ExploreGraph.tsx → main graph view; viewMode: "grid" | "layers" | "3d"; uses useComparisonMode
ExploreGraph3D.tsx → Three.js 3D force graph (react-force-graph-3d, SSR-disabled)
EnrichedEdge.tsx → custom React Flow edge with how/achieves tooltip
LaneLabel.tsx → custom ReactFlow node type for swimlane lane backgrounds
ToolNode.tsx → custom node (collapsed 190px ↔ expanded 280px, CSS transition)
panels/
FilterPanel.tsx → category + edge type toggles, search
DetailPanel.tsx → right slide-in tool profile
ComparisonPanel.tsx → side-by-side tool comparison (used in /compare)
StackHealthPanel.tsx → slot coverage + health summary for the Builder
comparison/ → atomic cells used inside ComparisonPanel
CategoryPill.tsx · ChooseIfCard.tsx · DescriptionCard.tsx · FreeTierCell.tsx
LinksCard.tsx · PlanPills.tsx · Row.tsx · ToolPill.tsx · TypeBadge.tsx
mobile/
BottomSheet.tsx → generic bottom sheet wrapper
ToolDetailSheet.tsx → tool detail view optimized for mobile
ui/
Navbar.tsx → 56px, Logo component, icon tabs, route-aware right slot
Logo.tsx → inline SVG 3-node graph logo on gradient rect
GetStartedModal.tsx → onboarding modal
LandingAuthButton.tsx → GitHub OAuth sign-in button (landing page)
MyStackTray.tsx → saved stacks tray (authenticated users)
StackQuizModal.tsx → quiz-to-stack modal
SuggestToolModal.tsx → tool suggestion form modal
ToolUsageButton.tsx → "I use this" production usage button
WalkthroughOverlay.tsx → product walkthrough overlay
data/
tools.json → 235 tools, 21 categories
relationships.json → ~543 edges (integrates-with / commonly-paired-with / competes-with)
stacks.json → 33 curated stacks with flow edges
slots.json → 20 slot types for the Builder
lib/
types.ts → all TS interfaces + getCategoryColor() + STACK_LAYERS + CATEGORIES
constants.ts → SITE_URL, GITHUB_URL, TOOL_COUNT, CATEGORY_COUNT, STACK_COUNT, RELATIONSHIP_COUNT
graph.ts → applyDagreLayout() + gridLayout() + swimlaneLayout()
stackStory.ts → generateStackStory() → { flow, prose }
genomeAnalysis.ts → stack genome detection + scoring logic
graduationDetection.ts → detects when a genome score qualifies for graduation
health.ts → health score computation from GitHub signals
github.ts → GitHub API client (stars, last commit, archive status)
quizScoring.ts → quiz → stack scoring
db.ts → Supabase client (server + browser)
data-loaders.ts → typed loaders for JSON data files
metadata.ts → per-route metadata helpers
format.ts → shared formatting utilities
parseStack.ts → URL ?s= param ↔ tool id array
data/
tools.ts · stacks.ts · relationships.ts · slots.ts · counts.ts
scripts/
sync-counts.mjs → patches TOOL_COUNT / STACK_COUNT / RELATIONSHIP_COUNT in README.md + CLAUDE.md
seed-db.ts → seed remote Supabase
seed-db-local.ts → seed local Docker Postgres
seed-data.ts → shared seed data logic
public/
favicon.svg → same 3-node graph design as Logo
llms.txt → LLM crawler optimization
All TypeScript types and interfaces live in lib/types.ts. It is the single source of truth.
- Never use raw string literals for category IDs — import and use the
CategoryIdunion type. - Never hardcode category hex colors — always call
getCategoryColor(id: CategoryId)fromlib/types.ts. Adding a new category means adding it to theCATEGORIESarray there; the color propagates everywhere automatically. - When a type changes, update all call sites — no casting, no intermediate adapters. The project is pre-1.0 so a clean sweep is always the right call.
STACK_LAYERSandSTACK_CLUSTERSare defined inlib/types.ts; do not duplicate them inline in components.
data/tools.json, data/relationships.json, data/stacks.json, and data/slots.json are the source of truth. Every edit to these files must satisfy:
id— kebab-case, globally uniquecategory— must be a validCategoryIdfromlib/types.tsslot— must match anidindata/slots.json; if no slot fits, add one to slots.json firsttype— must be"oss"or"commercial"(nevernull)github_stars—nullfor commercial tools with no public repo; a number otherwisehealth_score,last_synced_at,is_stale— leave asnulluntil the health pipeline populates themcost_model— optional; see coverage rules below. Kept separate frompricingby design:pricingis a display/marketing model (tier names, price strings for UI);cost_modelis a computation model (numeric rates the simulator uses to project cost at scale)
128 of 207 tools have cost_model populated. The remaining 79 are excluded for one of two reasons:
1. OSS / self-hosted free (~55 tools) — aider, pydantic-ai, mermaid, mlflow, browser-use, etc.
These are type: "oss" with pricing.free_tier: true. The simulator infers type: "free" for these at runtime; no cost_model entry is needed.
2. Opaque / enterprise-only pricing (~24 tools) — aha-ai, manus-ai, codegen, factory-ai, etc.
Pricing is "contact sales", per-org negotiation, or invite-only. There is no public per-unit formula to model. Leave cost_model absent; the simulator will mark them as "pricing unavailable."
When adding a new tool:
- OSS/self-hosted → omit
cost_model - Commercial with published per-unit pricing → add
cost_model(seeCostModelinlib/types.ts) - Commercial enterprise-only → omit
cost_model
When pricing changes: update pricing and/or cost_model in tools.json + reseed. The nightly cron hashes both fields together, so any change to either automatically fires a pricing_change event on the next run.
- Both
sourceandtargetmust reference valid toolids that exist in tools.json - No orphaned references — adding or removing a tool requires auditing relationships
make sync-counts— patches counts in README.md and CLAUDE.md automaticallymake seed-validate— validates JSON integrity without a DB connection; fix any errors before proceedinglib/constants.tsderivesTOOL_COUNT,STACK_COUNT,RELATIONSHIP_COUNTfrom the JSON at build time — never hardcode these in UI components, always import fromlib/constants.ts
These routes run in Next.js edge runtime (no Node.js APIs, no fs, no crypto, no server-only imports):
app/badge/route.ts— SVG badge endpointapp/badge/tool/[toolId]/route.ts— per-tool badgeapp/opengraph-image.tsx— root OG imageapp/builder/opengraph-image.tsx— builder OG imageapp/stacks/opengraph-image.tsx— stacks OG imageapp/explore/og/route.tsx,app/stacks/og/route.tsx,app/builder/og/route.tsx,app/genome/og/route.tsx— route-level OG handlers
If you touch these files: no require(), no Node built-ins, no Supabase server client (edge-compatible client only).
Heavy browser libs that break SSR must be lazily imported:
// Good — Three.js and react-force-graph-3d
const ExploreGraph3D = dynamic(() => import("./ExploreGraph3D"), { ssr: false });Never import react-force-graph-3d at the top of a file that is server-rendered.
Tests live in __tests__/lib/ using Vitest. Run with make test.
When to add tests:
- New pure functions in
lib/— always add a test - New data-shape constants or lookup utilities — add coverage
- Bug fixes that had a specific repro case — add a regression test
When not to add tests:
- React components (no component tests currently; don't add a testing library for them without discussion)
- One-off scripts in
scripts/
Migrations live in supabase/migrations/. Always scaffold new migrations with make db-migrate name=<snake_case_name>.
Workflow for any schema change:
make db-migrate name=add_foo_column— creates a timestamped migration file- Edit the generated SQL
make db-push-local— apply to local Postgres, verifymake runstill worksmake db-push— promote to remote Supabase when satisfied- If you add a new column visible to the app, update the
Tool/Stackinterface inlib/types.tsand re-runmake typecheck
Never edit migration files that have already been pushed to the remote — scaffold a new one instead.
Page-level client components (GenomeClient, StacksClient, BuilderClient) are thin shells — they wire state and layout but contain no business logic.
| Where | What |
|---|---|
hooks/ |
URL-backed state, multi-piece state machines (useBuilderState, useComparisonMode) |
lib/ |
Pure functions with no React dependency |
app/<route>/components/ |
Sub-components owned by a single route |
components/ |
Shared components used across multiple routes |
Keep individual files under ~300 lines. When a file grows beyond that, extract a component or hook.
{ id, name, category, tagline, description, type: "oss"|"commercial",
pricing: { free_tier, plans[] }, github_stars, slot, prominent,
website_url, github_url, choose_if, provider,
use_context: "dev-productivity"|"app-infrastructure"|"both",
aliases: { npm[], pip[], env_vars[], config_files[] }, // Stack Genome detection signals
added_at, // ISO date — drives "New" badge; set when adding a tool
health_score, last_synced_at, is_stale } // populated by nightly sync pipeline; leave null when addingSource of truth is CATEGORIES in lib/types.ts. Never hardcode these — always call getCategoryColor(id).
| Category | Label | Color |
|---|---|---|
| coding-assistants | Coding Assistants | #7c6bff |
| autonomous-agents | Autonomous Agents | #ff6b6b |
| agent-frameworks | Agent Frameworks | #fdcb6e |
| pipelines-rag | Pipelines & RAG | #26de81 |
| llm-infra | LLM Infrastructure | #4ecdc4 |
| design | Design & UI | #ff9f43 |
| devops | DevOps & CI/CD | #fd9644 |
| docs | Documentation | #74b9ff |
| product-mgmt | Product & PM | #fd79a8 |
| mcp | MCP Servers | #a29bfe |
| prompt-eval | Prompt & Eval | #55efc4 |
| specifications | Specifications | #e17055 |
| fine-tuning | Fine-tuning | #e84393 |
| voice-ai | Voice AI | #00b894 |
| multimodal | Multimodal | #6c5ce7 |
| browser-automation | Browser Automation | #f0932b |
Builder selections are URL-encoded as ?s=tool-id-1,tool-id-2,...
app/builder/page.tsx— usesuseSearchParams+useRouterto read/write thesparam; wrapped in<Suspense>components/ui/Navbar.tsx— "Share Stack" button copieswindow.location.hrefto clipboard, shows "Copied!" for 2sapp/builder/opengraph-image.tsx— edge-runtime OG image generator; reads?s=param, renders tool pills on dark backgroundapp/badge/route.ts— edge-runtime SVG badge endpoint:GET /badge?s=cursor,langgraph→ shields.io-style SVG
- Colored 2px top accent strip (full opacity when expanded, dimmed otherwise)
- OSS tag:
◆ Open Source— green (#26de81), filled bg, shown whenevertype === "oss" - Free Tier tag:
✦ Free Tier— teal (#00d4aa), filled bg, shown wheneverpricing.free_tier === true - Stars moved to top-right (next to category label)
- Plan price pill + "Visit ↗" link remain in bottom row (collapsed/expanded respectively)
- Dynamically imported with
ssr: falsefrom ExploreGraph.tsx - Wheel event interception at container level (capture phase) amplifies deltaY × 6 for responsive zoom
- d3 forces:
chargewithstrength(-60).distanceMax(150),centerwithstrength(0.8) - OrbitControls:
zoomSpeed = 3,enableDamping = true,dampingFactor = 0.08 - Nodes: THREE.Sphere + SpriteText label above; selected node gets a ring
- Camera set to
z: 250on first engine stop
Auth is handled by Supabase Auth with GitHub OAuth. No username/password login.
app/auth/callback/route.ts— OAuth callback; exchanges the code for a session and redirectshooks/useUser.ts— session + profile state available client-sidelib/db.ts— exports both the browser client (createBrowserClient) and server client (createServerClient)- GitHub OAuth credentials are configured in the Supabase dashboard, not as env vars
Profiletype inlib/types.ts— mirrorspublic.profilestable (id, github_id, github_username, avatar_url)
See .env.example at the project root. Never commit real secrets.
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_POSTGRES_SUPABASE_URL |
Yes | Supabase project URL |
NEXT_PUBLIC_POSTGRES_SUPABASE_ANON_KEY |
Yes | Supabase anon key (public) |
POSTGRES_SUPABASE_SERVICE_ROLE_KEY |
Yes (server) | Supabase service role key — never expose to client |
DATABASE_URL |
Yes (local) | Local Docker Postgres connection string |
POSTGRES_URL_NON_POOLING |
Yes (migrations) | Direct connection for Supabase CLI / migrations |
GITHUB_TOKEN |
Yes | GitHub API token for nightly health sync |
CRON_SECRET |
Yes | Bearer token Vercel sends to /api/cron/sync-health |
GOOGLE_AI_API_KEY |
Yes | Google AI key for /api/roast and /api/challenge |
NEXT_PUBLIC_SITE_URL |
Optional | Overrides aichitect.dev for metadata/OG URLs |
The app falls back to static JSON data gracefully when Supabase env vars are absent (local dev without DB).
Linear is the source of truth for task tracking and prioritization.
- Before answering "what's next" or starting any planned work — check Linear first (
list_projects+list_issues). Do not rely on memory files or this document for task status. - When documenting new tasks or plans — create or update Linear issues first, before writing to any local file.
The Pencil design file is AIStack.pen, located one directory above the project root (../AIStack.pen).
- Use the
pencilMCP tools to read and edit it — never useRead,Grep, orcaton.penfiles (contents are encrypted and only accessible via the MCP tools).