From 794a421f231fc9d7a6ae803502e61ee05ace664d Mon Sep 17 00:00:00 2001 From: spivakov83 Date: Wed, 15 Oct 2025 15:59:03 +0300 Subject: [PATCH 01/10] feat: enhance tooling detection and stack inference for Python projects --- app/api/scan-repo/route.ts | 17 +++++- app/existing/[repoUrl]/repo-scan-client.tsx | 37 ++++++++++-- file-templates/agents-template.md | 2 +- .../copilot-instructions-template.md | 33 +++++------ file-templates/cursor-rules-template.json | 2 +- lib/scan-to-wizard.ts | 57 +++++++++++++++++-- lib/stack-guidance.ts | 6 +- lib/template-render.ts | 16 +++++- 8 files changed, 139 insertions(+), 31 deletions(-) diff --git a/app/api/scan-repo/route.ts b/app/api/scan-repo/route.ts index b4408a4..09bdb8f 100644 --- a/app/api/scan-repo/route.ts +++ b/app/api/scan-repo/route.ts @@ -121,7 +121,10 @@ const detectTooling = (paths: string[], pkg: PackageJson | null): { tooling: str const matchers: Array<{ pattern: RegExp; value: string; target: Set }> = [ { pattern: /^requirements\.txt$/, value: "pip", target: tooling }, - { pattern: /^pyproject\.toml$/, value: "Poetry", target: tooling }, + { pattern: /^poetry\.lock$/, value: "Poetry", target: tooling }, + { pattern: /^pipfile$/, value: "Pipenv", target: tooling }, + { pattern: /^pipfile\.lock$/, value: "Pipenv", target: tooling }, + { pattern: /^pyproject\.toml$/, value: "PyProject", target: tooling }, { pattern: /pom\.xml$/, value: "Maven", target: tooling }, { pattern: /build\.gradle(\.kts)?$/, value: "Gradle", target: tooling }, { pattern: /(^|\/)dockerfile$/, value: "Docker", target: tooling }, @@ -138,6 +141,15 @@ const detectTooling = (paths: string[], pkg: PackageJson | null): { tooling: str { pattern: /vite\.config\.(js|cjs|mjs|ts)?$/, value: "Vite", target: tooling }, { pattern: /rollup\.config\.(js|cjs|mjs|ts)?$/, value: "Rollup", target: tooling }, { pattern: /tailwind\.config\.(js|cjs|mjs|ts)?$/, value: "Tailwind CSS", target: tooling }, + { pattern: /(^|\/)ruff\.toml$/, value: "Ruff", target: tooling }, + { pattern: /(^|\/)\.ruff\.toml$/, value: "Ruff", target: tooling }, + { pattern: /(^|\/)black\.toml$/, value: "black", target: tooling }, + { pattern: /(^|\/)\.flake8$/, value: "flake8", target: tooling }, + { pattern: /(^|\/)flake8\.cfg$/, value: "flake8", target: tooling }, + { pattern: /(^|\/)mypy\.ini$/, value: "mypy", target: tooling }, + { pattern: /(^|\/)\.mypy\.ini$/, value: "mypy", target: tooling }, + { pattern: /(^|\/)setup\.cfg$/, value: "setup.cfg", target: tooling }, + { pattern: /(^|\/)\.pre-commit-config\.ya?ml$/, value: "pre-commit", target: tooling }, { pattern: /jest\.config\.(js|cjs|mjs|ts|json)?$/, value: "Jest", target: testing }, { pattern: /vitest\.(config|setup)/, value: "Vitest", target: testing }, { pattern: /(^|\/)cypress\//, value: "Cypress", target: testing }, @@ -145,6 +157,9 @@ const detectTooling = (paths: string[], pkg: PackageJson | null): { tooling: str { pattern: /playwright\.config\.(js|cjs|mjs|ts)?$/, value: "Playwright", target: testing }, { pattern: /karma\.conf(\.js)?$/, value: "Karma", target: testing }, { pattern: /mocha\./, value: "Mocha", target: testing }, + { pattern: /(^|\/)pytest\.ini$/, value: "pytest", target: testing }, + { pattern: /(^|\/)conftest\.py$/, value: "pytest", target: testing }, + { pattern: /(^|\/)tox\.ini$/, value: "tox", target: testing }, ] for (const { pattern, value, target } of matchers) { diff --git a/app/existing/[repoUrl]/repo-scan-client.tsx b/app/existing/[repoUrl]/repo-scan-client.tsx index 726b22b..64d96c7 100644 --- a/app/existing/[repoUrl]/repo-scan-client.tsx +++ b/app/existing/[repoUrl]/repo-scan-client.tsx @@ -14,6 +14,7 @@ import { generateFromRepoScan } from "@/lib/scan-generate" import FinalOutputView from "@/components/final-output-view" import RepoScanLoader from "@/components/repo-scan-loader" import type { GeneratedFileResult } from "@/types/output" +import { inferStackFromScan } from "@/lib/scan-to-wizard" const buildQuery = (url: string) => `/api/scan-repo?url=${encodeURIComponent(url)}` @@ -99,10 +100,38 @@ export default function RepoScanClient({ initialRepoUrl }: RepoScanClientProps) return [] } - return Object.entries(scanResult.structure).map(([key, value]) => ({ - key, - value, - })) + const stack = inferStackFromScan(scanResult) + const stackCategory = (() => { + if (stack === "python") return "python" + if (["nextjs", "react", "angular", "vue", "svelte", "nuxt", "astro", "remix"].includes(stack)) { + return "frontend" + } + return "general" + })() + + const metadata: Record< + keyof RepoScanSummary["structure"], + { label: string; categories: Array<"any" | "frontend" | "python" | "general"> } + > = { + src: { label: "src", categories: ["any"] }, + components: { label: "components", categories: ["frontend"] }, + tests: { label: "tests", categories: ["any"] }, + apps: { label: "apps", categories: ["frontend", "general"] }, + packages: { label: "packages", categories: ["frontend", "general"] }, + } + + return (Object.entries(scanResult.structure) as Array<[keyof RepoScanSummary["structure"], boolean]>) + .map(([key, value]) => { + const details = metadata[key] ?? { label: key, categories: ["general"] } + const showEntry = value || details.categories.includes("any") || details.categories.includes(stackCategory) + return showEntry + ? { + key: details.label, + value, + } + : null + }) + .filter((entry): entry is { key: string; value: boolean } => Boolean(entry)) }, [scanResult]) const handleStartScan = () => { diff --git a/file-templates/agents-template.md b/file-templates/agents-template.md index d22f8e9..11c77f1 100644 --- a/file-templates/agents-template.md +++ b/file-templates/agents-template.md @@ -58,7 +58,7 @@ This guide provides conventions and best practices for building AI agent applica ### Performance & Monitoring - Logging: **{{logging}}** -- Performance considerations: **{{reactPerf}}** +- Performance focus: **{{reactPerf}}** - Additional concerns: - Monitor token usage and cost efficiency. - Handle API rate limits gracefully. diff --git a/file-templates/copilot-instructions-template.md b/file-templates/copilot-instructions-template.md index ac0e0d6..0eda7ff 100644 --- a/file-templates/copilot-instructions-template.md +++ b/file-templates/copilot-instructions-template.md @@ -1,6 +1,6 @@ --- # Configuration for Copilot in this project -applyTo: "**/*.{ts,tsx,js,jsx,md}" # apply to all code files by default +applyTo: "{{applyToGlob}}" # apply to relevant code files by default --- # Copilot Instructions @@ -39,10 +39,10 @@ Regenerate whenever your JSON configuration changes (stack, naming, testing, etc - Code style: follow **{{codeStyle}}** ### File and Folder Structure -- Component / UI layout: **{{fileStructure}}** -- Styling approach: **{{styling}}** -- State management: **{{stateManagement}}** -- API layer organization: **{{apiLayer}}** +- Module / feature layout: **{{fileStructure}}** +- Styling approach (if applicable): **{{styling}}** +- State management / shared context: **{{stateManagement}}** +- API / service layer organization: **{{apiLayer}}** - Folder strategy: **{{folders}}** @@ -63,19 +63,20 @@ Regenerate whenever your JSON configuration changes (stack, naming, testing, etc --- -## 5. Performance & Data Loading +## 5. Performance & Data Handling - Data fetching: **{{dataFetching}}** -- React performance optimizations: **{{reactPerf}}** +- Performance focus: **{{reactPerf}}** **Do** -- Use pagination or limit queries. -- Memoize expensive computations. -- Lazy-load non-critical modules. +- Use pagination or streaming for large datasets. +- Cache or memoize expensive work when it matters. +- Offload non-critical processing to background tasks. **Don’t** -- Fetch all data at once. -- Put heavy logic in render without memoization. +- Load entire datasets eagerly without need. +- Block hot execution paths with heavy synchronous work. +- Skip instrumentation that would surface performance regressions. --- @@ -89,7 +90,7 @@ Regenerate whenever your JSON configuration changes (stack, naming, testing, etc - Never commit secrets; use environment variables. - Validate all incoming data (API and client). - Do not log secrets or PII. -- Use structured/contextual logs instead of raw `console.log`. +- Use structured/contextual logs instead of raw print/log statements. --- @@ -112,10 +113,10 @@ Regenerate whenever your JSON configuration changes (stack, naming, testing, etc ## 8. Copilot Usage Guidance -- Use Copilot for boilerplate (hooks, component scaffolds). +- Use Copilot for boilerplate (e.g., scaffolds, repetitive wiring). - Provide context in comments/prompts. - Reject completions that break naming, structure, or validation rules. -- Ask clarifying questions in comments (e.g., “// Should this live in services?”). +- Ask clarifying questions in comments (e.g., “# Should this live in services?”). - Prefer completions that respect folder boundaries and import paths. **Don’t rely on Copilot for** @@ -130,7 +131,7 @@ Regenerate whenever your JSON configuration changes (stack, naming, testing, etc Recommended editor configuration: - Use `.editorconfig` for indentation/line endings. -- Enable linting/formatting (ESLint, Prettier, or Biome). +- Enable linting/formatting (ESLint, Prettier, Ruff, Black, etc.). - Set `editor.formatOnSave = true`. - Suggested integrations: - VS Code: `dbaeumer.vscode-eslint`, `esbenp.prettier-vscode` diff --git a/file-templates/cursor-rules-template.json b/file-templates/cursor-rules-template.json index a92e874..f806fbe 100644 --- a/file-templates/cursor-rules-template.json +++ b/file-templates/cursor-rules-template.json @@ -27,7 +27,7 @@ }, "performance": { "dataFetching": "{{dataFetching}}", - "reactOptimizations": "{{reactPerf}}" + "focus": "{{reactPerf}}" }, "security": { "auth": "{{auth}}", diff --git a/lib/scan-to-wizard.ts b/lib/scan-to-wizard.ts index fc6d291..6fe015e 100644 --- a/lib/scan-to-wizard.ts +++ b/lib/scan-to-wizard.ts @@ -17,6 +17,8 @@ const pickStack = (scan: RepoScanSummary): string => { return "react" } +export const inferStackFromScan = (scan: RepoScanSummary): string => pickStack(scan) + const pickLanguage = (scan: RepoScanSummary): string | null => { const languages = (scan.languages ?? []).map((l) => l.toLowerCase()) if (languages.includes("typescript")) return "typescript" @@ -27,6 +29,8 @@ const pickLanguage = (scan: RepoScanSummary): string | null => { const pickTestingUT = (scan: RepoScanSummary): string | null => { const testing = (scan.testing ?? []).map((t) => t.toLowerCase()) + if (testing.includes("pytest")) return "pytest" + if (testing.includes("unittest")) return "unittest" if (testing.includes("vitest")) return "vitest" if (testing.includes("jest")) return "jest" return null @@ -42,6 +46,7 @@ const pickTestingE2E = (scan: RepoScanSummary): string | null => { const pickStyling = (scan: RepoScanSummary, stack: string): string => { const tooling = (scan.tooling ?? []).map((t) => t.toLowerCase()) if (tooling.includes("tailwind css") || tooling.includes("tailwind")) return "tailwind" + if (stack === "python") return "Not applicable (backend Python project)" return stack === "nextjs" ? "tailwind" : "cssmodules" } @@ -68,7 +73,15 @@ const pickComponentNaming = (scan: RepoScanSummary): string => { return detected === "camelcase" ? "camelCase" : "PascalCase" } -const pickCodeStyle = (scan: RepoScanSummary): string => { +const pickCodeStyle = (scan: RepoScanSummary, stack: string): string => { + if (stack === "python") { + const tooling = (scan.tooling ?? []).map((t) => t.toLowerCase()) + if (tooling.includes("ruff")) return "ruff" + if (tooling.includes("black")) return "black" + if (tooling.includes("pyproject.toml")) return "pep8" + if (tooling.includes("pipenv")) return "pep8" + return "pep8" + } const detected = (scan as any).codeStylePreference as string | undefined | null if (!detected) return "airbnb" if (detected === "standardjs") return "standardjs" @@ -77,6 +90,13 @@ const pickCodeStyle = (scan: RepoScanSummary): string => { } const pickFileStructure = (scan: RepoScanSummary, stack: string): string => { + if (stack === "python") { + const hasSrc = scan.structure?.src ?? false + const hasPackagesDir = scan.structure?.packages ?? false + if (hasSrc) return "src layout (src/)" + if (hasPackagesDir) return "packages directory (monorepo-style)" + return "top-level modules" + } if (stack === "nextjs") { // Prefer App Router by default; hybrid/pages if hints present (see enriched fields if any) const routing = (scan as any).routing as string | undefined @@ -91,30 +111,35 @@ const pickFileStructure = (scan: RepoScanSummary, stack: string): string => { const pickStateMgmt = (scan: RepoScanSummary, stack: string): string => { const detected = ((scan as any).stateMgmt as string | undefined) ?? null if (detected) return detected + if (stack === "python") return "Not applicable" return stack === "nextjs" ? "zustand" : "context-hooks" } const pickDataFetching = (scan: RepoScanSummary, stack: string): string => { const detected = ((scan as any).dataFetching as string | undefined) ?? null if (detected) return detected + if (stack === "python") return "Not applicable" return stack === "nextjs" ? "server-components" : "swr" } const pickAuth = (scan: RepoScanSummary, stack: string): string => { const detected = ((scan as any).auth as string | undefined) ?? null if (detected) return detected + if (stack === "python") return "TODO: document auth/session strategy" return stack === "nextjs" ? "next-auth" : "env" } -const pickValidation = (scan: RepoScanSummary): string => { +const pickValidation = (scan: RepoScanSummary, stack: string): string => { const detected = ((scan as any).validation as string | undefined) ?? null if (detected) return detected + if (stack === "python") return "TODO: specify validation library (Pydantic, Marshmallow, etc.)" return "zod" } const pickLogging = (scan: RepoScanSummary, stack: string): string => { const detected = ((scan as any).logging as string | undefined) ?? null if (detected) return detected + if (stack === "python") return "TODO: describe logging approach (structlog, stdlib logging)" return stack === "nextjs" ? "sentry" : "structured" } @@ -135,6 +160,7 @@ const pickPrRules = (scan: RepoScanSummary): string => { const buildToolingSummary = (scan: RepoScanSummary, stack: string): string => { const parts = scan.tooling ?? [] if (parts.length > 0) return parts.join(" + ") + if (stack === "python") return "pip" return stack === "nextjs" ? "create-next-app" : stack === "react" ? "vite" : "custom-config" } @@ -156,7 +182,7 @@ export function buildResponsesFromScan(scan: RepoScanSummary): ScanToWizardResul testingUT: pickTestingUT(scan), testingE2E: pickTestingE2E(scan), projectPriority: "maintainability", - codeStyle: pickCodeStyle(scan), + codeStyle: pickCodeStyle(scan, stack), variableNaming: "camelCase", fileNaming: pickFileNaming(scan), componentNaming: pickComponentNaming(scan), @@ -169,12 +195,35 @@ export function buildResponsesFromScan(scan: RepoScanSummary): ScanToWizardResul dataFetching: pickDataFetching(scan, stack), reactPerf: "memoHooks", auth: pickAuth(scan, stack), - validation: pickValidation(scan), + validation: pickValidation(scan, stack), logging: pickLogging(scan, stack), commitStyle: pickCommitStyle(scan), prRules: pickPrRules(scan), outputFile: null, } + if (stack === "python") { + responses.tooling = responses.tooling ?? "pip" + responses.language = + responses.language && responses.language.toLowerCase() === "python" ? "Python" : responses.language ?? "Python" + responses.variableNaming = "snake_case" + responses.fileNaming = "snake_case" + responses.componentNaming = "Not applicable" + responses.exports = "module exports" + responses.comments = "docstrings" + responses.stateManagement = "Not applicable" + responses.apiLayer = "TODO: document primary framework (FastAPI, Django, Flask, etc.)" + responses.folders = "by-module" + responses.dataFetching = "Not applicable" + responses.reactPerf = "Not applicable" + responses.testingUT = responses.testingUT ?? "pytest" + responses.testingE2E = "TODO: outline integration / end-to-end coverage" + responses.styling = "Not applicable (backend Python project)" + responses.codeStyle = responses.codeStyle ?? "pep8" + responses.auth = responses.auth ?? "TODO: document auth/session strategy" + responses.validation = responses.validation ?? "TODO: specify validation library (Pydantic, Marshmallow, etc.)" + responses.logging = responses.logging ?? "TODO: describe logging approach (structlog, stdlib logging)" + } + return { stack, responses } } diff --git a/lib/stack-guidance.ts b/lib/stack-guidance.ts index 1b1f60d..3ef3d19 100644 --- a/lib/stack-guidance.ts +++ b/lib/stack-guidance.ts @@ -47,9 +47,9 @@ const stackGuidanceBySlug: Record = { "Keep links, forms, and nested routes aligned with Remix conventions to benefit from built-in optimizations.", ]), python: asMarkdownList([ - "Call out whether FastAPI, Django, or Flask is the project's default framework.", - "Define typing expectations (mypy, Ruff, or dynamic) to keep contributions consistent.", - "Describe package management commands (Poetry, pip-tools, uv) for installing and locking dependencies.", + "TODO: Call out whether FastAPI, Django, or Flask is the project's default framework.", + "TODO: Define typing expectations (mypy, Ruff, or dynamic) to keep contributions consistent.", + "TODO: Describe package management commands (Poetry, pip-tools, uv) for installing and locking dependencies.", ]), } diff --git a/lib/template-render.ts b/lib/template-render.ts index e924c06..afbed87 100644 --- a/lib/template-render.ts +++ b/lib/template-render.ts @@ -5,6 +5,20 @@ import type { WizardResponses } from '@/types/wizard' import { getTemplateConfig, type TemplateKey } from '@/lib/template-config' import { getStackGuidance } from '@/lib/stack-guidance' +const determineApplyToGlob = (responses: WizardResponses, stackSlug?: string): string => { + const normalizedStack = (responses.stackSelection || stackSlug || '').trim().toLowerCase() + + if (normalizedStack === 'python') { + return '**/*.{py,pyi,md}' + } + + if (['nextjs', 'react', 'angular', 'vue', 'svelte', 'nuxt', 'astro', 'remix'].includes(normalizedStack)) { + return '**/*.{ts,tsx,js,jsx,md}' + } + + return '**/*.{ts,tsx,js,jsx,md}' +} + function mapOutputFileToTemplateType(outputFile: string): string { const mapping: Record = { 'instructions-md': 'copilot-instructions', @@ -132,6 +146,7 @@ export async function renderTemplate({ const stackGuidanceSlug = responses.stackSelection || framework const stackGuidance = getStackGuidance(stackGuidanceSlug) replaceStaticPlaceholder('stackGuidance', stackGuidance) + replaceStaticPlaceholder('applyToGlob', determineApplyToGlob(responses, stackGuidanceSlug)) return { content: generatedContent, @@ -139,4 +154,3 @@ export async function renderTemplate({ isJson: isJsonTemplate, } } - From 354b23228ce0424f62bae1bdaee538e748930f4b Mon Sep 17 00:00:00 2001 From: spivakov83 Date: Wed, 15 Oct 2025 17:00:20 +0300 Subject: [PATCH 02/10] feat: Add conventions for various frameworks and languages - Introduced conventions for Next.js, Nuxt, Python, React, Remix, Svelte, and Vue. - Each convention includes default settings and rules based on tooling and testing frameworks. - Enhanced the scan flow documentation to outline how repository scans are transformed into AI instruction defaults. - Updated the wizard summary and storage to accommodate new defaulted question tracking. - Implemented logic to apply convention rules based on detected tooling and testing during repository scans. - Added types for conventions to improve type safety and clarity in the codebase. --- README.md | 3 + app/api/scan-generate/[fileId]/route.ts | 40 +++ app/api/scan-repo/route.ts | 10 + app/existing/[repoUrl]/repo-scan-client.tsx | 61 ++-- app/new/stack/stack-summary-page.tsx | 22 +- components/wizard-completion-summary.tsx | 5 + conventions/angular.json | 40 +++ conventions/astro.json | 28 ++ conventions/default.json | 31 ++ conventions/nextjs.json | 40 +++ conventions/nuxt.json | 26 ++ conventions/python.json | 62 ++++ conventions/react.json | 24 ++ conventions/remix.json | 24 ++ conventions/svelte.json | 33 ++ conventions/vue.json | 29 ++ docs/scan-flow.md | 49 +++ lib/__tests__/wizard-summary.test.ts | 2 + lib/conventions.ts | 136 +++++++ lib/scan-generate.ts | 30 +- lib/scan-prefill.ts | 8 +- lib/scan-to-wizard.ts | 377 ++++++++++---------- lib/wizard-storage.ts | 1 + lib/wizard-summary-data.ts | 10 +- lib/wizard-summary.ts | 3 + types/conventions.ts | 35 ++ types/repo-scan.ts | 7 + 27 files changed, 906 insertions(+), 230 deletions(-) create mode 100644 app/api/scan-generate/[fileId]/route.ts create mode 100644 conventions/angular.json create mode 100644 conventions/astro.json create mode 100644 conventions/default.json create mode 100644 conventions/nextjs.json create mode 100644 conventions/nuxt.json create mode 100644 conventions/python.json create mode 100644 conventions/react.json create mode 100644 conventions/remix.json create mode 100644 conventions/svelte.json create mode 100644 conventions/vue.json create mode 100644 docs/scan-flow.md create mode 100644 lib/conventions.ts create mode 100644 types/conventions.ts diff --git a/README.md b/README.md index a2ff3cd..58236f2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ Build high-signal agent and instruction files from community-proven best practic 4. Answer topic prompts across general, architecture, performance, security, commits, and more—or lean on the recommended defaults when you need a fast decision. 5. Review a completion summary that highlights what made it into your file and which areas still need decisions. +## Architecture docs +- [Scan → Wizard Flow](docs/scan-flow.md) – how repository scans feed conventions, defaults, and final instruction files. + ## Community knowledge base - Every topic originates from the developer community—playbooks, real-world retrospectives, and shared tooling habits. - JSON entries in `data/` capture those insights: each answer carries labels, examples, pros/cons, tags, and authoritative `docs` links. diff --git a/app/api/scan-generate/[fileId]/route.ts b/app/api/scan-generate/[fileId]/route.ts new file mode 100644 index 0000000..c58e8e6 --- /dev/null +++ b/app/api/scan-generate/[fileId]/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server" + +import { buildResponsesFromScan } from "@/lib/scan-to-wizard" +import { renderTemplate } from "@/lib/template-render" +import { getMimeTypeForFormat } from "@/lib/wizard-utils" +import type { RepoScanSummary } from "@/types/repo-scan" + +type RouteContext = { + params: Promise<{ fileId: string }> +} + +export async function POST(request: NextRequest, context: RouteContext) { + try { + const { fileId } = await context.params + const payload = (await request.json()) as { scan?: RepoScanSummary | null; format?: string | null } + + if (!payload?.scan) { + return NextResponse.json({ error: "Missing scan payload" }, { status: 400 }) + } + + const { stack, responses } = await buildResponsesFromScan(payload.scan) + responses.outputFile = fileId + + const rendered = await renderTemplate({ + responses, + frameworkFromPath: stack, + fileNameFromPath: fileId, + }) + + return NextResponse.json({ + fileName: rendered.fileName, + content: rendered.content, + mimeType: getMimeTypeForFormat(payload.format ?? undefined) ?? null, + }) + } catch (error) { + console.error("Failed to generate instructions from scan", error) + return NextResponse.json({ error: "Failed to generate instructions from scan" }, { status: 500 }) + } +} + diff --git a/app/api/scan-repo/route.ts b/app/api/scan-repo/route.ts index 09bdb8f..25abd3a 100644 --- a/app/api/scan-repo/route.ts +++ b/app/api/scan-repo/route.ts @@ -6,6 +6,8 @@ import type { RepoScanSummary, RepoStructureSummary, } from "@/types/repo-scan" +import { inferStackFromScan } from "@/lib/scan-to-wizard" +import { loadStackConventions } from "@/lib/conventions" const GITHUB_API_BASE_URL = "https://api.github.com" const GITHUB_HOSTNAMES = new Set(["github.com", "www.github.com"]) @@ -790,6 +792,14 @@ export async function GET(request: NextRequest): Promise(summary) } catch (error) { console.error("Unexpected error while scanning repository", error) diff --git a/app/existing/[repoUrl]/repo-scan-client.tsx b/app/existing/[repoUrl]/repo-scan-client.tsx index 64d96c7..1c1b85a 100644 --- a/app/existing/[repoUrl]/repo-scan-client.tsx +++ b/app/existing/[repoUrl]/repo-scan-client.tsx @@ -14,9 +14,10 @@ import { generateFromRepoScan } from "@/lib/scan-generate" import FinalOutputView from "@/components/final-output-view" import RepoScanLoader from "@/components/repo-scan-loader" import type { GeneratedFileResult } from "@/types/output" -import { inferStackFromScan } from "@/lib/scan-to-wizard" const buildQuery = (url: string) => `/api/scan-repo?url=${encodeURIComponent(url)}` +const CONVENTIONS_DOC_URL = + process.env.NEXT_PUBLIC_CONVENTIONS_URL ?? "https://github.com/devcontext-ai/devcontext/tree/main/conventions" const formatList = (values: string[]) => (values.length > 0 ? values.join(", ") : "Not detected") @@ -100,38 +101,12 @@ export default function RepoScanClient({ initialRepoUrl }: RepoScanClientProps) return [] } - const stack = inferStackFromScan(scanResult) - const stackCategory = (() => { - if (stack === "python") return "python" - if (["nextjs", "react", "angular", "vue", "svelte", "nuxt", "astro", "remix"].includes(stack)) { - return "frontend" - } - return "general" - })() + const relevantKeys = scanResult.conventions?.structureRelevant ?? ["src", "components", "tests", "apps", "packages"] - const metadata: Record< - keyof RepoScanSummary["structure"], - { label: string; categories: Array<"any" | "frontend" | "python" | "general"> } - > = { - src: { label: "src", categories: ["any"] }, - components: { label: "components", categories: ["frontend"] }, - tests: { label: "tests", categories: ["any"] }, - apps: { label: "apps", categories: ["frontend", "general"] }, - packages: { label: "packages", categories: ["frontend", "general"] }, - } - - return (Object.entries(scanResult.structure) as Array<[keyof RepoScanSummary["structure"], boolean]>) - .map(([key, value]) => { - const details = metadata[key] ?? { label: key, categories: ["general"] } - const showEntry = value || details.categories.includes("any") || details.categories.includes(stackCategory) - return showEntry - ? { - key: details.label, - value, - } - : null - }) - .filter((entry): entry is { key: string; value: boolean } => Boolean(entry)) + return relevantKeys.map((key) => ({ + key, + value: scanResult.structure[key as keyof RepoScanSummary["structure"]] ?? false, + })) }, [scanResult]) const handleStartScan = () => { @@ -253,6 +228,28 @@ export default function RepoScanClient({ initialRepoUrl }: RepoScanClientProps)

+ {scanResult.conventions && !scanResult.conventions.hasCustomConventions ? ( +
+
+ + We don’t have conventions for {scanResult.conventions.stack} yet. + + + Add a new conventions/{scanResult.conventions.stack}.json file to customize detection and defaults. + +
+ + View conventions directory + +
+
+
+ ) : null}