From c07380643cf53e50b8a991d234658d715d133ddf Mon Sep 17 00:00:00 2001 From: GaneshBamalwa Date: Mon, 17 Aug 2026 19:33:48 +0530 Subject: [PATCH 1/7] feat(repo-interview-prep): implement multi-agent interview analysis architecture - Expanded \lamatic.config.ts\ to include sequential architecture, grill, and prod-readiness agents - Promoted Lamatic Next.js frontend to fully parse resilient JSON using \jsonrepair\ - Rebuilt React UI with beautifully styled tabs for Mermaid flowcharts, dropdown defensive strategies, and production checklist - Standardized environment variables across local SDK and server-side actions --- kits/repo-interview-prep/.gitignore | 4 + kits/repo-interview-prep/README.md | 109 + kits/repo-interview-prep/agent.md | 126 ++ kits/repo-interview-prep/apps/.env.example | 8 + kits/repo-interview-prep/apps/.gitignore | 6 + kits/repo-interview-prep/apps/AGENTS.md | 9 + kits/repo-interview-prep/apps/CLAUDE.md | 1 + .../apps/actions/orchestrate.ts | 101 + kits/repo-interview-prep/apps/app/globals.css | 110 + kits/repo-interview-prep/apps/app/layout.tsx | 24 + kits/repo-interview-prep/apps/app/page.tsx | 482 ++++ .../apps/lib/lamatic-client.ts | 14 + kits/repo-interview-prep/apps/lib/types.ts | 51 + kits/repo-interview-prep/apps/next-env.d.ts | 7 + kits/repo-interview-prep/apps/next.config.mjs | 6 + kits/repo-interview-prep/apps/orchestrate.js | 27 + .../apps/package-lock.json | 1947 +++++++++++++++++ kits/repo-interview-prep/apps/package.json | 33 + .../apps/postcss.config.mjs | 4 + kits/repo-interview-prep/apps/tsconfig.json | 41 + .../constitutions/default.md | 28 + .../flows/repo-interview-prep.ts | 499 +++++ kits/repo-interview-prep/lamatic.config.ts | 20 + ...-prep_llmnode-936_generative-model-name.ts | 15 + ...epo-interview-prep_llmnode-936_system_0.md | 16 + .../repo-interview-prep_llmnode-936_user_1.md | 35 + .../prompts/system_interview.md | 1 + .../prompts/user_architecture.md | 7 + .../repo-interview-prep/prompts/user_grill.md | 6 + kits/repo-interview-prep/prompts/user_prod.md | 7 + 30 files changed, 3744 insertions(+) create mode 100644 kits/repo-interview-prep/.gitignore create mode 100644 kits/repo-interview-prep/README.md create mode 100644 kits/repo-interview-prep/agent.md create mode 100644 kits/repo-interview-prep/apps/.env.example create mode 100644 kits/repo-interview-prep/apps/.gitignore create mode 100644 kits/repo-interview-prep/apps/AGENTS.md create mode 100644 kits/repo-interview-prep/apps/CLAUDE.md create mode 100644 kits/repo-interview-prep/apps/actions/orchestrate.ts create mode 100644 kits/repo-interview-prep/apps/app/globals.css create mode 100644 kits/repo-interview-prep/apps/app/layout.tsx create mode 100644 kits/repo-interview-prep/apps/app/page.tsx create mode 100644 kits/repo-interview-prep/apps/lib/lamatic-client.ts create mode 100644 kits/repo-interview-prep/apps/lib/types.ts create mode 100644 kits/repo-interview-prep/apps/next-env.d.ts create mode 100644 kits/repo-interview-prep/apps/next.config.mjs create mode 100644 kits/repo-interview-prep/apps/orchestrate.js create mode 100644 kits/repo-interview-prep/apps/package-lock.json create mode 100644 kits/repo-interview-prep/apps/package.json create mode 100644 kits/repo-interview-prep/apps/postcss.config.mjs create mode 100644 kits/repo-interview-prep/apps/tsconfig.json create mode 100644 kits/repo-interview-prep/constitutions/default.md create mode 100644 kits/repo-interview-prep/flows/repo-interview-prep.ts create mode 100644 kits/repo-interview-prep/lamatic.config.ts create mode 100644 kits/repo-interview-prep/model-configs/repo-interview-prep_llmnode-936_generative-model-name.ts create mode 100644 kits/repo-interview-prep/prompts/repo-interview-prep_llmnode-936_system_0.md create mode 100644 kits/repo-interview-prep/prompts/repo-interview-prep_llmnode-936_user_1.md create mode 100644 kits/repo-interview-prep/prompts/system_interview.md create mode 100644 kits/repo-interview-prep/prompts/user_architecture.md create mode 100644 kits/repo-interview-prep/prompts/user_grill.md create mode 100644 kits/repo-interview-prep/prompts/user_prod.md diff --git a/kits/repo-interview-prep/.gitignore b/kits/repo-interview-prep/.gitignore new file mode 100644 index 000000000..5d996efe4 --- /dev/null +++ b/kits/repo-interview-prep/.gitignore @@ -0,0 +1,4 @@ +.lamatic/ +node_modules/ +.env +.env.local diff --git a/kits/repo-interview-prep/README.md b/kits/repo-interview-prep/README.md new file mode 100644 index 000000000..ce32092e1 --- /dev/null +++ b/kits/repo-interview-prep/README.md @@ -0,0 +1,109 @@ +# Repo Interview Prep + +Turn any public GitHub repository into a complete, code-specific interview preparation brief in seconds. + +Paste a repo URL. Get a 2-minute verbal pitch, 15 tailored follow-up questions with suggested answers, concepts to review, red flags in your code, and strengths to highlight — all grounded in what is actually in your project, not generic advice. + +--- + +## What It Does + +Most candidates struggle to talk about their own projects under pressure. They built the thing, but give vague answers when a senior engineer probes the architecture, trade-offs, or weak points. This kit reads your actual repo — the README, file structure, and tech stack — and generates a deeply technical prep brief tailored to your code. + +**Output includes:** +- `project_summary` — concise 2-3 sentence overview of what the project actually does +- `tech_stack` — list of detected technologies +- `complexity_level` — junior / mid / senior signal +- `pitch` — a memorizable 2-minute verbal pitch in first-person spoken English +- `follow_up_questions` — 15 questions an interviewer would ask, with the signal they're testing and a strong suggested answer +- `concepts_to_review` — what to study before the interview, and how deep to go +- `red_flags` — what a senior engineer will push back on, and how to address it +- `strengths_to_highlight` — what genuinely shows strong engineering judgment in your code + +--- + +## Prerequisites + +| Requirement | Details | +|---|---| +| Firecrawl API Key | Free at [firecrawl.dev](https://firecrawl.dev) — 500 pages/month free | +| LLM Credential | Any capable model; recommended: `gemini-2.0-flash` or `gpt-4o-mini` | +| Public GitHub Repo | The target repository must be publicly accessible | + +--- + +## Setup + +1. **Add Firecrawl credential** in Lamatic Studio → Credentials → Firecrawl +2. **Select your LLM** in the `Generate Text` node — configure model and credential +3. **Deploy the flow** + +--- + +## Usage + +Send a POST request to the deployed flow endpoint: + +```json +{ + "github_repo_url": "https://github.com/your-username/your-repo", + "target_role": "SWE Intern", + "jd_text": "We are looking for a backend engineer with experience in distributed systems...", + "github_token": "" +} +``` + +| Field | Required | Description | +|---|---|---| +| `github_repo_url` | ✅ | Full GitHub repo URL | +| `target_role` | ❌ | Role you are interviewing for (improves question relevance) | +| `jd_text` | ❌ | Job description text (tailors questions to a specific role) | +| `github_token` | ❌ | GitHub personal access token (not required for public repos) | + +--- + +## Example Response + +```json +{ + "prep_brief": { + "project_summary": "ATLAS is a distributed AI orchestration platform that converts natural language requests into structured workflows across Google Workspace APIs using a custom DAG-based parallel execution engine.", + "tech_stack": ["Python", "FastAPI", "React", "Vite", "Docker", "Redis", "ChromaDB"], + "complexity_level": "mid", + "pitch": "For my project ATLAS, I built a distributed AI orchestration platform...", + "follow_up_questions": [...], + "concepts_to_review": [...], + "red_flags": [...], + "strengths_to_highlight": [...] + } +} +``` + +--- + +## Flow Architecture + +``` +API Trigger + → Code Node (parses GitHub URL) + → Firecrawl Node (scrapes repo page for README + file listing) + → Generate Text (LLM generates the prep brief as JSON) + → API Response +``` + +--- + +## Troubleshooting + +| Issue | Fix | +|---|---| +| Output describes an "empty repo" | Firecrawl credential is missing or invalid — check credentials in Studio | +| Code node parse error | GitHub URL is malformed — use exact format: `https://github.com/owner/repo` | +| Output is not valid JSON | Switch to a stronger model (`gemini-1.5-pro` or `gpt-4o`) | +| Questions are too generic | Add `target_role` and `jd_text` to the request payload | + +--- + +## Author + +Built by [Ganesh Bamalwa](mailto:ganeshbamalwa89@gmail.com) for the Lamatic AgentKit Challenge. diff --git a/kits/repo-interview-prep/agent.md b/kits/repo-interview-prep/agent.md new file mode 100644 index 000000000..bead41ed7 --- /dev/null +++ b/kits/repo-interview-prep/agent.md @@ -0,0 +1,126 @@ +# Repo Interview Prep + +## Overview + +**Repo Interview Prep** is a single-flow Lamatic template that turns any public GitHub repository into a complete, code-specific interview preparation brief. It scrapes the repository's README, file listing, and project description, then uses an LLM to generate a structured JSON brief containing a 2-minute verbal pitch, 15 tailored follow-up questions with suggested answers, concepts to review, red flags in the code, and strengths to highlight. + +The output is grounded in what is actually in the repository — not generic interview advice. The LLM is instructed to identify real architectural decisions, specific technologies, quantifiable claims, and potential weaknesses that a senior interviewer would push on. + +--- + +## Purpose + +Candidates routinely struggle to articulate their own projects in interviews. They built the thing, but under pressure they give vague answers or miss the deeper engineering signals an interviewer is probing for. This kit solves that by: + +1. **Reading the actual code context** via Firecrawl's GitHub page scraper +2. **Generating tailored questions** based on the real tech stack, architecture, and trade-offs present in the repo +3. **Writing suggested answers** that the candidate can personalize and rehearse +4. **Surfacing red flags honestly** — what an interviewer will push back on, and how to address it + +--- + +## Flows + +### `repo-interview-prep` + +| Property | Value | +|---|---| +| Trigger | API Request (GraphQL) | +| Inputs | `github_repo_url` (required), `target_role` (optional), `jd_text` (optional), `github_token` (optional) | +| Output | `prep_brief` (JSON string) | + +**Node pipeline:** + +``` +API Trigger → Code Node → Firecrawl Node → Generate Text (LLM) → API Response +``` + +1. **API Trigger** — receives the GitHub repo URL and optional context (target role, job description) +2. **Code Node** — parses the URL to extract `owner`, `repo`, and constructs the full GitHub page URL +3. **Firecrawl Node** (`syncSingleScrape`) — scrapes the GitHub repository page and returns cleaned markdown content including README, file listing, and repo description +4. **Generate Text (LLM)** — receives the scraped content and generates the full prep brief as a raw JSON object +5. **API Response** — returns `prep_brief` to the caller + +--- + +## Guardrails + +- The LLM is instructed to **base everything strictly on the scraped content** — no invented technologies or fabricated features +- The system prompt explicitly instructs the model to return **raw JSON only** — no markdown fences, no preamble, no explanation outside the JSON +- Red flags are always surfaced honestly, and suggested answers frame them constructively rather than defensively +- The constitution (`@constitutions/default.md`) applies standard safety, PII, and tone guardrails + +--- + +## Integration Reference + +| Service | Purpose | Required | +|---|---|---| +| Firecrawl | Scrapes GitHub repository page for README and file listing | Yes — configure Firecrawl credentials in Lamatic | +| LLM Provider | Generates the interview prep brief | Yes — configure model in `LLMNode_936` | +| GitHub Token | Increases GitHub API rate limits for private-context metadata | No — optional, passed as `github_token` in request payload | + +--- + +## Environment Setup + +1. **Firecrawl API Key** — sign up at [firecrawl.dev](https://firecrawl.dev) (free tier: 500 pages/month), add credential in Lamatic Studio +2. **LLM Model** — any capable chat model works; recommended: `gemini-2.0-flash` or `gpt-4o-mini` + +--- + +## Inputs + +| Field | Type | Required | Description | +|---|---|---|---| +| `github_repo_url` | `string` | Yes | Full GitHub URL, e.g. `https://github.com/username/repo` | +| `target_role` | `string` | No | Role the candidate is interviewing for, e.g. `SWE Intern` | +| `jd_text` | `string` | No | Job description text to tailor questions to a specific role | +| `github_token` | `string` | No | Personal access token for GitHub (increases rate limits) | + +--- + +## Output Schema + +The `prep_brief` field is a JSON string with the following structure: + +```json +{ + "project_summary": "string", + "tech_stack": ["string"], + "complexity_level": "junior | mid | senior", + "pitch": "string", + "follow_up_questions": [ + { + "question": "string", + "why_they_ask": "string", + "suggested_answer": "string" + } + ], + "concepts_to_review": [ + { + "concept": "string", + "why_relevant": "string", + "depth_needed": "surface | moderate | deep" + } + ], + "red_flags": [ + { + "observation": "string", + "how_to_address": "string" + } + ], + "strengths_to_highlight": ["string"] +} +``` + +--- + +## Common Failure Modes + +| Symptom | Likely Cause | Fix | +|---|---|---| +| `prep_brief` contains generic empty-repo advice | Firecrawl failed to scrape the GitHub page | Verify the Firecrawl credential is valid and the repo URL is public | +| Parse error on code node | Malformed GitHub URL passed in `github_repo_url` | Ensure URL follows `https://github.com/owner/repo` format | +| Output is not valid JSON | LLM prefixed the JSON with explanation text | Add stricter phrasing to the user prompt or switch to a stronger model | +| Firecrawl returns empty markdown | GitHub rate-limited or repo is private | Use a `github_token` in the request or make the repo public | diff --git a/kits/repo-interview-prep/apps/.env.example b/kits/repo-interview-prep/apps/.env.example new file mode 100644 index 000000000..2623be523 --- /dev/null +++ b/kits/repo-interview-prep/apps/.env.example @@ -0,0 +1,8 @@ +# Environment variables for Repo Interview Prep +# Copy this file to .env.local and fill in your values from Lamatic Studio → Settings → API Docs + +LAMATIC_PROJECT_ENDPOINT=https://your-project-endpoint.lamatic.workers.dev +LAMATIC_FLOW_ID=your-flow-id-uuid +LAMATIC_AGENT_ID=your-flow-id-uuid +LAMATIC_PROJECT_ID=your-project-id-uuid +LAMATIC_PROJECT_API_KEY=lt-your-api-key diff --git a/kits/repo-interview-prep/apps/.gitignore b/kits/repo-interview-prep/apps/.gitignore new file mode 100644 index 000000000..fe6ccfe71 --- /dev/null +++ b/kits/repo-interview-prep/apps/.gitignore @@ -0,0 +1,6 @@ +.env.local +.env*.local +node_modules/ +.next/ +out/ +dist/ diff --git a/kits/repo-interview-prep/apps/AGENTS.md b/kits/repo-interview-prep/apps/AGENTS.md new file mode 100644 index 000000000..643577dfa --- /dev/null +++ b/kits/repo-interview-prep/apps/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/kits/repo-interview-prep/apps/CLAUDE.md b/kits/repo-interview-prep/apps/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/kits/repo-interview-prep/apps/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/kits/repo-interview-prep/apps/actions/orchestrate.ts b/kits/repo-interview-prep/apps/actions/orchestrate.ts new file mode 100644 index 000000000..4c22f933a --- /dev/null +++ b/kits/repo-interview-prep/apps/actions/orchestrate.ts @@ -0,0 +1,101 @@ +"use server"; + +import { lamaticClient } from "@/lib/lamatic-client"; +import { config } from "../orchestrate"; +import type { PrepBrief, ArchitectureAnalysis, GrillQuestion, ProductionReadiness, RepoAnalysis } from "@/lib/types"; + +import { jsonrepair } from "jsonrepair"; + +// Helper to robustly parse JSON from string +function safeParse(raw: any, fallbackName: string): T { + if (!raw) { + throw new Error(`No ${fallbackName} found in response. Check workflow output configuration.`); + } + + try { + const jsonStr = typeof raw === "string" ? raw : JSON.stringify(raw); + const clean = jsonStr.replace(/^```json\s*/i, "").replace(/```\s*$/i, "").trim(); + + try { + return JSON.parse(clean) as T; + } catch (e) { + console.log(`[repo-interview-prep] Standard JSON parse failed for ${fallbackName}, attempting jsonrepair...`); + const repaired = jsonrepair(clean); + return JSON.parse(repaired) as T; + } + } catch (err) { + throw new Error(`Failed to parse ${fallbackName} as JSON, even after repair.`); + } +} + +export async function generatePrepBrief( + github_repo_url: string, + target_role: string, + jd_text: string +): Promise<{ success: boolean; data?: RepoAnalysis; error?: string }> { + try { + if (!process.env.LAMATIC_FLOW_ID) { + throw new Error( + "LAMATIC_FLOW_ID environment variable is not set. Please add it to your .env.local file." + ); + } + + const flows = config.flows; + const flow = flows.step1; + + if (!flow.workflowId) { + throw new Error("Workflow ID not found in config."); + } + + const inputs = { + github_repo_url, + target_role: target_role || "", + jd_text: jd_text || "", + github_token: "", + }; + + console.log("[repo-interview-prep] Executing flow:", flow.workflowId); + let resData = await lamaticClient.executeFlow(flow.workflowId, inputs); + console.log("[repo-interview-prep] Response status:", resData?.status); + + if (resData?.status === "error") { + throw new Error(`Lamatic workflow error: ${resData?.message}`); + } + + // Handle async polling if needed + if (resData?.result?.requestId && !resData?.result?.prep_brief) { + const requestId = resData.result.requestId; + console.log("[repo-interview-prep] Polling async result:", requestId); + resData = await lamaticClient.checkStatus(requestId, 2, 120); // extended timeout to 120s for 4 sequential LLMs + if (resData?.status === "error") { + throw new Error(`Async execution failed: ${resData?.message}`); + } + } + + const resObj = resData?.result || (resData as any)?.data?.output?.result || resData?.result?.output; + + if (!resObj) { + throw new Error("No result found in response payload."); + } + + // Parse all 4 sections + const prep_brief = safeParse(resObj.prep_brief, "prep_brief"); + const architecture = safeParse(resObj.architecture, "architecture"); + const grill_me = safeParse<{ questions: GrillQuestion[] }>(resObj.grill_me, "grill_me"); + const production = safeParse(resObj.production, "production"); + + return { + success: true, + data: { + prep_brief, + architecture, + grill_me, + production + } + }; + } catch (error) { + console.error("[repo-interview-prep] Error:", error); + const message = error instanceof Error ? error.message : "Unknown error occurred"; + return { success: false, error: message }; + } +} diff --git a/kits/repo-interview-prep/apps/app/globals.css b/kits/repo-interview-prep/apps/app/globals.css new file mode 100644 index 000000000..b908fe722 --- /dev/null +++ b/kits/repo-interview-prep/apps/app/globals.css @@ -0,0 +1,110 @@ +@import "tailwindcss"; + +:root { + --bg: #0d1117; + --surface: #161b22; + --surface-2: #21262d; + --border: #30363d; + --border-subtle: #21262d; + --text: #e6edf3; + --text-muted: #8b949e; + --text-subtle: #6e7681; + --accent: #7c3aed; + --accent-light: #a78bfa; + --accent-glow: rgba(124, 58, 237, 0.15); + --green: #22c55e; + --green-bg: rgba(34, 197, 94, 0.08); + --amber: #f59e0b; + --amber-bg: rgba(245, 158, 11, 0.08); + --blue: #3b82f6; + --blue-bg: rgba(59, 130, 246, 0.08); + --purple-bg: rgba(124, 58, 237, 0.08); +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { height: 100%; background: var(--bg); color: var(--text); } + +body { font-family: var(--font-geist-sans), system-ui, sans-serif; } + +/* Scrollbar */ +::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } + +/* Animations */ +@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } +@keyframes spin { to { transform: rotate(360deg); } } +@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } +@keyframes gradient-shift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.fade-in { animation: fadeIn 0.4s ease forwards; } +.spin { animation: spin 1s linear infinite; } +.pulse { animation: pulse 2s ease-in-out infinite; } + +.gradient-text { + background: linear-gradient(135deg, #7c3aed, #a78bfa, #7c3aed); + background-size: 200% 200%; + animation: gradient-shift 3s ease infinite; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.glow-border { + border: 1px solid var(--border); + box-shadow: 0 0 0 1px var(--border), inset 0 1px 0 rgba(255,255,255,0.04); + transition: border-color 0.2s, box-shadow 0.2s; +} +.glow-border:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.04); +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; +} + +.btn-primary { + background: var(--accent); + color: #fff; + border: none; + border-radius: 8px; + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s, transform 0.1s, box-shadow 0.2s; + display: flex; + align-items: center; + gap: 8px; +} +.btn-primary:hover:not(:disabled) { + background: #6d28d9; + box-shadow: 0 0 20px var(--accent-glow); +} +.btn-primary:active:not(:disabled) { transform: scale(0.98); } +.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; } + +.badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 20px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; +} +.badge-junior { background: var(--green-bg); color: var(--green); border: 1px solid rgba(34,197,94,0.2); } +.badge-mid { background: var(--blue-bg); color: var(--blue); border: 1px solid rgba(59,130,246,0.2); } +.badge-senior { background: var(--purple-bg); color: var(--accent-light); border: 1px solid rgba(124,58,237,0.2); } +.badge-surface { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); } +.badge-moderate { background: var(--blue-bg); color: var(--blue); border: 1px solid rgba(59,130,246,0.2); } +.badge-deep { background: var(--purple-bg); color: var(--accent-light); border: 1px solid rgba(124,58,237,0.2); } diff --git a/kits/repo-interview-prep/apps/app/layout.tsx b/kits/repo-interview-prep/apps/app/layout.tsx new file mode 100644 index 000000000..6b7100619 --- /dev/null +++ b/kits/repo-interview-prep/apps/app/layout.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geist = Geist({ subsets: ["latin"], variable: "--font-geist-sans" }); +const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" }); + +export const metadata: Metadata = { + title: "Repo Interview Prep — Lamatic AgentKit", + description: + "Turn any GitHub repository into a complete, code-specific interview preparation brief. Get a 2-minute pitch, 15 tailored questions with answers, concepts to review, and red flags — all grounded in your actual code.", +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + + {children} + + + ); +} diff --git a/kits/repo-interview-prep/apps/app/page.tsx b/kits/repo-interview-prep/apps/app/page.tsx new file mode 100644 index 000000000..864b4cf21 --- /dev/null +++ b/kits/repo-interview-prep/apps/app/page.tsx @@ -0,0 +1,482 @@ +"use client"; + +import { useState } from "react"; +import { generatePrepBrief } from "@/actions/orchestrate"; +import type { PrepBrief, RepoAnalysis } from "@/lib/types"; + +type Step = "input" | "loading" | "results"; + +const LOAD_STEPS = [ + "Parsing repository URL...", + "Scraping GitHub page...", + "Analyzing tech stack...", + "Drafting architecture diagrams...", + "Generating aggressive interview questions...", + "Checking production readiness...", + "Building your final prep brief...", +]; + +const complexityClass: Record = { + junior: "badge-junior", + mid: "badge-mid", + senior: "badge-senior", +}; + +const depthClass: Record = { + surface: "badge-surface", + moderate: "badge-moderate", + deep: "badge-deep", +}; + +export default function Page() { + const [step, setStep] = useState("input"); + const [repoUrl, setRepoUrl] = useState(""); + const [role, setRole] = useState(""); + const [jd, setJd] = useState(""); + const [showJd, setShowJd] = useState(false); + const [loadStep, setLoadStep] = useState(0); + const [analysis, setAnalysis] = useState(null); + const [error, setError] = useState(null); + const [activeSection, setActiveSection] = useState("summary"); + const [expandedQ, setExpandedQ] = useState(null); + const [copied, setCopied] = useState(false); + + const brief = analysis?.prep_brief; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!repoUrl.trim()) return; + setError(null); + setStep("loading"); + setLoadStep(0); + + // Fake progress animation + const interval = setInterval(() => { + setLoadStep((p) => (p < LOAD_STEPS.length - 1 ? p + 1 : p)); + }, 4500); // Slower because 4 sequential LLMs take ~30-40s + + const result = await generatePrepBrief(repoUrl.trim(), role, jd); + clearInterval(interval); + + if (result.success && result.data) { + setAnalysis(result.data); + setStep("results"); + setActiveSection("summary"); + } else { + setError(result.error ?? "Unknown error"); + setStep("input"); + } + } + + function copyPitch() { + if (brief?.pitch) { + navigator.clipboard.writeText(brief.pitch); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } + + const repoName = repoUrl.replace("https://github.com/", "").replace(/\/$/, ""); + + const NAV = [ + { id: "summary", label: "Overview" }, + { id: "architecture", label: "Architecture" }, + { id: "pitch", label: "Your Pitch" }, + { id: "grill", label: `Grill Me (${analysis?.grill_me?.questions?.length ?? 0})` }, + { id: "questions", label: `Q&A (${brief?.follow_up_questions?.length ?? 0})` }, + { id: "concepts", label: "Concepts" }, + { id: "production", label: "Prod Readiness" }, + { id: "redflags", label: `Red Flags (${brief?.red_flags?.length ?? 0})` }, + { id: "strengths", label: "Strengths" }, + ]; + + // ─── INPUT ───────────────────────────────────────────────────── + if (step === "input") { + return ( +
+
+ {/* Brand */} +
+
+ + RepoPrep +
+

+ Turn your projects into
interview gold +

+

+ Paste a GitHub repo URL. Get a complete, code-specific prep brief in seconds. +

+
+ + {/* Form */} +
+
+ {/* URL input */} +
+ +
+ github.com/ + setRepoUrl("https://github.com/" + e.target.value)} + style={{ flex: 1, background: "transparent", border: "none", outline: "none", padding: "11px 12px 11px 4px", color: "var(--text)", fontSize: 14 }} + required + /> +
+
+ + {/* Target role */} +
+ +
+ setRole(e.target.value)} + style={{ width: "100%", background: "transparent", border: "none", outline: "none", padding: "11px 12px", color: "var(--text)", fontSize: 14 }} + /> +
+
+ + {/* JD toggle */} + + + {showJd && ( +
+ +
+