@viberails/scanner 0.1.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.
@@ -0,0 +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"]}
@@ -0,0 +1,126 @@
1
+ import { CodebaseStatistics, DetectedStructure, DetectedConvention, DetectedStack, DetectedWorkspace, ScanResult } from '@viberails/types';
2
+
3
+ interface WalkedDirectory {
4
+ /** Path relative to project root, using forward slashes. */
5
+ relativePath: string;
6
+ /** Absolute path. */
7
+ absolutePath: string;
8
+ /** Number of source files directly in this directory. */
9
+ sourceFileCount: number;
10
+ /** Names of source files in this directory. */
11
+ sourceFileNames: string[];
12
+ /** Depth relative to the project root (1 = direct children). */
13
+ depth: number;
14
+ }
15
+ /**
16
+ * Walks a project directory tree using BFS, collecting directory info.
17
+ *
18
+ * @param projectPath - Absolute path to the project root.
19
+ * @param maxDepth - Maximum directory depth to traverse (default 4).
20
+ * @returns Flat list of all visited directories (excluding root itself).
21
+ */
22
+ declare function walkDirectory(projectPath: string, maxDepth?: number): Promise<WalkedDirectory[]>;
23
+
24
+ /**
25
+ * Computes quantitative statistics about a project's source files.
26
+ *
27
+ * Reads each source file and produces aggregate metrics including
28
+ * file counts, line counts, and extension breakdown.
29
+ *
30
+ * @param projectPath - Absolute path to the project root.
31
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
32
+ * @returns Statistics about the codebase.
33
+ */
34
+ declare function computeStatistics(projectPath: string, dirs?: WalkedDirectory[]): Promise<CodebaseStatistics>;
35
+
36
+ /**
37
+ * Detects coding conventions used in a project by analyzing file names
38
+ * and configuration files.
39
+ *
40
+ * @param projectPath - Absolute path to the project root directory.
41
+ * @param structure - Previously detected directory structure, used to identify
42
+ * directories by role.
43
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
44
+ * @returns A record of detected conventions keyed by convention name.
45
+ * Only statistical conventions with sampleSize >= 3 are included.
46
+ */
47
+ declare function detectConventions(projectPath: string, structure: DetectedStructure, dirs?: WalkedDirectory[]): Promise<Record<string, DetectedConvention>>;
48
+
49
+ /**
50
+ * Extracts the major version number from a semver range string.
51
+ *
52
+ * @param range - A semver range such as `"^15.0.3"`, `"~2.1.0"`, or `"3.x"`.
53
+ * @returns The major version string (e.g. `"15"`), or `undefined` if extraction fails.
54
+ */
55
+ declare function extractMajorVersion(range: string): string | undefined;
56
+ /**
57
+ * Detects the technology stack of a project by reading its package.json
58
+ * and checking for lock files and configuration files.
59
+ *
60
+ * @param projectPath - Absolute path to the project root directory.
61
+ * @returns The detected technology stack.
62
+ */
63
+ declare function detectStack(projectPath: string): Promise<DetectedStack>;
64
+
65
+ /**
66
+ * Detects the directory structure and organization of a project.
67
+ *
68
+ * Classifies directories by role, detects whether a src/ directory is in use,
69
+ * and identifies test file patterns.
70
+ *
71
+ * @param projectPath - Absolute path to the project root directory.
72
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
73
+ * @returns The detected directory structure.
74
+ */
75
+ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]): Promise<DetectedStructure>;
76
+
77
+ /**
78
+ * Detects workspace configuration for monorepo projects.
79
+ *
80
+ * Checks for `pnpm-workspace.yaml` first, then falls back to
81
+ * `package.json` `workspaces` field. Returns `undefined` for
82
+ * single-package projects.
83
+ *
84
+ * @param projectRoot - Absolute path to the project root.
85
+ * @returns Detected workspace info, or `undefined` if not a monorepo.
86
+ */
87
+ declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
88
+
89
+ /**
90
+ * Options for the scan function.
91
+ */
92
+ type ScanOptions = {};
93
+ /**
94
+ * Scans a project directory and returns a comprehensive analysis of its
95
+ * stack, structure, conventions, and statistics.
96
+ *
97
+ * This is the primary entry point for the scanner package. It composes
98
+ * all individual detection functions into a unified ScanResult.
99
+ *
100
+ * @param projectPath - Absolute or relative path to the project root.
101
+ * @param options - Optional scan configuration.
102
+ * @returns Complete scan result for the project.
103
+ * @throws If the project path does not exist or is not a directory.
104
+ */
105
+ declare function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult>;
106
+
107
+ /**
108
+ * Minimal interface for the fields we read from package.json.
109
+ */
110
+ interface PackageJson {
111
+ name?: string;
112
+ version?: string;
113
+ dependencies?: Record<string, string>;
114
+ devDependencies?: Record<string, string>;
115
+ }
116
+ /**
117
+ * Safely reads and parses a package.json file from the given directory.
118
+ *
119
+ * @param projectPath - Absolute path to the project root directory.
120
+ * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.
121
+ */
122
+ declare function readPackageJson(projectPath: string): Promise<PackageJson | null>;
123
+
124
+ declare const VERSION = "0.1.0";
125
+
126
+ export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, walkDirectory };
@@ -0,0 +1,126 @@
1
+ import { CodebaseStatistics, DetectedStructure, DetectedConvention, DetectedStack, DetectedWorkspace, ScanResult } from '@viberails/types';
2
+
3
+ interface WalkedDirectory {
4
+ /** Path relative to project root, using forward slashes. */
5
+ relativePath: string;
6
+ /** Absolute path. */
7
+ absolutePath: string;
8
+ /** Number of source files directly in this directory. */
9
+ sourceFileCount: number;
10
+ /** Names of source files in this directory. */
11
+ sourceFileNames: string[];
12
+ /** Depth relative to the project root (1 = direct children). */
13
+ depth: number;
14
+ }
15
+ /**
16
+ * Walks a project directory tree using BFS, collecting directory info.
17
+ *
18
+ * @param projectPath - Absolute path to the project root.
19
+ * @param maxDepth - Maximum directory depth to traverse (default 4).
20
+ * @returns Flat list of all visited directories (excluding root itself).
21
+ */
22
+ declare function walkDirectory(projectPath: string, maxDepth?: number): Promise<WalkedDirectory[]>;
23
+
24
+ /**
25
+ * Computes quantitative statistics about a project's source files.
26
+ *
27
+ * Reads each source file and produces aggregate metrics including
28
+ * file counts, line counts, and extension breakdown.
29
+ *
30
+ * @param projectPath - Absolute path to the project root.
31
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
32
+ * @returns Statistics about the codebase.
33
+ */
34
+ declare function computeStatistics(projectPath: string, dirs?: WalkedDirectory[]): Promise<CodebaseStatistics>;
35
+
36
+ /**
37
+ * Detects coding conventions used in a project by analyzing file names
38
+ * and configuration files.
39
+ *
40
+ * @param projectPath - Absolute path to the project root directory.
41
+ * @param structure - Previously detected directory structure, used to identify
42
+ * directories by role.
43
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
44
+ * @returns A record of detected conventions keyed by convention name.
45
+ * Only statistical conventions with sampleSize >= 3 are included.
46
+ */
47
+ declare function detectConventions(projectPath: string, structure: DetectedStructure, dirs?: WalkedDirectory[]): Promise<Record<string, DetectedConvention>>;
48
+
49
+ /**
50
+ * Extracts the major version number from a semver range string.
51
+ *
52
+ * @param range - A semver range such as `"^15.0.3"`, `"~2.1.0"`, or `"3.x"`.
53
+ * @returns The major version string (e.g. `"15"`), or `undefined` if extraction fails.
54
+ */
55
+ declare function extractMajorVersion(range: string): string | undefined;
56
+ /**
57
+ * Detects the technology stack of a project by reading its package.json
58
+ * and checking for lock files and configuration files.
59
+ *
60
+ * @param projectPath - Absolute path to the project root directory.
61
+ * @returns The detected technology stack.
62
+ */
63
+ declare function detectStack(projectPath: string): Promise<DetectedStack>;
64
+
65
+ /**
66
+ * Detects the directory structure and organization of a project.
67
+ *
68
+ * Classifies directories by role, detects whether a src/ directory is in use,
69
+ * and identifies test file patterns.
70
+ *
71
+ * @param projectPath - Absolute path to the project root directory.
72
+ * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.
73
+ * @returns The detected directory structure.
74
+ */
75
+ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]): Promise<DetectedStructure>;
76
+
77
+ /**
78
+ * Detects workspace configuration for monorepo projects.
79
+ *
80
+ * Checks for `pnpm-workspace.yaml` first, then falls back to
81
+ * `package.json` `workspaces` field. Returns `undefined` for
82
+ * single-package projects.
83
+ *
84
+ * @param projectRoot - Absolute path to the project root.
85
+ * @returns Detected workspace info, or `undefined` if not a monorepo.
86
+ */
87
+ declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
88
+
89
+ /**
90
+ * Options for the scan function.
91
+ */
92
+ type ScanOptions = {};
93
+ /**
94
+ * Scans a project directory and returns a comprehensive analysis of its
95
+ * stack, structure, conventions, and statistics.
96
+ *
97
+ * This is the primary entry point for the scanner package. It composes
98
+ * all individual detection functions into a unified ScanResult.
99
+ *
100
+ * @param projectPath - Absolute or relative path to the project root.
101
+ * @param options - Optional scan configuration.
102
+ * @returns Complete scan result for the project.
103
+ * @throws If the project path does not exist or is not a directory.
104
+ */
105
+ declare function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult>;
106
+
107
+ /**
108
+ * Minimal interface for the fields we read from package.json.
109
+ */
110
+ interface PackageJson {
111
+ name?: string;
112
+ version?: string;
113
+ dependencies?: Record<string, string>;
114
+ devDependencies?: Record<string, string>;
115
+ }
116
+ /**
117
+ * Safely reads and parses a package.json file from the given directory.
118
+ *
119
+ * @param projectPath - Absolute path to the project root directory.
120
+ * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.
121
+ */
122
+ declare function readPackageJson(projectPath: string): Promise<PackageJson | null>;
123
+
124
+ declare const VERSION = "0.1.0";
125
+
126
+ export { type PackageJson, type ScanOptions, VERSION, type WalkedDirectory, computeStatistics, detectConventions, detectStack, detectStructure, detectWorkspace, extractMajorVersion, readPackageJson, scan, walkDirectory };