@viberails/scanner 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/compute-statistics.ts","../src/utils/walk-directory.ts","../src/detect-conventions.ts","../src/utils/classify-filename.ts","../src/detect-stack.ts","../src/utils/read-package-json.ts","../src/detect-structure.ts","../src/utils/classify-directory.ts","../src/detect-workspace.ts","../src/scan.ts"],"sourcesContent":["export const VERSION = '0.1.0';\n\nexport { computeStatistics } from './compute-statistics.js';\nexport { detectConventions } from './detect-conventions.js';\nexport { detectStack, extractMajorVersion } from './detect-stack.js';\nexport { detectStructure } from './detect-structure.js';\nexport { detectWorkspace } from './detect-workspace.js';\nexport type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport type { PackageJson } from './utils/read-package-json.js';\nexport { readPackageJson } from './utils/read-package-json.js';\nexport type { WalkedDirectory } from './utils/walk-directory.js';\nexport { walkDirectory } from './utils/walk-directory.js';\n","import { readdir, readFile } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport type { CodebaseStatistics, FileStatistic } from '@viberails/types';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { SOURCE_EXTENSIONS, walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Counts lines in a file by reading its contents.\n *\n * @param filePath - Absolute path to the file.\n * @returns Number of lines, or 0 if the file can't be read.\n */\nasync function countLines(filePath: string): Promise<number> {\n try {\n const content = await readFile(filePath, 'utf-8');\n if (content.length === 0) return 0;\n let count = 0;\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10) count++;\n }\n // A file with no trailing newline has one more line than newline count\n if (content.charCodeAt(content.length - 1) !== 10) count++;\n return count;\n } catch {\n return 0;\n }\n}\n\n/**\n * Collects root-level source file names from the project directory.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns Array of source file names in the root directory.\n */\nasync function getRootSourceFiles(projectPath: string): Promise<string[]> {\n try {\n const entries = await readdir(projectPath, { withFileTypes: true });\n const sourceFiles: string[] = [];\n for (const entry of entries) {\n if (entry.isFile()) {\n const ext = extname(entry.name);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFiles.push(entry.name);\n }\n }\n }\n return sourceFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Computes quantitative statistics about a project's source files.\n *\n * Reads each source file and produces aggregate metrics including\n * file counts, line counts, and extension breakdown.\n *\n * @param projectPath - Absolute path to the project root.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns Statistics about the codebase.\n */\nexport async function computeStatistics(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<CodebaseStatistics> {\n const directories = dirs ?? (await walkDirectory(projectPath));\n const rootFiles = await getRootSourceFiles(projectPath);\n\n // Collect all file paths and extensions\n const filesToProcess: Array<{ relativePath: string; absolutePath: string; ext: string }> = [];\n\n // Root-level source files\n for (const name of rootFiles) {\n filesToProcess.push({\n relativePath: name,\n absolutePath: join(projectPath, name),\n ext: extname(name),\n });\n }\n\n // Files from subdirectories\n for (const dir of directories) {\n for (const name of dir.sourceFileNames) {\n filesToProcess.push({\n relativePath: `${dir.relativePath}/${name}`,\n absolutePath: join(dir.absolutePath, name),\n ext: extname(name),\n });\n }\n }\n\n const totalFiles = filesToProcess.length;\n\n if (totalFiles === 0) {\n return {\n totalFiles: 0,\n totalLines: 0,\n averageFileLines: 0,\n largestFiles: [],\n filesByExtension: {},\n };\n }\n\n // Count lines for all files concurrently\n const lineResults = await Promise.all(\n filesToProcess.map(async (file) => ({\n path: file.relativePath,\n lines: await countLines(file.absolutePath),\n ext: file.ext,\n })),\n );\n\n let totalLines = 0;\n const filesByExtension: Record<string, number> = {};\n const allFiles: FileStatistic[] = [];\n\n for (const result of lineResults) {\n totalLines += result.lines;\n filesByExtension[result.ext] = (filesByExtension[result.ext] ?? 0) + 1;\n allFiles.push({ path: result.path, lines: result.lines });\n }\n\n // Sort descending by lines, take top 5\n allFiles.sort((a, b) => b.lines - a.lines);\n const largestFiles = allFiles.slice(0, 5);\n\n return {\n totalFiles,\n totalLines,\n averageFileLines: Math.round(totalLines / totalFiles),\n largestFiles,\n filesByExtension,\n };\n}\n","import { readdir } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\n\n/** Directories to always skip during scanning. */\nconst IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n '.viberails',\n 'coverage',\n '.turbo',\n '.cache',\n '.output',\n]);\n\n/** Source file extensions to count. */\nexport const SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.vue',\n '.svelte',\n '.astro',\n]);\n\nexport interface WalkedDirectory {\n /** Path relative to project root, using forward slashes. */\n relativePath: string;\n /** Absolute path. */\n absolutePath: string;\n /** Number of source files directly in this directory. */\n sourceFileCount: number;\n /** Names of source files in this directory. */\n sourceFileNames: string[];\n /** Depth relative to the project root (1 = direct children). */\n depth: number;\n}\n\n/**\n * Walks a project directory tree using BFS, collecting directory info.\n *\n * @param projectPath - Absolute path to the project root.\n * @param maxDepth - Maximum directory depth to traverse (default 4).\n * @returns Flat list of all visited directories (excluding root itself).\n */\nexport async function walkDirectory(\n projectPath: string,\n maxDepth: number = 4,\n): Promise<WalkedDirectory[]> {\n const results: WalkedDirectory[] = [];\n const queue: Array<{ absolutePath: string; depth: number }> = [];\n\n try {\n const rootEntries = await readdir(projectPath, { withFileTypes: true });\n for (const entry of rootEntries) {\n if (entry.isDirectory() && !entry.isSymbolicLink() && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(projectPath, entry.name), depth: 1 });\n }\n }\n } catch {\n return results;\n }\n\n while (queue.length > 0) {\n const { absolutePath, depth } = queue.shift()!;\n const sourceFileNames: string[] = [];\n\n try {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isSymbolicLink()) continue;\n\n if (entry.isDirectory() && depth < maxDepth && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(absolutePath, entry.name), depth: depth + 1 });\n } else if (entry.isFile()) {\n const dotIndex = entry.name.lastIndexOf('.');\n if (dotIndex > 0) {\n const ext = entry.name.substring(dotIndex);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFileNames.push(entry.name);\n }\n }\n }\n }\n } catch {\n continue;\n }\n\n const rel = relative(projectPath, absolutePath).split('\\\\').join('/');\n results.push({\n relativePath: rel,\n absolutePath,\n sourceFileCount: sourceFileNames.length,\n sourceFileNames,\n depth,\n });\n }\n\n return results;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyFilename } from './utils/classify-filename.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects coding conventions used in a project by analyzing file names\n * and configuration files.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param structure - Previously detected directory structure, used to identify\n * directories by role.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns A record of detected conventions keyed by convention name.\n * Only statistical conventions with sampleSize >= 3 are included.\n */\nexport async function detectConventions(\n projectPath: string,\n structure: DetectedStructure,\n dirs?: WalkedDirectory[],\n): Promise<Record<string, DetectedConvention>> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n const fileNaming = detectFileNaming(dirs);\n if (fileNaming) result.fileNaming = fileNaming;\n\n const componentNaming = detectComponentNaming(dirs, structure);\n if (componentNaming) result.componentNaming = componentNaming;\n\n const hookNaming = detectHookNaming(dirs, structure);\n if (hookNaming) result.hookNaming = hookNaming;\n\n // importAlias is binary (present or not) — bypasses sampleSize threshold\n const importAlias = await detectImportAlias(projectPath);\n if (importAlias) result.importAlias = importAlias;\n\n return result;\n}\n\n/** Strips the file extension, handling multi-dot names like foo.test.ts. */\nfunction stripExtension(filename: string): string {\n const firstDot = filename.indexOf('.');\n return firstDot > 0 ? filename.substring(0, firstDot) : filename;\n}\n\n/**\n * Detects the dominant file naming convention across all directories\n * with 3+ source files. Only returns conventions with consistency >= 70%\n * (medium or high confidence).\n */\nfunction detectFileNaming(dirs: WalkedDirectory[]): DetectedConvention | undefined {\n const conventionCounts = new Map<string, number>();\n let total = 0;\n\n for (const dir of dirs) {\n if (dir.sourceFileCount < 3) continue;\n\n for (const filename of dir.sourceFileNames) {\n if (filename.includes('.test.') || filename.includes('.spec.')) continue;\n const bare = stripExtension(filename);\n if (bare === 'index') continue;\n\n const convention = classifyFilename(bare);\n total++;\n if (convention !== 'unknown') {\n conventionCounts.set(convention, (conventionCounts.get(convention) ?? 0) + 1);\n }\n }\n }\n\n if (total < 3) return undefined;\n\n const sorted = [...conventionCounts.entries()].sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0) return undefined;\n\n const [dominantConvention, dominantCount] = sorted[0];\n const consistency = Math.round((dominantCount / total) * 100);\n const confidence = confidenceFromConsistency(consistency);\n\n if (confidence === 'low') return undefined;\n\n return {\n value: dominantConvention,\n confidence,\n sampleSize: total,\n consistency,\n };\n}\n\n/**\n * Detects component naming convention by checking if .tsx files in\n * component directories use PascalCase filenames.\n */\nfunction detectComponentNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const componentPaths = new Set(\n structure.directories.filter((d) => d.role === 'components').map((d) => d.path),\n );\n\n const tsxFiles: string[] = [];\n for (const dir of dirs) {\n if (!componentPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n if (f.endsWith('.tsx')) tsxFiles.push(f);\n }\n }\n\n if (tsxFiles.length < 3) return undefined;\n\n const pascalCount = tsxFiles.filter((f) => /^[A-Z]/.test(stripExtension(f))).length;\n const consistency = Math.round((pascalCount / tsxFiles.length) * 100);\n const dominantValue = pascalCount >= tsxFiles.length / 2 ? 'PascalCase' : 'camelCase';\n\n return {\n value: dominantValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: tsxFiles.length,\n consistency,\n };\n}\n\n/**\n * Detects hook naming convention by checking if hook files use\n * kebab-case (use-*) or camelCase (useXxx) prefix.\n */\nfunction detectHookNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const hookPaths = new Set(\n structure.directories.filter((d) => d.role === 'hooks').map((d) => d.path),\n );\n\n const hookFiles: string[] = [];\n for (const dir of dirs) {\n if (!hookPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n const bare = stripExtension(f);\n if (bare.startsWith('use-') || /^use[A-Z]/.test(bare)) {\n hookFiles.push(f);\n }\n }\n }\n\n if (hookFiles.length < 3) return undefined;\n\n let kebabCount = 0;\n let camelCount = 0;\n\n for (const filename of hookFiles) {\n const bare = stripExtension(filename);\n if (bare.startsWith('use-')) {\n kebabCount++;\n } else if (/^use[A-Z]/.test(bare)) {\n camelCount++;\n }\n }\n\n const total = hookFiles.length;\n const isDominantKebab = kebabCount >= camelCount;\n const dominantCount = isDominantKebab ? kebabCount : camelCount;\n const consistency = Math.round((dominantCount / total) * 100);\n\n return {\n value: isDominantKebab ? 'use-*' : 'useXxx',\n confidence: confidenceFromConsistency(consistency),\n sampleSize: total,\n consistency,\n };\n}\n\n/** Minimal shape for the tsconfig subset we read. */\ninterface TsConfigSubset {\n compilerOptions?: {\n paths?: Record<string, string[]>;\n };\n}\n\n/**\n * Detects import alias patterns from tsconfig.json paths configuration.\n * Returns undefined if tsconfig.json is missing or has no paths.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n try {\n const raw = await readFile(join(projectPath, 'tsconfig.json'), 'utf-8');\n const tsconfig = JSON.parse(raw) as TsConfigSubset;\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) return undefined;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) return undefined;\n\n return {\n value: aliases[0],\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Naming convention types for filenames.\n */\nexport type FilenameConvention =\n | 'kebab-case'\n | 'camelCase'\n | 'PascalCase'\n | 'snake_case'\n | 'unknown';\n\nconst PATTERNS = [\n { convention: 'PascalCase', regex: /^[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'camelCase', regex: /^[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'kebab-case', regex: /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/ },\n { convention: 'snake_case', regex: /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/ },\n] as const;\n\n/**\n * Classifies a filename (without extension) into a naming convention.\n *\n * Single lowercase words like 'utils' return 'unknown' because they are\n * ambiguous — they could be kebab-case, snake_case, or camelCase without\n * a disambiguating structural marker.\n *\n * @param filename - The bare filename with no extension (e.g. 'user-profile').\n * @returns The detected naming convention, or 'unknown' if ambiguous.\n */\nexport function classifyFilename(filename: string): FilenameConvention {\n for (const { convention, regex } of PATTERNS) {\n if (regex.test(filename)) return convention;\n }\n return 'unknown';\n}\n","import { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedStack, StackItem } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Extracts the major version number from a semver range string.\n *\n * @param range - A semver range such as `\"^15.0.3\"`, `\"~2.1.0\"`, or `\"3.x\"`.\n * @returns The major version string (e.g. `\"15\"`), or `undefined` if extraction fails.\n */\nexport function extractMajorVersion(range: string): string | undefined {\n const match = range.match(/(\\d+)/);\n return match?.[1];\n}\n\n/** Check whether a file exists at the given path. */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Detection mapping tables\n// ---------------------------------------------------------------------------\n\ninterface FrameworkMapping {\n /** Package name to look for in dependencies. */\n dep: string;\n /** Name to use in the StackItem. */\n name: string;\n /** If set, the dep must NOT be present for this rule to match. */\n excludeDep?: string;\n}\n\nconst FRAMEWORK_MAPPINGS: FrameworkMapping[] = [\n { dep: 'next', name: 'nextjs' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: 'vue', name: 'vue' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'hono', name: 'hono' },\n { dep: '@supabase/supabase-js', name: 'supabase' },\n { dep: 'firebase', name: 'firebase' },\n { dep: '@prisma/client', name: 'prisma' },\n { dep: 'prisma', name: 'prisma' },\n { dep: 'drizzle-orm', name: 'drizzle' },\n];\n\nconst STYLING_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'tailwindcss', name: 'tailwindcss' },\n { dep: 'styled-components', name: 'styled-components' },\n { dep: '@emotion/react', name: 'emotion' },\n { dep: 'sass', name: 'sass' },\n];\n\nconst LIBRARY_MAPPINGS: Array<{ deps: string[]; name: string }> = [\n { deps: ['zod'], name: 'zod' },\n { deps: ['@trpc/server', '@trpc/client'], name: 'trpc' },\n { deps: ['@tanstack/react-query'], name: 'react-query' },\n];\n\nconst LOCK_FILE_MAP: Array<{ file: string; name: string }> = [\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'bun.lockb', name: 'bun' },\n { file: 'package-lock.json', name: 'npm' },\n];\n\n// ---------------------------------------------------------------------------\n// Main detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the technology stack of a project by reading its package.json\n * and checking for lock files and configuration files.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @returns The detected technology stack.\n */\nexport async function detectStack(projectPath: string): Promise<DetectedStack> {\n const pkg = await readPackageJson(projectPath);\n const allDeps: Record<string, string> = {\n ...pkg?.dependencies,\n ...pkg?.devDependencies,\n };\n\n const framework = detectFramework(allDeps);\n const language = await detectLanguage(projectPath, allDeps);\n const styling = detectFirst(allDeps, STYLING_MAPPINGS);\n const backend = detectFirst(allDeps, BACKEND_MAPPINGS);\n const packageManager = await detectPackageManager(projectPath);\n const linter = detectLinter(allDeps);\n const testRunner = detectTestRunner(allDeps);\n const libraries = detectLibraries(allDeps);\n\n return {\n ...(framework && { framework }),\n language,\n ...(styling && { styling }),\n ...(backend && { backend }),\n packageManager,\n ...(linter && { linter }),\n ...(testRunner && { testRunner }),\n libraries,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Individual detectors\n// ---------------------------------------------------------------------------\n\nfunction detectFramework(allDeps: Record<string, string>): StackItem | undefined {\n for (const mapping of FRAMEWORK_MAPPINGS) {\n if (!(mapping.dep in allDeps)) continue;\n if (mapping.excludeDep && mapping.excludeDep in allDeps) continue;\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n return undefined;\n}\n\nasync function detectLanguage(\n projectPath: string,\n allDeps: Record<string, string>,\n): Promise<StackItem> {\n if ('typescript' in allDeps) {\n return {\n name: 'typescript',\n version: extractMajorVersion(allDeps.typescript),\n };\n }\n if (await fileExists(join(projectPath, 'tsconfig.json'))) {\n return { name: 'typescript' };\n }\n return { name: 'javascript' };\n}\n\nfunction detectFirst(\n allDeps: Record<string, string>,\n mappings: Array<{ dep: string; name: string }>,\n): StackItem | undefined {\n for (const mapping of mappings) {\n if (mapping.dep in allDeps) {\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n }\n return undefined;\n}\n\nasync function detectPackageManager(projectPath: string): Promise<StackItem> {\n for (const entry of LOCK_FILE_MAP) {\n if (await fileExists(join(projectPath, entry.file))) {\n return { name: entry.name };\n }\n }\n return { name: 'npm' };\n}\n\nfunction detectLinter(allDeps: Record<string, string>): StackItem | undefined {\n if ('eslint' in allDeps) {\n return { name: 'eslint', version: extractMajorVersion(allDeps.eslint) };\n }\n if ('@biomejs/biome' in allDeps) {\n return {\n name: 'biome',\n version: extractMajorVersion(allDeps['@biomejs/biome']),\n };\n }\n return undefined;\n}\n\nfunction detectTestRunner(allDeps: Record<string, string>): StackItem | undefined {\n if ('vitest' in allDeps) {\n return { name: 'vitest', version: extractMajorVersion(allDeps.vitest) };\n }\n if ('jest' in allDeps) {\n return { name: 'jest', version: extractMajorVersion(allDeps.jest) };\n }\n return undefined;\n}\n\nfunction detectLibraries(allDeps: Record<string, string>): StackItem[] {\n const libs: StackItem[] = [];\n for (const mapping of LIBRARY_MAPPINGS) {\n const found = mapping.deps.find((dep) => dep in allDeps);\n if (found) {\n libs.push({\n name: mapping.name,\n version: extractMajorVersion(allDeps[found]),\n });\n }\n }\n return libs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Minimal interface for the fields we read from package.json.\n */\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Safely reads and parses a package.json file from the given directory.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.\n */\nexport async function readPackageJson(projectPath: string): Promise<PackageJson | null> {\n try {\n const raw = await readFile(join(projectPath, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n","import type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyDirectory } from './utils/classify-directory.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects the directory structure and organization of a project.\n *\n * Classifies directories by role, detects whether a src/ directory is in use,\n * and identifies test file patterns.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns The detected directory structure.\n */\nexport async function detectStructure(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<DetectedStructure> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n // Detect srcDir\n const hasSrcDir = dirs.some((d) => d.relativePath === 'src' || d.relativePath.startsWith('src/'));\n const srcDir = hasSrcDir ? 'src' : undefined;\n\n // Classify directories\n const directories = dirs.map((d) => classifyDirectory(d)).filter((d) => d !== null);\n\n // Detect test pattern\n const testPattern = detectTestPattern(dirs);\n\n return {\n ...(srcDir !== undefined && { srcDir }),\n directories,\n ...(testPattern !== undefined && { testPattern }),\n };\n}\n\n/**\n * Detects the dominant test file naming pattern from all source files.\n * Returns undefined if fewer than 3 test files are found.\n */\nfunction detectTestPattern(\n dirs: Array<{ sourceFileNames: string[] }>,\n): DetectedConvention<string> | undefined {\n const allFiles = dirs.flatMap((d) => d.sourceFileNames);\n const testFiles = allFiles.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n\n if (testFiles.length < 3) return undefined;\n\n const dotTestCount = testFiles.filter((f) => f.includes('.test.')).length;\n const dotSpecCount = testFiles.filter((f) => f.includes('.spec.')).length;\n\n const isDotTest = dotTestCount >= dotSpecCount;\n const dominantSep = isDotTest ? '.test.' : '.spec.';\n const dominantCount = isDotTest ? dotTestCount : dotSpecCount;\n const consistency = Math.round((dominantCount / testFiles.length) * 100);\n\n // Find most common extension among dominant test files\n const extCounts = new Map<string, number>();\n for (const f of testFiles.filter((f) => f.includes(dominantSep))) {\n const ext = f.substring(f.lastIndexOf('.'));\n extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n const topExt = [...extCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? '.ts';\n\n const sep = isDotTest ? 'test' : 'spec';\n\n return {\n value: `*.${sep}${topExt}`,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: testFiles.length,\n consistency,\n };\n}\n","import type { Confidence, DirectoryRole } from '@viberails/types';\nimport type { WalkedDirectory } from './walk-directory.js';\n\nexport interface ClassifiedDirectory {\n path: string;\n role: DirectoryRole;\n fileCount: number;\n confidence: Confidence;\n}\n\ninterface RolePattern {\n role: DirectoryRole;\n pathPatterns: string[];\n}\n\nconst ROLE_PATTERNS: RolePattern[] = [\n { role: 'pages', pathPatterns: ['src/app', 'src/pages', 'app', 'pages'] },\n { role: 'components', pathPatterns: ['src/components', 'components'] },\n { role: 'hooks', pathPatterns: ['src/hooks', 'hooks'] },\n {\n role: 'utils',\n pathPatterns: ['src/lib', 'src/utils', 'src/helpers', 'lib', 'utils', 'helpers'],\n },\n { role: 'types', pathPatterns: ['src/types', 'types', 'src/@types', '@types'] },\n { role: 'tests', pathPatterns: ['__tests__', 'tests', 'test', 'src/__tests__', 'src/tests'] },\n { role: 'styles', pathPatterns: ['src/styles', 'styles', 'src/css', 'css'] },\n { role: 'api', pathPatterns: ['src/api', 'api', 'src/app/api', 'app/api'] },\n { role: 'config', pathPatterns: ['config', 'src/config'] },\n];\n\n/**\n * Classifies a directory by its role in the project.\n *\n * @param dir - A walked directory entry.\n * @returns Classified directory info, or null if the directory should be skipped.\n */\nexport function classifyDirectory(dir: WalkedDirectory): ClassifiedDirectory | null {\n // Try name-based matching first\n const nameMatch = matchByName(dir.relativePath);\n if (nameMatch) {\n const confidence: Confidence = dir.sourceFileCount > 0 ? 'high' : 'low';\n return {\n path: dir.relativePath,\n role: nameMatch,\n fileCount: dir.sourceFileCount,\n confidence,\n };\n }\n\n // Skip directories with no source files if no name match\n if (dir.sourceFileCount === 0) return null;\n\n // Content-based heuristics\n const contentRole = inferFromContent(dir);\n if (contentRole) return contentRole;\n\n // Has source files but no classification\n return {\n path: dir.relativePath,\n role: 'unknown',\n fileCount: dir.sourceFileCount,\n confidence: 'low',\n };\n}\n\n/**\n * Matches a relative path against known role patterns.\n * Returns the role if matched, or null.\n */\nfunction matchByName(relativePath: string): DirectoryRole | null {\n for (const { role, pathPatterns } of ROLE_PATTERNS) {\n for (const pattern of pathPatterns) {\n if (relativePath === pattern) return role;\n }\n }\n return null;\n}\n\n/**\n * Infers directory role from file contents/names.\n */\nfunction inferFromContent(dir: WalkedDirectory): ClassifiedDirectory | null {\n const { sourceFileNames, sourceFileCount } = dir;\n\n // Check for hook files (use-* kebab or useXxx camelCase prefix)\n const hookFiles = sourceFileNames.filter((f) => {\n const name = f.split('.')[0];\n return name.startsWith('use-') || /^use[A-Z]/.test(name);\n });\n if (hookFiles.length > 0 && hookFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'hooks',\n fileCount: sourceFileCount,\n confidence: hookFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n // Check for test files\n const testFiles = sourceFileNames.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n if (testFiles.length > 0 && testFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'tests',\n fileCount: sourceFileCount,\n confidence: testFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n return null;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { DetectedWorkspace, WorkspacePackage } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Detects workspace configuration for monorepo projects.\n *\n * Checks for `pnpm-workspace.yaml` first, then falls back to\n * `package.json` `workspaces` field. Returns `undefined` for\n * single-package projects.\n *\n * @param projectRoot - Absolute path to the project root.\n * @returns Detected workspace info, or `undefined` if not a monorepo.\n */\nexport async function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined> {\n const patterns = await readWorkspacePatterns(projectRoot);\n if (!patterns || patterns.length === 0) return undefined;\n\n const packageDirs = await resolvePatterns(projectRoot, patterns);\n const packages = await resolvePackages(projectRoot, packageDirs);\n\n if (packages.length === 0) return undefined;\n\n // Resolve internal deps: check if any dependency name matches a workspace package\n const packageNames = new Set(packages.map((p) => p.name));\n for (const pkg of packages) {\n pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));\n }\n\n return { patterns, packages };\n}\n\n/**\n * Reads workspace glob patterns from pnpm-workspace.yaml or package.json.\n */\nasync function readWorkspacePatterns(projectRoot: string): Promise<string[] | undefined> {\n // Try pnpm-workspace.yaml first\n try {\n const yaml = await readFile(join(projectRoot, 'pnpm-workspace.yaml'), 'utf-8');\n return parsePnpmWorkspaceYaml(yaml);\n } catch {\n // Not found, try package.json\n }\n\n // Fall back to package.json workspaces field\n const pkg = await readPackageJson(projectRoot);\n if (!pkg) return undefined;\n\n const raw = pkg as Record<string, unknown>;\n const workspaces = raw.workspaces;\n\n if (Array.isArray(workspaces)) {\n return workspaces.filter((w): w is string => typeof w === 'string');\n }\n\n // Handle { packages: [...] } format\n if (workspaces && typeof workspaces === 'object' && 'packages' in workspaces) {\n const nested = (workspaces as { packages: unknown }).packages;\n if (Array.isArray(nested)) {\n return nested.filter((w): w is string => typeof w === 'string');\n }\n }\n\n return undefined;\n}\n\n/**\n * Parses workspace patterns from pnpm-workspace.yaml content.\n * Handles the common format: `packages:\\n - 'packages/*'`\n */\nfunction parsePnpmWorkspaceYaml(content: string): string[] {\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (trimmed === 'packages:') {\n inPackages = true;\n continue;\n }\n\n // Stop at next top-level key\n if (inPackages && trimmed.length > 0 && !trimmed.startsWith('-')) {\n break;\n }\n\n if (inPackages && trimmed.startsWith('-')) {\n // Extract the pattern, stripping quotes and leading dash\n const value = trimmed\n .slice(1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n if (value) patterns.push(value);\n }\n }\n\n return patterns;\n}\n\n/**\n * Resolves workspace glob patterns to actual directory paths.\n * Supports simple `*` wildcard matching (e.g. `packages/*`).\n */\nasync function resolvePatterns(projectRoot: string, patterns: string[]): Promise<string[]> {\n const dirs: string[] = [];\n\n for (const pattern of patterns) {\n if (pattern.endsWith('/*')) {\n // Simple wildcard: list children of the parent directory\n const parent = join(projectRoot, pattern.slice(0, -2));\n try {\n const entries = await readdir(parent, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n dirs.push(join(parent, entry.name));\n }\n }\n } catch {\n // Parent directory doesn't exist, skip\n }\n } else {\n // Literal path\n dirs.push(join(projectRoot, pattern));\n }\n }\n\n return dirs;\n}\n\n/**\n * Resolves workspace directories to WorkspacePackage objects.\n * Skips directories without a valid package.json.\n */\nasync function resolvePackages(projectRoot: string, dirs: string[]): Promise<WorkspacePackage[]> {\n const packages: WorkspacePackage[] = [];\n\n for (const dir of dirs) {\n const pkg = await readPackageJson(dir);\n if (!pkg?.name) continue;\n\n // Collect all dependency names as potential internal deps\n const allDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n\n packages.push({\n name: pkg.name,\n path: dir,\n relativePath: relative(projectRoot, dir),\n internalDeps: allDeps,\n });\n }\n\n return packages;\n}\n","import { stat } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { ScanResult } from '@viberails/types';\nimport { computeStatistics } from './compute-statistics.js';\nimport { detectConventions } from './detect-conventions.js';\nimport { detectStack } from './detect-stack.js';\nimport { detectStructure } from './detect-structure.js';\nimport { detectWorkspace } from './detect-workspace.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Options for the scan function.\n */\nexport type ScanOptions = {};\n\n/** Patterns that indicate a directory is inside test fixtures, not real project code. */\nconst FIXTURE_PATTERNS = [\n /^tests\\/fixtures(\\/|$)/,\n /^test\\/fixtures(\\/|$)/,\n /^__tests__\\/fixtures(\\/|$)/,\n /^fixtures(\\/|$)/,\n];\n\n/**\n * Filters out directories that are inside test fixture directories.\n * Fixture directories contain sample project structures that should not\n * be analyzed as part of the real project.\n */\nfunction filterFixtureDirs(dirs: WalkedDirectory[]): WalkedDirectory[] {\n return dirs.filter((d) => !FIXTURE_PATTERNS.some((pattern) => pattern.test(d.relativePath)));\n}\n\n/**\n * Scans a project directory and returns a comprehensive analysis of its\n * stack, structure, conventions, and statistics.\n *\n * This is the primary entry point for the scanner package. It composes\n * all individual detection functions into a unified ScanResult.\n *\n * @param projectPath - Absolute or relative path to the project root.\n * @param options - Optional scan configuration.\n * @returns Complete scan result for the project.\n * @throws If the project path does not exist or is not a directory.\n */\nexport async function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult> {\n const root = resolve(projectPath);\n\n // Validate that the path exists and is a directory\n try {\n const st = await stat(root);\n if (!st.isDirectory()) {\n throw new Error(`Project path is not a directory: ${root}`);\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith('Project path is not')) {\n throw err;\n }\n throw new Error(`Project path does not exist: ${root}`);\n }\n\n // Walk directory tree once and share with all detectors\n const allDirs = await walkDirectory(root, 4);\n const dirs = filterFixtureDirs(allDirs);\n\n // Run independent detectors in parallel, passing shared walk result\n const [stack, structure, statistics] = await Promise.all([\n detectStack(root),\n detectStructure(root, dirs),\n computeStatistics(root, dirs),\n ]);\n\n // detectConventions depends on structure result\n const conventions = await detectConventions(root, structure, dirs);\n\n // Detect workspace configuration (monorepo support)\n const workspace = await detectWorkspace(root);\n\n return {\n root,\n stack,\n structure,\n conventions,\n statistics,\n workspace,\n };\n}\n"],"mappings":"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","names":["import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_types","f","import_promises","import_node_path","import_promises","import_node_path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/aggregate.ts","../src/compute-statistics.ts","../src/utils/walk-directory.ts","../src/detect-conventions.ts","../src/utils/classify-filename.ts","../src/detect-stack.ts","../src/utils/read-package-json.ts","../src/detect-structure.ts","../src/utils/classify-directory.ts","../src/detect-workspace.ts","../src/scan-package.ts","../src/scan.ts"],"sourcesContent":["export const VERSION = '0.1.0';\n\nexport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nexport { computeStatistics } from './compute-statistics.js';\nexport { detectConventions } from './detect-conventions.js';\nexport { detectStack, extractMajorVersion } from './detect-stack.js';\nexport { detectStructure } from './detect-structure.js';\nexport { detectWorkspace } from './detect-workspace.js';\nexport { scanPackage } from './scan-package.js';\nexport type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport type { PackageJson } from './utils/read-package-json.js';\nexport { readPackageJson } from './utils/read-package-json.js';\nexport type { WalkedDirectory } from './utils/walk-directory.js';\nexport { walkDirectory } from './utils/walk-directory.js';\n","import {\n confidenceFromConsistency,\n type CodebaseStatistics,\n type DetectedConvention,\n type DetectedStack,\n type DetectedStructure,\n type PackageScanResult,\n} from '@viberails/types';\n\n/** Framework priority order — higher-priority frameworks become the primary. */\nconst FRAMEWORK_PRIORITY = [\n 'nextjs',\n 'sveltekit',\n 'astro',\n 'expo',\n 'react-native',\n 'svelte',\n 'vue',\n 'react',\n];\n\n/**\n * Combines per-package stacks into a single aggregate stack.\n *\n * TypeScript wins over JavaScript if any package uses it.\n * The highest-priority framework becomes the primary; others go into libraries.\n */\nexport function aggregateStacks(packages: PackageScanResult[]): DetectedStack {\n if (packages.length === 1) return packages[0].stack;\n\n const language = packages.some((p) => p.stack.language.name === 'typescript')\n ? packages.find((p) => p.stack.language.name === 'typescript')!.stack.language\n : packages[0].stack.language;\n\n const packageManager = packages[0].stack.packageManager;\n\n const frameworkPackages = packages.filter((p) => p.stack.framework);\n let framework: (typeof packages)[0]['stack']['framework'];\n if (frameworkPackages.length > 0) {\n frameworkPackages.sort((a, b) => {\n const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework!.name);\n const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework!.name);\n return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);\n });\n framework = frameworkPackages[0].stack.framework;\n }\n\n const libraryMap = new Map<string, { name: string; version?: string }>();\n\n for (const pkg of packages) {\n if (pkg.stack.framework && pkg.stack.framework.name !== framework?.name) {\n libraryMap.set(pkg.stack.framework.name, pkg.stack.framework);\n }\n for (const lib of pkg.stack.libraries) {\n if (!libraryMap.has(lib.name)) {\n libraryMap.set(lib.name, lib);\n }\n }\n }\n\n const styling = packages.find((p) => p.stack.styling)?.stack.styling;\n const backend = packages.find((p) => p.stack.backend)?.stack.backend;\n const linter = packages.find((p) => p.stack.linter)?.stack.linter;\n const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;\n\n return {\n language,\n packageManager,\n framework,\n libraries: [...libraryMap.values()],\n styling,\n backend,\n linter,\n testRunner,\n };\n}\n\n/**\n * Combines per-package structures into a single aggregate structure.\n *\n * Directory paths are prefixed with the package's relativePath.\n */\nexport function aggregateStructures(packages: PackageScanResult[]): DetectedStructure {\n if (packages.length === 1) return packages[0].structure;\n\n const srcDir = packages.some((p) => p.structure.srcDir) ? 'src' : undefined;\n\n const directories = packages.flatMap((pkg) =>\n pkg.structure.directories.map((dir) => ({\n ...dir,\n path: pkg.relativePath ? `${pkg.relativePath}/${dir.path}` : dir.path,\n })),\n );\n\n const testPatterns = packages\n .map((p) => p.structure.testPattern)\n .filter((t): t is DetectedConvention<string> => t !== undefined);\n\n let testPattern: DetectedConvention<string> | undefined;\n if (testPatterns.length > 0) {\n const counts = new Map<string, { count: number; pattern: DetectedConvention<string> }>();\n for (const tp of testPatterns) {\n const existing = counts.get(tp.value);\n if (existing) {\n existing.count++;\n } else {\n counts.set(tp.value, { count: 1, pattern: tp });\n }\n }\n let best = { count: 0, pattern: testPatterns[0] };\n for (const entry of counts.values()) {\n if (entry.count > best.count) best = entry;\n }\n testPattern = best.pattern;\n }\n\n return { srcDir, directories, testPattern };\n}\n\n/**\n * Combines per-package conventions into aggregate conventions.\n *\n * When all packages agree on a convention, reports it with averaged consistency.\n * When packages disagree, scales consistency by agreement ratio.\n * Omits conventions present in fewer than half of packages.\n */\nexport function aggregateConventions(\n packages: PackageScanResult[],\n): Record<string, DetectedConvention> {\n if (packages.length === 1) return packages[0].conventions;\n\n const allKeys = new Set<string>();\n for (const pkg of packages) {\n for (const key of Object.keys(pkg.conventions)) {\n allKeys.add(key);\n }\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n for (const key of allKeys) {\n const entries = packages\n .map((p) => p.conventions[key])\n .filter((c): c is DetectedConvention => c !== undefined);\n\n if (entries.length < packages.length / 2) continue;\n\n const valueCounts = new Map<\n string,\n { count: number; totalConsistency: number; totalSamples: number }\n >();\n for (const entry of entries) {\n const existing = valueCounts.get(entry.value);\n if (existing) {\n existing.count++;\n existing.totalConsistency += entry.consistency;\n existing.totalSamples += entry.sampleSize;\n } else {\n valueCounts.set(entry.value, {\n count: 1,\n totalConsistency: entry.consistency,\n totalSamples: entry.sampleSize,\n });\n }\n }\n\n let majorityValue = '';\n let majorityData = { count: 0, totalConsistency: 0, totalSamples: 0 };\n for (const [value, data] of valueCounts) {\n if (data.count > majorityData.count) {\n majorityValue = value;\n majorityData = data;\n }\n }\n\n const agreement = majorityData.count / entries.length;\n const avgConsistency = majorityData.totalConsistency / majorityData.count;\n const consistency = Math.round(avgConsistency * agreement);\n\n result[key] = {\n value: majorityValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: majorityData.totalSamples,\n consistency,\n };\n }\n\n return result;\n}\n\n/**\n * Combines per-package statistics into aggregate statistics.\n *\n * Sums totals, recomputes averages, merges largest files with path prefixing.\n */\nexport function aggregateStatistics(packages: PackageScanResult[]): CodebaseStatistics {\n if (packages.length === 1) return packages[0].statistics;\n\n const totalFiles = packages.reduce((sum, p) => sum + p.statistics.totalFiles, 0);\n const totalLines = packages.reduce((sum, p) => sum + p.statistics.totalLines, 0);\n const averageFileLines = totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0;\n\n const largestFiles = packages\n .flatMap((pkg) =>\n pkg.statistics.largestFiles.map((f) => ({\n path: pkg.relativePath ? `${pkg.relativePath}/${f.path}` : f.path,\n lines: f.lines,\n })),\n )\n .sort((a, b) => b.lines - a.lines)\n .slice(0, 5);\n\n const filesByExtension: Record<string, number> = {};\n for (const pkg of packages) {\n for (const [ext, count] of Object.entries(pkg.statistics.filesByExtension)) {\n filesByExtension[ext] = (filesByExtension[ext] ?? 0) + count;\n }\n }\n\n return { totalFiles, totalLines, averageFileLines, largestFiles, filesByExtension };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport type { CodebaseStatistics, FileStatistic } from '@viberails/types';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { SOURCE_EXTENSIONS, walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Counts lines in a file by reading its contents.\n *\n * @param filePath - Absolute path to the file.\n * @returns Number of lines, or 0 if the file can't be read.\n */\nasync function countLines(filePath: string): Promise<number> {\n try {\n const content = await readFile(filePath, 'utf-8');\n if (content.length === 0) return 0;\n let count = 0;\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10) count++;\n }\n // A file with no trailing newline has one more line than newline count\n if (content.charCodeAt(content.length - 1) !== 10) count++;\n return count;\n } catch {\n return 0;\n }\n}\n\n/**\n * Collects root-level source file names from the project directory.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns Array of source file names in the root directory.\n */\nasync function getRootSourceFiles(projectPath: string): Promise<string[]> {\n try {\n const entries = await readdir(projectPath, { withFileTypes: true });\n const sourceFiles: string[] = [];\n for (const entry of entries) {\n if (entry.isFile()) {\n const ext = extname(entry.name);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFiles.push(entry.name);\n }\n }\n }\n return sourceFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Computes quantitative statistics about a project's source files.\n *\n * Reads each source file and produces aggregate metrics including\n * file counts, line counts, and extension breakdown.\n *\n * @param projectPath - Absolute path to the project root.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns Statistics about the codebase.\n */\nexport async function computeStatistics(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<CodebaseStatistics> {\n const directories = dirs ?? (await walkDirectory(projectPath));\n const rootFiles = await getRootSourceFiles(projectPath);\n\n // Collect all file paths and extensions\n const filesToProcess: Array<{ relativePath: string; absolutePath: string; ext: string }> = [];\n\n // Root-level source files\n for (const name of rootFiles) {\n filesToProcess.push({\n relativePath: name,\n absolutePath: join(projectPath, name),\n ext: extname(name),\n });\n }\n\n // Files from subdirectories\n for (const dir of directories) {\n for (const name of dir.sourceFileNames) {\n filesToProcess.push({\n relativePath: `${dir.relativePath}/${name}`,\n absolutePath: join(dir.absolutePath, name),\n ext: extname(name),\n });\n }\n }\n\n const totalFiles = filesToProcess.length;\n\n if (totalFiles === 0) {\n return {\n totalFiles: 0,\n totalLines: 0,\n averageFileLines: 0,\n largestFiles: [],\n filesByExtension: {},\n };\n }\n\n // Count lines for all files concurrently\n const lineResults = await Promise.all(\n filesToProcess.map(async (file) => ({\n path: file.relativePath,\n lines: await countLines(file.absolutePath),\n ext: file.ext,\n })),\n );\n\n let totalLines = 0;\n const filesByExtension: Record<string, number> = {};\n const allFiles: FileStatistic[] = [];\n\n for (const result of lineResults) {\n totalLines += result.lines;\n filesByExtension[result.ext] = (filesByExtension[result.ext] ?? 0) + 1;\n allFiles.push({ path: result.path, lines: result.lines });\n }\n\n // Sort descending by lines, take top 5\n allFiles.sort((a, b) => b.lines - a.lines);\n const largestFiles = allFiles.slice(0, 5);\n\n return {\n totalFiles,\n totalLines,\n averageFileLines: Math.round(totalLines / totalFiles),\n largestFiles,\n filesByExtension,\n };\n}\n","import { readdir } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\n\n/** Directories to always skip during scanning. */\nconst IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n '.viberails',\n 'coverage',\n '.turbo',\n '.cache',\n '.output',\n]);\n\n/** Source file extensions to count. */\nexport const SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.vue',\n '.svelte',\n '.astro',\n]);\n\nexport interface WalkedDirectory {\n /** Path relative to project root, using forward slashes. */\n relativePath: string;\n /** Absolute path. */\n absolutePath: string;\n /** Number of source files directly in this directory. */\n sourceFileCount: number;\n /** Names of source files in this directory. */\n sourceFileNames: string[];\n /** Depth relative to the project root (1 = direct children). */\n depth: number;\n}\n\n/**\n * Walks a project directory tree using BFS, collecting directory info.\n *\n * @param projectPath - Absolute path to the project root.\n * @param maxDepth - Maximum directory depth to traverse (default 4).\n * @returns Flat list of all visited directories (excluding root itself).\n */\nexport async function walkDirectory(\n projectPath: string,\n maxDepth: number = 4,\n): Promise<WalkedDirectory[]> {\n const results: WalkedDirectory[] = [];\n const queue: Array<{ absolutePath: string; depth: number }> = [];\n\n try {\n const rootEntries = await readdir(projectPath, { withFileTypes: true });\n for (const entry of rootEntries) {\n if (entry.isDirectory() && !entry.isSymbolicLink() && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(projectPath, entry.name), depth: 1 });\n }\n }\n } catch {\n return results;\n }\n\n while (queue.length > 0) {\n const { absolutePath, depth } = queue.shift()!;\n const sourceFileNames: string[] = [];\n\n try {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isSymbolicLink()) continue;\n\n if (entry.isDirectory() && depth < maxDepth && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(absolutePath, entry.name), depth: depth + 1 });\n } else if (entry.isFile()) {\n const dotIndex = entry.name.lastIndexOf('.');\n if (dotIndex > 0) {\n const ext = entry.name.substring(dotIndex);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFileNames.push(entry.name);\n }\n }\n }\n }\n } catch {\n continue;\n }\n\n const rel = relative(projectPath, absolutePath).split('\\\\').join('/');\n results.push({\n relativePath: rel,\n absolutePath,\n sourceFileCount: sourceFileNames.length,\n sourceFileNames,\n depth,\n });\n }\n\n return results;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyFilename } from './utils/classify-filename.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects coding conventions used in a project by analyzing file names\n * and configuration files.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param structure - Previously detected directory structure, used to identify\n * directories by role.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns A record of detected conventions keyed by convention name.\n * Only statistical conventions with sampleSize >= 3 are included.\n */\nexport async function detectConventions(\n projectPath: string,\n structure: DetectedStructure,\n dirs?: WalkedDirectory[],\n): Promise<Record<string, DetectedConvention>> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n const fileNaming = detectFileNaming(dirs);\n if (fileNaming) result.fileNaming = fileNaming;\n\n const componentNaming = detectComponentNaming(dirs, structure);\n if (componentNaming) result.componentNaming = componentNaming;\n\n const hookNaming = detectHookNaming(dirs, structure);\n if (hookNaming) result.hookNaming = hookNaming;\n\n // importAlias is binary (present or not) — bypasses sampleSize threshold\n const importAlias = await detectImportAlias(projectPath);\n if (importAlias) result.importAlias = importAlias;\n\n return result;\n}\n\n/** Strips the file extension, handling multi-dot names like foo.test.ts. */\nfunction stripExtension(filename: string): string {\n const firstDot = filename.indexOf('.');\n return firstDot > 0 ? filename.substring(0, firstDot) : filename;\n}\n\n/**\n * Detects the dominant file naming convention across all directories\n * with 3+ source files. Only returns conventions with consistency >= 70%\n * (medium or high confidence).\n */\nfunction detectFileNaming(dirs: WalkedDirectory[]): DetectedConvention | undefined {\n const conventionCounts = new Map<string, number>();\n let total = 0;\n\n for (const dir of dirs) {\n if (dir.sourceFileCount < 3) continue;\n\n for (const filename of dir.sourceFileNames) {\n if (filename.includes('.test.') || filename.includes('.spec.')) continue;\n const bare = stripExtension(filename);\n if (bare === 'index') continue;\n\n const convention = classifyFilename(bare);\n total++;\n if (convention !== 'unknown') {\n conventionCounts.set(convention, (conventionCounts.get(convention) ?? 0) + 1);\n }\n }\n }\n\n if (total < 3) return undefined;\n\n const sorted = [...conventionCounts.entries()].sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0) return undefined;\n\n const [dominantConvention, dominantCount] = sorted[0];\n const consistency = Math.round((dominantCount / total) * 100);\n const confidence = confidenceFromConsistency(consistency);\n\n if (confidence === 'low') return undefined;\n\n return {\n value: dominantConvention,\n confidence,\n sampleSize: total,\n consistency,\n };\n}\n\n/**\n * Detects component naming convention by checking if .tsx files in\n * component directories use PascalCase filenames.\n */\nfunction detectComponentNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const componentPaths = new Set(\n structure.directories.filter((d) => d.role === 'components').map((d) => d.path),\n );\n\n const tsxFiles: string[] = [];\n for (const dir of dirs) {\n if (!componentPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n if (f.endsWith('.tsx')) tsxFiles.push(f);\n }\n }\n\n if (tsxFiles.length < 3) return undefined;\n\n const pascalCount = tsxFiles.filter((f) => /^[A-Z]/.test(stripExtension(f))).length;\n const consistency = Math.round((pascalCount / tsxFiles.length) * 100);\n const dominantValue = pascalCount >= tsxFiles.length / 2 ? 'PascalCase' : 'camelCase';\n\n return {\n value: dominantValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: tsxFiles.length,\n consistency,\n };\n}\n\n/**\n * Detects hook naming convention by checking if hook files use\n * kebab-case (use-*) or camelCase (useXxx) prefix.\n */\nfunction detectHookNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const hookPaths = new Set(\n structure.directories.filter((d) => d.role === 'hooks').map((d) => d.path),\n );\n\n const hookFiles: string[] = [];\n for (const dir of dirs) {\n if (!hookPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n const bare = stripExtension(f);\n if (bare.startsWith('use-') || /^use[A-Z]/.test(bare)) {\n hookFiles.push(f);\n }\n }\n }\n\n if (hookFiles.length < 3) return undefined;\n\n let kebabCount = 0;\n let camelCount = 0;\n\n for (const filename of hookFiles) {\n const bare = stripExtension(filename);\n if (bare.startsWith('use-')) {\n kebabCount++;\n } else if (/^use[A-Z]/.test(bare)) {\n camelCount++;\n }\n }\n\n const total = hookFiles.length;\n const isDominantKebab = kebabCount >= camelCount;\n const dominantCount = isDominantKebab ? kebabCount : camelCount;\n const consistency = Math.round((dominantCount / total) * 100);\n\n return {\n value: isDominantKebab ? 'use-*' : 'useXxx',\n confidence: confidenceFromConsistency(consistency),\n sampleSize: total,\n consistency,\n };\n}\n\n/** Minimal shape for the tsconfig subset we read. */\ninterface TsConfigSubset {\n compilerOptions?: {\n paths?: Record<string, string[]>;\n };\n}\n\n/**\n * Detects import alias patterns from tsconfig.json paths configuration.\n * Returns undefined if tsconfig.json is missing or has no paths.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n try {\n const raw = await readFile(join(projectPath, 'tsconfig.json'), 'utf-8');\n const tsconfig = JSON.parse(raw) as TsConfigSubset;\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) return undefined;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) return undefined;\n\n return {\n value: aliases.join(','),\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Naming convention types for filenames.\n */\nexport type FilenameConvention =\n | 'kebab-case'\n | 'camelCase'\n | 'PascalCase'\n | 'snake_case'\n | 'unknown';\n\nconst PATTERNS = [\n { convention: 'PascalCase', regex: /^[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'camelCase', regex: /^[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'kebab-case', regex: /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/ },\n { convention: 'snake_case', regex: /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/ },\n] as const;\n\n/**\n * Classifies a filename (without extension) into a naming convention.\n *\n * Single lowercase words like 'utils' return 'unknown' because they are\n * ambiguous — they could be kebab-case, snake_case, or camelCase without\n * a disambiguating structural marker.\n *\n * @param filename - The bare filename with no extension (e.g. 'user-profile').\n * @returns The detected naming convention, or 'unknown' if ambiguous.\n */\nexport function classifyFilename(filename: string): FilenameConvention {\n for (const { convention, regex } of PATTERNS) {\n if (regex.test(filename)) return convention;\n }\n return 'unknown';\n}\n","import { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedStack, StackItem } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Extracts the major version number from a semver range string.\n *\n * @param range - A semver range such as `\"^15.0.3\"`, `\"~2.1.0\"`, or `\"3.x\"`.\n * @returns The major version string (e.g. `\"15\"`), or `undefined` if extraction fails.\n */\nexport function extractMajorVersion(range: string): string | undefined {\n const match = range.match(/(\\d+)/);\n return match?.[1];\n}\n\n/** Check whether a file exists at the given path. */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Detection mapping tables\n// ---------------------------------------------------------------------------\n\ninterface FrameworkMapping {\n /** Package name to look for in dependencies. */\n dep: string;\n /** Name to use in the StackItem. */\n name: string;\n /** If set, the dep must NOT be present for this rule to match. */\n excludeDep?: string;\n}\n\nconst FRAMEWORK_MAPPINGS: FrameworkMapping[] = [\n { dep: 'next', name: 'nextjs' },\n { dep: 'expo', name: 'expo' },\n { dep: 'react-native', name: 'react-native', excludeDep: 'expo' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: 'vue', name: 'vue' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'hono', name: 'hono' },\n { dep: '@supabase/supabase-js', name: 'supabase' },\n { dep: 'firebase', name: 'firebase' },\n { dep: '@prisma/client', name: 'prisma' },\n { dep: 'prisma', name: 'prisma' },\n { dep: 'drizzle-orm', name: 'drizzle' },\n];\n\nconst STYLING_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'tailwindcss', name: 'tailwindcss' },\n { dep: 'styled-components', name: 'styled-components' },\n { dep: '@emotion/react', name: 'emotion' },\n { dep: 'sass', name: 'sass' },\n];\n\nconst LIBRARY_MAPPINGS: Array<{ deps: string[]; name: string }> = [\n { deps: ['zod'], name: 'zod' },\n { deps: ['@trpc/server', '@trpc/client'], name: 'trpc' },\n { deps: ['@tanstack/react-query'], name: 'react-query' },\n];\n\nconst LOCK_FILE_MAP: Array<{ file: string; name: string }> = [\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'bun.lockb', name: 'bun' },\n { file: 'package-lock.json', name: 'npm' },\n];\n\n// ---------------------------------------------------------------------------\n// Main detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the technology stack of a project by reading its package.json\n * and checking for lock files and configuration files.\n *\n * When `additionalDeps` is provided, they are merged as a base layer\n * beneath the package's own deps. This allows monorepo root-level deps\n * (e.g. typescript, eslint) to be visible during per-package scanning.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param additionalDeps - Optional base dependencies merged under package deps.\n * @returns The detected technology stack.\n */\nexport async function detectStack(\n projectPath: string,\n additionalDeps?: Record<string, string>,\n): Promise<DetectedStack> {\n const pkg = await readPackageJson(projectPath);\n const allDeps: Record<string, string> = {\n ...additionalDeps,\n ...pkg?.dependencies,\n ...pkg?.devDependencies,\n };\n\n const framework = detectFramework(allDeps);\n const language = await detectLanguage(projectPath, allDeps);\n const styling = detectFirst(allDeps, STYLING_MAPPINGS);\n const backend = detectFirst(allDeps, BACKEND_MAPPINGS);\n const packageManager = await detectPackageManager(projectPath);\n const linter = detectLinter(allDeps);\n const testRunner = detectTestRunner(allDeps);\n const libraries = detectLibraries(allDeps);\n\n return {\n ...(framework && { framework }),\n language,\n ...(styling && { styling }),\n ...(backend && { backend }),\n packageManager,\n ...(linter && { linter }),\n ...(testRunner && { testRunner }),\n libraries,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Individual detectors\n// ---------------------------------------------------------------------------\n\nfunction detectFramework(allDeps: Record<string, string>): StackItem | undefined {\n for (const mapping of FRAMEWORK_MAPPINGS) {\n if (!(mapping.dep in allDeps)) continue;\n if (mapping.excludeDep && mapping.excludeDep in allDeps) continue;\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n return undefined;\n}\n\nasync function detectLanguage(\n projectPath: string,\n allDeps: Record<string, string>,\n): Promise<StackItem> {\n if ('typescript' in allDeps) {\n return {\n name: 'typescript',\n version: extractMajorVersion(allDeps.typescript),\n };\n }\n if (await fileExists(join(projectPath, 'tsconfig.json'))) {\n return { name: 'typescript' };\n }\n return { name: 'javascript' };\n}\n\nfunction detectFirst(\n allDeps: Record<string, string>,\n mappings: Array<{ dep: string; name: string }>,\n): StackItem | undefined {\n for (const mapping of mappings) {\n if (mapping.dep in allDeps) {\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n }\n return undefined;\n}\n\nasync function detectPackageManager(projectPath: string): Promise<StackItem> {\n for (const entry of LOCK_FILE_MAP) {\n if (await fileExists(join(projectPath, entry.file))) {\n return { name: entry.name };\n }\n }\n return { name: 'npm' };\n}\n\nfunction detectLinter(allDeps: Record<string, string>): StackItem | undefined {\n if ('eslint' in allDeps) {\n return { name: 'eslint', version: extractMajorVersion(allDeps.eslint) };\n }\n if ('@biomejs/biome' in allDeps) {\n return {\n name: 'biome',\n version: extractMajorVersion(allDeps['@biomejs/biome']),\n };\n }\n return undefined;\n}\n\nfunction detectTestRunner(allDeps: Record<string, string>): StackItem | undefined {\n if ('vitest' in allDeps) {\n return { name: 'vitest', version: extractMajorVersion(allDeps.vitest) };\n }\n if ('jest' in allDeps) {\n return { name: 'jest', version: extractMajorVersion(allDeps.jest) };\n }\n return undefined;\n}\n\nfunction detectLibraries(allDeps: Record<string, string>): StackItem[] {\n const libs: StackItem[] = [];\n for (const mapping of LIBRARY_MAPPINGS) {\n const found = mapping.deps.find((dep) => dep in allDeps);\n if (found) {\n libs.push({\n name: mapping.name,\n version: extractMajorVersion(allDeps[found]),\n });\n }\n }\n return libs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Minimal interface for the fields we read from package.json.\n */\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Safely reads and parses a package.json file from the given directory.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.\n */\nexport async function readPackageJson(projectPath: string): Promise<PackageJson | null> {\n try {\n const raw = await readFile(join(projectPath, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n","import type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyDirectory } from './utils/classify-directory.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects the directory structure and organization of a project.\n *\n * Classifies directories by role, detects whether a src/ directory is in use,\n * and identifies test file patterns.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns The detected directory structure.\n */\nexport async function detectStructure(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<DetectedStructure> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n // Detect srcDir\n const hasSrcDir = dirs.some((d) => d.relativePath === 'src' || d.relativePath.startsWith('src/'));\n const srcDir = hasSrcDir ? 'src' : undefined;\n\n // Classify directories\n const directories = dirs.map((d) => classifyDirectory(d)).filter((d) => d !== null);\n\n // Detect test pattern\n const testPattern = detectTestPattern(dirs);\n\n return {\n ...(srcDir !== undefined && { srcDir }),\n directories,\n ...(testPattern !== undefined && { testPattern }),\n };\n}\n\n/**\n * Detects the dominant test file naming pattern from all source files.\n * Returns undefined if fewer than 3 test files are found.\n */\nfunction detectTestPattern(\n dirs: Array<{ sourceFileNames: string[] }>,\n): DetectedConvention<string> | undefined {\n const allFiles = dirs.flatMap((d) => d.sourceFileNames);\n const testFiles = allFiles.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n\n if (testFiles.length < 3) return undefined;\n\n const dotTestCount = testFiles.filter((f) => f.includes('.test.')).length;\n const dotSpecCount = testFiles.filter((f) => f.includes('.spec.')).length;\n\n const isDotTest = dotTestCount >= dotSpecCount;\n const dominantSep = isDotTest ? '.test.' : '.spec.';\n const dominantCount = isDotTest ? dotTestCount : dotSpecCount;\n const consistency = Math.round((dominantCount / testFiles.length) * 100);\n\n // Find most common extension among dominant test files\n const extCounts = new Map<string, number>();\n for (const f of testFiles.filter((f) => f.includes(dominantSep))) {\n const ext = f.substring(f.lastIndexOf('.'));\n extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n const topExt = [...extCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? '.ts';\n\n const sep = isDotTest ? 'test' : 'spec';\n\n return {\n value: `*.${sep}${topExt}`,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: testFiles.length,\n consistency,\n };\n}\n","import type { Confidence, DirectoryRole } from '@viberails/types';\nimport type { WalkedDirectory } from './walk-directory.js';\n\nexport interface ClassifiedDirectory {\n path: string;\n role: DirectoryRole;\n fileCount: number;\n confidence: Confidence;\n}\n\ninterface RolePattern {\n role: DirectoryRole;\n pathPatterns: string[];\n}\n\nconst ROLE_PATTERNS: RolePattern[] = [\n { role: 'pages', pathPatterns: ['src/app', 'src/pages', 'app', 'pages'] },\n { role: 'components', pathPatterns: ['src/components', 'components'] },\n { role: 'hooks', pathPatterns: ['src/hooks', 'hooks'] },\n {\n role: 'utils',\n pathPatterns: ['src/lib', 'src/utils', 'src/helpers', 'lib', 'utils', 'helpers'],\n },\n { role: 'types', pathPatterns: ['src/types', 'types', 'src/@types', '@types'] },\n { role: 'tests', pathPatterns: ['__tests__', 'tests', 'test', 'src/__tests__', 'src/tests'] },\n { role: 'styles', pathPatterns: ['src/styles', 'styles', 'src/css', 'css'] },\n { role: 'api', pathPatterns: ['src/api', 'api', 'src/app/api', 'app/api'] },\n { role: 'config', pathPatterns: ['config', 'src/config'] },\n];\n\n/**\n * Classifies a directory by its role in the project.\n *\n * @param dir - A walked directory entry.\n * @returns Classified directory info, or null if the directory should be skipped.\n */\nexport function classifyDirectory(dir: WalkedDirectory): ClassifiedDirectory | null {\n // Try name-based matching first\n const nameMatch = matchByName(dir.relativePath);\n if (nameMatch) {\n const confidence: Confidence = dir.sourceFileCount > 0 ? 'high' : 'low';\n return {\n path: dir.relativePath,\n role: nameMatch,\n fileCount: dir.sourceFileCount,\n confidence,\n };\n }\n\n // Skip directories with no source files if no name match\n if (dir.sourceFileCount === 0) return null;\n\n // Content-based heuristics\n const contentRole = inferFromContent(dir);\n if (contentRole) return contentRole;\n\n // Has source files but no classification\n return {\n path: dir.relativePath,\n role: 'unknown',\n fileCount: dir.sourceFileCount,\n confidence: 'low',\n };\n}\n\n/**\n * Matches a relative path against known role patterns.\n * Supports suffix matching so monorepo paths like `apps/web/lib`\n * match patterns like `lib`.\n * Returns the role if matched, or null.\n */\nfunction matchByName(relativePath: string): DirectoryRole | null {\n for (const { role, pathPatterns } of ROLE_PATTERNS) {\n for (const pattern of pathPatterns) {\n if (relativePath === pattern || relativePath.endsWith(`/${pattern}`)) {\n return role;\n }\n }\n }\n return null;\n}\n\n/**\n * Infers directory role from file contents/names.\n */\nfunction inferFromContent(dir: WalkedDirectory): ClassifiedDirectory | null {\n const { sourceFileNames, sourceFileCount } = dir;\n\n // Check for hook files (use-* kebab or useXxx camelCase prefix)\n // Require at least 2 matching files to avoid misclassifying directories\n // with a single hook utility (e.g. lib/ with one useXxx file)\n const hookFiles = sourceFileNames.filter((f) => {\n const name = f.split('.')[0];\n return name.startsWith('use-') || /^use[A-Z]/.test(name);\n });\n if (hookFiles.length >= 2 && hookFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'hooks',\n fileCount: sourceFileCount,\n confidence: hookFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n // Check for test files — require at least 2 to avoid false positives\n const testFiles = sourceFileNames.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n if (testFiles.length >= 2 && testFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'tests',\n fileCount: sourceFileCount,\n confidence: testFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n return null;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { DetectedWorkspace, WorkspacePackage } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Detects workspace configuration for monorepo projects.\n *\n * Checks for `pnpm-workspace.yaml` first, then falls back to\n * `package.json` `workspaces` field. Returns `undefined` for\n * single-package projects.\n *\n * @param projectRoot - Absolute path to the project root.\n * @returns Detected workspace info, or `undefined` if not a monorepo.\n */\nexport async function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined> {\n const patterns = await readWorkspacePatterns(projectRoot);\n if (!patterns || patterns.length === 0) return undefined;\n\n const packageDirs = await resolvePatterns(projectRoot, patterns);\n const packages = await resolvePackages(projectRoot, packageDirs);\n\n if (packages.length === 0) return undefined;\n\n // Resolve internal deps: check if any dependency name matches a workspace package\n const packageNames = new Set(packages.map((p) => p.name));\n for (const pkg of packages) {\n pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));\n }\n\n return { patterns, packages };\n}\n\n/**\n * Reads workspace glob patterns from pnpm-workspace.yaml or package.json.\n */\nasync function readWorkspacePatterns(projectRoot: string): Promise<string[] | undefined> {\n // Try pnpm-workspace.yaml first\n try {\n const yaml = await readFile(join(projectRoot, 'pnpm-workspace.yaml'), 'utf-8');\n return parsePnpmWorkspaceYaml(yaml);\n } catch {\n // Not found, try package.json\n }\n\n // Fall back to package.json workspaces field\n const pkg = await readPackageJson(projectRoot);\n if (!pkg) return undefined;\n\n const raw = pkg as Record<string, unknown>;\n const workspaces = raw.workspaces;\n\n if (Array.isArray(workspaces)) {\n return workspaces.filter((w): w is string => typeof w === 'string');\n }\n\n // Handle { packages: [...] } format\n if (workspaces && typeof workspaces === 'object' && 'packages' in workspaces) {\n const nested = (workspaces as { packages: unknown }).packages;\n if (Array.isArray(nested)) {\n return nested.filter((w): w is string => typeof w === 'string');\n }\n }\n\n return undefined;\n}\n\n/**\n * Parses workspace patterns from pnpm-workspace.yaml content.\n * Handles the common format: `packages:\\n - 'packages/*'`\n */\nfunction parsePnpmWorkspaceYaml(content: string): string[] {\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (trimmed === 'packages:') {\n inPackages = true;\n continue;\n }\n\n // Stop at next top-level key\n if (inPackages && trimmed.length > 0 && !trimmed.startsWith('-')) {\n break;\n }\n\n if (inPackages && trimmed.startsWith('-')) {\n // Extract the pattern, stripping quotes and leading dash\n const value = trimmed\n .slice(1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n if (value) patterns.push(value);\n }\n }\n\n return patterns;\n}\n\n/**\n * Resolves workspace glob patterns to actual directory paths.\n * Supports simple `*` wildcard matching (e.g. `packages/*`).\n */\nasync function resolvePatterns(projectRoot: string, patterns: string[]): Promise<string[]> {\n const dirs: string[] = [];\n\n for (const pattern of patterns) {\n if (pattern.endsWith('/*')) {\n // Simple wildcard: list children of the parent directory\n const parent = join(projectRoot, pattern.slice(0, -2));\n try {\n const entries = await readdir(parent, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n dirs.push(join(parent, entry.name));\n }\n }\n } catch {\n // Parent directory doesn't exist, skip\n }\n } else {\n // Literal path\n dirs.push(join(projectRoot, pattern));\n }\n }\n\n return dirs;\n}\n\n/**\n * Resolves workspace directories to WorkspacePackage objects.\n * Skips directories without a valid package.json.\n */\nasync function resolvePackages(projectRoot: string, dirs: string[]): Promise<WorkspacePackage[]> {\n const packages: WorkspacePackage[] = [];\n\n for (const dir of dirs) {\n const pkg = await readPackageJson(dir);\n if (!pkg?.name) continue;\n\n // Collect all dependency names as potential internal deps\n const allDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n\n packages.push({\n name: pkg.name,\n path: dir,\n relativePath: relative(projectRoot, dir),\n internalDeps: allDeps,\n });\n }\n\n return packages;\n}\n","import { resolve } from 'node:path';\nimport type { PackageScanResult } from '@viberails/types';\nimport { computeStatistics } from './compute-statistics.js';\nimport { detectConventions } from './detect-conventions.js';\nimport { detectStack } from './detect-stack.js';\nimport { detectStructure } from './detect-structure.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Scans a single package directory and returns per-package scan results.\n *\n * Reuses all existing detector functions scoped to the package directory.\n * In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,\n * eslint) as a base layer — package-specific deps overlay on top.\n *\n * @param packagePath - Absolute path to the package directory.\n * @param name - Package name from package.json.\n * @param relativePath - Path relative to workspace root (empty string for single-package).\n * @param rootDeps - Optional root-level dependencies merged as a base layer.\n * @returns Per-package scan result.\n */\nexport async function scanPackage(\n packagePath: string,\n name: string,\n relativePath: string,\n rootDeps?: Record<string, string>,\n): Promise<PackageScanResult> {\n const root = resolve(packagePath);\n const dirs = await walkDirectory(root, 4);\n\n const [stack, structure, statistics] = await Promise.all([\n detectStack(root, rootDeps),\n detectStructure(root, dirs),\n computeStatistics(root, dirs),\n ]);\n\n const conventions = await detectConventions(root, structure, dirs);\n\n return {\n name,\n root,\n relativePath,\n stack,\n structure,\n conventions,\n statistics,\n };\n}\n","import { stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\nimport type { ScanResult } from '@viberails/types';\nimport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nimport { detectWorkspace } from './detect-workspace.js';\nimport { scanPackage } from './scan-package.js';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Options for the scan function.\n */\nexport type ScanOptions = {};\n\n/**\n * Scans a project directory and returns a comprehensive analysis of its\n * stack, structure, conventions, and statistics.\n *\n * For monorepos, each workspace package is scanned independently and\n * results are aggregated. For single-package projects, the root is\n * scanned as a single package.\n *\n * @param projectPath - Absolute or relative path to the project root.\n * @param options - Optional scan configuration.\n * @returns Complete scan result for the project.\n * @throws If the project path does not exist or is not a directory.\n */\nexport async function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult> {\n const root = resolve(projectPath);\n\n // Validate that the path exists and is a directory\n try {\n const st = await stat(root);\n if (!st.isDirectory()) {\n throw new Error(`Project path is not a directory: ${root}`);\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith('Project path is not')) {\n throw err;\n }\n throw new Error(`Project path does not exist: ${root}`);\n }\n\n const workspace = await detectWorkspace(root);\n\n if (workspace && workspace.packages.length > 0) {\n // Read root deps for sharing with workspace packages\n const rootPkg = await readPackageJson(root);\n const rootDeps: Record<string, string> = {\n ...rootPkg?.dependencies,\n ...rootPkg?.devDependencies,\n };\n\n // Scan each workspace package in parallel\n const packages = await Promise.all(\n workspace.packages.map((wp) => scanPackage(wp.path, wp.name, wp.relativePath, rootDeps)),\n );\n\n return {\n root,\n stack: aggregateStacks(packages),\n structure: aggregateStructures(packages),\n conventions: aggregateConventions(packages),\n statistics: aggregateStatistics(packages),\n workspace,\n packages,\n };\n }\n\n // Single-package project\n const rootPkg = await readPackageJson(root);\n const name = rootPkg?.name ?? basename(root);\n const pkg = await scanPackage(root, name, '');\n\n return {\n root,\n stack: pkg.stack,\n structure: pkg.structure,\n conventions: pkg.conventions,\n statistics: pkg.statistics,\n packages: [pkg],\n };\n}\n"],"mappings":"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","names":["import_promises","import_node_path","import_promises","import_node_path","import_types","import_promises","import_node_path","import_promises","import_node_path","import_types","f","import_promises","import_node_path","import_node_path","import_promises","import_node_path","rootPkg"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,32 @@
1
- import { CodebaseStatistics, DetectedStructure, DetectedConvention, DetectedStack, DetectedWorkspace, ScanResult } from '@viberails/types';
1
+ import { PackageScanResult, DetectedConvention, DetectedStack, CodebaseStatistics, DetectedStructure, DetectedWorkspace, ScanResult } from '@viberails/types';
2
+
3
+ /**
4
+ * Combines per-package stacks into a single aggregate stack.
5
+ *
6
+ * TypeScript wins over JavaScript if any package uses it.
7
+ * The highest-priority framework becomes the primary; others go into libraries.
8
+ */
9
+ declare function aggregateStacks(packages: PackageScanResult[]): DetectedStack;
10
+ /**
11
+ * Combines per-package structures into a single aggregate structure.
12
+ *
13
+ * Directory paths are prefixed with the package's relativePath.
14
+ */
15
+ declare function aggregateStructures(packages: PackageScanResult[]): DetectedStructure;
16
+ /**
17
+ * Combines per-package conventions into aggregate conventions.
18
+ *
19
+ * When all packages agree on a convention, reports it with averaged consistency.
20
+ * When packages disagree, scales consistency by agreement ratio.
21
+ * Omits conventions present in fewer than half of packages.
22
+ */
23
+ declare function aggregateConventions(packages: PackageScanResult[]): Record<string, DetectedConvention>;
24
+ /**
25
+ * Combines per-package statistics into aggregate statistics.
26
+ *
27
+ * Sums totals, recomputes averages, merges largest files with path prefixing.
28
+ */
29
+ declare function aggregateStatistics(packages: PackageScanResult[]): CodebaseStatistics;
2
30
 
3
31
  interface WalkedDirectory {
4
32
  /** Path relative to project root, using forward slashes. */
@@ -57,10 +85,15 @@ declare function extractMajorVersion(range: string): string | undefined;
57
85
  * Detects the technology stack of a project by reading its package.json
58
86
  * and checking for lock files and configuration files.
59
87
  *
88
+ * When `additionalDeps` is provided, they are merged as a base layer
89
+ * beneath the package's own deps. This allows monorepo root-level deps
90
+ * (e.g. typescript, eslint) to be visible during per-package scanning.
91
+ *
60
92
  * @param projectPath - Absolute path to the project root directory.
93
+ * @param additionalDeps - Optional base dependencies merged under package deps.
61
94
  * @returns The detected technology stack.
62
95
  */
63
- declare function detectStack(projectPath: string): Promise<DetectedStack>;
96
+ declare function detectStack(projectPath: string, additionalDeps?: Record<string, string>): Promise<DetectedStack>;
64
97
 
65
98
  /**
66
99
  * Detects the directory structure and organization of a project.
@@ -86,6 +119,21 @@ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]):
86
119
  */
87
120
  declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
88
121
 
122
+ /**
123
+ * Scans a single package directory and returns per-package scan results.
124
+ *
125
+ * Reuses all existing detector functions scoped to the package directory.
126
+ * In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
127
+ * eslint) as a base layer — package-specific deps overlay on top.
128
+ *
129
+ * @param packagePath - Absolute path to the package directory.
130
+ * @param name - Package name from package.json.
131
+ * @param relativePath - Path relative to workspace root (empty string for single-package).
132
+ * @param rootDeps - Optional root-level dependencies merged as a base layer.
133
+ * @returns Per-package scan result.
134
+ */
135
+ declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
136
+
89
137
  /**
90
138
  * Options for the scan function.
91
139
  */
@@ -94,8 +142,9 @@ type ScanOptions = {};
94
142
  * Scans a project directory and returns a comprehensive analysis of its
95
143
  * stack, structure, conventions, and statistics.
96
144
  *
97
- * This is the primary entry point for the scanner package. It composes
98
- * all individual detection functions into a unified ScanResult.
145
+ * For monorepos, each workspace package is scanned independently and
146
+ * results are aggregated. For single-package projects, the root is
147
+ * scanned as a single package.
99
148
  *
100
149
  * @param projectPath - Absolute or relative path to the project root.
101
150
  * @param options - Optional scan configuration.
@@ -123,4 +172,4 @@ declare function readPackageJson(projectPath: string): Promise<PackageJson | nul
123
172
 
124
173
  declare const VERSION = "0.1.0";
125
174
 
126
- export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, walkDirectory };
175
+ export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, aggregateConventions, aggregateStacks, aggregateStatistics, aggregateStructures, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, scanPackage, walkDirectory };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,32 @@
1
- import { CodebaseStatistics, DetectedStructure, DetectedConvention, DetectedStack, DetectedWorkspace, ScanResult } from '@viberails/types';
1
+ import { PackageScanResult, DetectedConvention, DetectedStack, CodebaseStatistics, DetectedStructure, DetectedWorkspace, ScanResult } from '@viberails/types';
2
+
3
+ /**
4
+ * Combines per-package stacks into a single aggregate stack.
5
+ *
6
+ * TypeScript wins over JavaScript if any package uses it.
7
+ * The highest-priority framework becomes the primary; others go into libraries.
8
+ */
9
+ declare function aggregateStacks(packages: PackageScanResult[]): DetectedStack;
10
+ /**
11
+ * Combines per-package structures into a single aggregate structure.
12
+ *
13
+ * Directory paths are prefixed with the package's relativePath.
14
+ */
15
+ declare function aggregateStructures(packages: PackageScanResult[]): DetectedStructure;
16
+ /**
17
+ * Combines per-package conventions into aggregate conventions.
18
+ *
19
+ * When all packages agree on a convention, reports it with averaged consistency.
20
+ * When packages disagree, scales consistency by agreement ratio.
21
+ * Omits conventions present in fewer than half of packages.
22
+ */
23
+ declare function aggregateConventions(packages: PackageScanResult[]): Record<string, DetectedConvention>;
24
+ /**
25
+ * Combines per-package statistics into aggregate statistics.
26
+ *
27
+ * Sums totals, recomputes averages, merges largest files with path prefixing.
28
+ */
29
+ declare function aggregateStatistics(packages: PackageScanResult[]): CodebaseStatistics;
2
30
 
3
31
  interface WalkedDirectory {
4
32
  /** Path relative to project root, using forward slashes. */
@@ -57,10 +85,15 @@ declare function extractMajorVersion(range: string): string | undefined;
57
85
  * Detects the technology stack of a project by reading its package.json
58
86
  * and checking for lock files and configuration files.
59
87
  *
88
+ * When `additionalDeps` is provided, they are merged as a base layer
89
+ * beneath the package's own deps. This allows monorepo root-level deps
90
+ * (e.g. typescript, eslint) to be visible during per-package scanning.
91
+ *
60
92
  * @param projectPath - Absolute path to the project root directory.
93
+ * @param additionalDeps - Optional base dependencies merged under package deps.
61
94
  * @returns The detected technology stack.
62
95
  */
63
- declare function detectStack(projectPath: string): Promise<DetectedStack>;
96
+ declare function detectStack(projectPath: string, additionalDeps?: Record<string, string>): Promise<DetectedStack>;
64
97
 
65
98
  /**
66
99
  * Detects the directory structure and organization of a project.
@@ -86,6 +119,21 @@ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]):
86
119
  */
87
120
  declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
88
121
 
122
+ /**
123
+ * Scans a single package directory and returns per-package scan results.
124
+ *
125
+ * Reuses all existing detector functions scoped to the package directory.
126
+ * In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
127
+ * eslint) as a base layer — package-specific deps overlay on top.
128
+ *
129
+ * @param packagePath - Absolute path to the package directory.
130
+ * @param name - Package name from package.json.
131
+ * @param relativePath - Path relative to workspace root (empty string for single-package).
132
+ * @param rootDeps - Optional root-level dependencies merged as a base layer.
133
+ * @returns Per-package scan result.
134
+ */
135
+ declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
136
+
89
137
  /**
90
138
  * Options for the scan function.
91
139
  */
@@ -94,8 +142,9 @@ type ScanOptions = {};
94
142
  * Scans a project directory and returns a comprehensive analysis of its
95
143
  * stack, structure, conventions, and statistics.
96
144
  *
97
- * This is the primary entry point for the scanner package. It composes
98
- * all individual detection functions into a unified ScanResult.
145
+ * For monorepos, each workspace package is scanned independently and
146
+ * results are aggregated. For single-package projects, the root is
147
+ * scanned as a single package.
99
148
  *
100
149
  * @param projectPath - Absolute or relative path to the project root.
101
150
  * @param options - Optional scan configuration.
@@ -123,4 +172,4 @@ declare function readPackageJson(projectPath: string): Promise<PackageJson | nul
123
172
 
124
173
  declare const VERSION = "0.1.0";
125
174
 
126
- export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, walkDirectory };
175
+ export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, aggregateConventions, aggregateStacks, aggregateStatistics, aggregateStructures, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, scanPackage, walkDirectory };