@viberails/scanner 0.2.1 → 0.2.3

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.
package/dist/index.cjs CHANGED
@@ -52,14 +52,15 @@ var FRAMEWORK_PRIORITY = [
52
52
  ];
53
53
  function aggregateStacks(packages) {
54
54
  if (packages.length === 1) return packages[0].stack;
55
- const language = packages.some((p) => p.stack.language.name === "typescript") ? packages.find((p) => p.stack.language.name === "typescript").stack.language : packages[0].stack.language;
55
+ const tsPackage = packages.find((p) => p.stack.language.name === "typescript");
56
+ const language = tsPackage ? tsPackage.stack.language : packages[0].stack.language;
56
57
  const packageManager = packages[0].stack.packageManager;
57
58
  const frameworkPackages = packages.filter((p) => p.stack.framework);
58
59
  let framework;
59
60
  if (frameworkPackages.length > 0) {
60
61
  frameworkPackages.sort((a, b) => {
61
- const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework.name);
62
- const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework.name);
62
+ const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework?.name ?? "");
63
+ const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework?.name ?? "");
63
64
  return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
64
65
  });
65
66
  framework = frameworkPackages[0].stack.framework;
@@ -489,22 +490,24 @@ function detectHookNaming(dirs, structure) {
489
490
  };
490
491
  }
491
492
  async function detectImportAlias(projectPath) {
492
- try {
493
- const raw = await (0, import_promises3.readFile)((0, import_node_path3.join)(projectPath, "tsconfig.json"), "utf-8");
494
- const tsconfig = JSON.parse(raw);
495
- const paths = tsconfig.compilerOptions?.paths;
496
- if (!paths) return void 0;
497
- const aliases = Object.keys(paths);
498
- if (aliases.length === 0) return void 0;
499
- return {
500
- value: aliases.join(","),
501
- confidence: "high",
502
- sampleSize: aliases.length,
503
- consistency: 100
504
- };
505
- } catch {
506
- return void 0;
493
+ for (const configFile of ["tsconfig.json", "jsconfig.json"]) {
494
+ try {
495
+ const raw = await (0, import_promises3.readFile)((0, import_node_path3.join)(projectPath, configFile), "utf-8");
496
+ const config = JSON.parse(raw);
497
+ const paths = config.compilerOptions?.paths;
498
+ if (!paths) continue;
499
+ const aliases = Object.keys(paths);
500
+ if (aliases.length === 0) continue;
501
+ return {
502
+ value: aliases.join(","),
503
+ confidence: "high",
504
+ sampleSize: aliases.length,
505
+ consistency: 100
506
+ };
507
+ } catch {
508
+ }
507
509
  }
510
+ return void 0;
508
511
  }
509
512
 
510
513
  // src/detect-stack.ts
@@ -540,15 +543,21 @@ var FRAMEWORK_MAPPINGS = [
540
543
  { dep: "next", name: "nextjs" },
541
544
  { dep: "expo", name: "expo" },
542
545
  { dep: "react-native", name: "react-native", excludeDep: "expo" },
546
+ { dep: "@angular/core", name: "angular" },
543
547
  { dep: "@sveltejs/kit", name: "sveltekit" },
544
548
  { dep: "svelte", name: "svelte" },
545
549
  { dep: "astro", name: "astro" },
546
- { dep: "vue", name: "vue" },
550
+ { dep: "@remix-run/react", name: "remix" },
551
+ { dep: "nuxt", name: "nuxt" },
552
+ { dep: "vue", name: "vue", excludeDep: "nuxt" },
553
+ { dep: "gatsby", name: "gatsby" },
547
554
  { dep: "react", name: "react", excludeDep: "next" }
548
555
  ];
549
556
  var BACKEND_MAPPINGS = [
557
+ { dep: "@nestjs/core", name: "nestjs" },
550
558
  { dep: "express", name: "express" },
551
559
  { dep: "fastify", name: "fastify" },
560
+ { dep: "koa", name: "koa" },
552
561
  { dep: "hono", name: "hono" },
553
562
  { dep: "@supabase/supabase-js", name: "supabase" },
554
563
  { dep: "firebase", name: "firebase" },
@@ -674,6 +683,15 @@ function detectTestRunner(allDeps) {
674
683
  if ("jest" in allDeps) {
675
684
  return { name: "jest", version: extractMajorVersion(allDeps.jest) };
676
685
  }
686
+ if ("@playwright/test" in allDeps) {
687
+ return { name: "playwright", version: extractMajorVersion(allDeps["@playwright/test"]) };
688
+ }
689
+ if ("cypress" in allDeps) {
690
+ return { name: "cypress", version: extractMajorVersion(allDeps.cypress) };
691
+ }
692
+ if ("mocha" in allDeps) {
693
+ return { name: "mocha", version: extractMajorVersion(allDeps.mocha) };
694
+ }
677
695
  return void 0;
678
696
  }
679
697
  function detectLibraries(allDeps) {
@@ -973,7 +991,7 @@ async function scan(projectPath, _options) {
973
991
  }
974
992
 
975
993
  // src/index.ts
976
- var VERSION = "0.1.0";
994
+ var VERSION = "0.2.3";
977
995
  // Annotate the CommonJS export names for ESM import in node:
978
996
  0 && (module.exports = {
979
997
  VERSION,
@@ -1 +1 @@
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.ts","../src/scan-package.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 type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport { scanPackage } from './scan-package.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 type CodebaseStatistics,\n confidenceFromConsistency,\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 formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;\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 formatter,\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 '.expo',\n 'android',\n 'ios',\n 'Pods',\n '.gradle',\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 item = queue.shift();\n if (!item) break;\n const { absolutePath, depth } = item;\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 formatter = detectFormatter(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 ...(formatter && { formatter }),\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 detectFormatter(allDeps: Record<string, string>): StackItem | undefined {\n if ('prettier' in allDeps) {\n return { name: 'prettier', version: extractMajorVersion(allDeps.prettier) };\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 { 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 = Record<string, never>;\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","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"],"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_promises","import_node_path","import_node_path","rootPkg"]}
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.ts","../src/scan-package.ts"],"sourcesContent":["declare const __PACKAGE_VERSION__: string;\nexport const VERSION: string = __PACKAGE_VERSION__;\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 type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport { scanPackage } from './scan-package.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 type CodebaseStatistics,\n confidenceFromConsistency,\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 tsPackage = packages.find((p) => p.stack.language.name === 'typescript');\n const language = tsPackage ? tsPackage.stack.language : 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 formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;\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 formatter,\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 '.expo',\n 'android',\n 'ios',\n 'Pods',\n '.gradle',\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 item = queue.shift();\n if (!item) break;\n const { absolutePath, depth } = item;\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 or jsconfig.json paths configuration.\n * Checks tsconfig.json first, then falls back to jsconfig.json for JavaScript projects.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n for (const configFile of ['tsconfig.json', 'jsconfig.json']) {\n try {\n const raw = await readFile(join(projectPath, configFile), 'utf-8');\n const config = JSON.parse(raw) as TsConfigSubset;\n const paths = config.compilerOptions?.paths;\n if (!paths) continue;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) continue;\n\n return {\n value: aliases.join(','),\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {}\n }\n return undefined;\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: '@angular/core', name: 'angular' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: '@remix-run/react', name: 'remix' },\n { dep: 'nuxt', name: 'nuxt' },\n { dep: 'vue', name: 'vue', excludeDep: 'nuxt' },\n { dep: 'gatsby', name: 'gatsby' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: '@nestjs/core', name: 'nestjs' },\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'koa', name: 'koa' },\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 formatter = detectFormatter(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 ...(formatter && { formatter }),\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 detectFormatter(allDeps: Record<string, string>): StackItem | undefined {\n if ('prettier' in allDeps) {\n return { name: 'prettier', version: extractMajorVersion(allDeps.prettier) };\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 if ('@playwright/test' in allDeps) {\n return { name: 'playwright', version: extractMajorVersion(allDeps['@playwright/test']) };\n }\n if ('cypress' in allDeps) {\n return { name: 'cypress', version: extractMajorVersion(allDeps.cypress) };\n }\n if ('mocha' in allDeps) {\n return { name: 'mocha', version: extractMajorVersion(allDeps.mocha) };\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 { 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 = Record<string, never>;\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","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"],"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_promises","import_node_path","import_node_path","rootPkg"]}
package/dist/index.d.cts CHANGED
@@ -170,6 +170,6 @@ interface PackageJson {
170
170
  */
171
171
  declare function readPackageJson(projectPath: string): Promise<PackageJson | null>;
172
172
 
173
- declare const VERSION = "0.1.0";
173
+ declare const VERSION: string;
174
174
 
175
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
@@ -170,6 +170,6 @@ interface PackageJson {
170
170
  */
171
171
  declare function readPackageJson(projectPath: string): Promise<PackageJson | null>;
172
172
 
173
- declare const VERSION = "0.1.0";
173
+ declare const VERSION: string;
174
174
 
175
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.js CHANGED
@@ -14,14 +14,15 @@ var FRAMEWORK_PRIORITY = [
14
14
  ];
15
15
  function aggregateStacks(packages) {
16
16
  if (packages.length === 1) return packages[0].stack;
17
- const language = packages.some((p) => p.stack.language.name === "typescript") ? packages.find((p) => p.stack.language.name === "typescript").stack.language : packages[0].stack.language;
17
+ const tsPackage = packages.find((p) => p.stack.language.name === "typescript");
18
+ const language = tsPackage ? tsPackage.stack.language : packages[0].stack.language;
18
19
  const packageManager = packages[0].stack.packageManager;
19
20
  const frameworkPackages = packages.filter((p) => p.stack.framework);
20
21
  let framework;
21
22
  if (frameworkPackages.length > 0) {
22
23
  frameworkPackages.sort((a, b) => {
23
- const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework.name);
24
- const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework.name);
24
+ const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework?.name ?? "");
25
+ const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework?.name ?? "");
25
26
  return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
26
27
  });
27
28
  framework = frameworkPackages[0].stack.framework;
@@ -451,22 +452,24 @@ function detectHookNaming(dirs, structure) {
451
452
  };
452
453
  }
453
454
  async function detectImportAlias(projectPath) {
454
- try {
455
- const raw = await readFile2(join3(projectPath, "tsconfig.json"), "utf-8");
456
- const tsconfig = JSON.parse(raw);
457
- const paths = tsconfig.compilerOptions?.paths;
458
- if (!paths) return void 0;
459
- const aliases = Object.keys(paths);
460
- if (aliases.length === 0) return void 0;
461
- return {
462
- value: aliases.join(","),
463
- confidence: "high",
464
- sampleSize: aliases.length,
465
- consistency: 100
466
- };
467
- } catch {
468
- return void 0;
455
+ for (const configFile of ["tsconfig.json", "jsconfig.json"]) {
456
+ try {
457
+ const raw = await readFile2(join3(projectPath, configFile), "utf-8");
458
+ const config = JSON.parse(raw);
459
+ const paths = config.compilerOptions?.paths;
460
+ if (!paths) continue;
461
+ const aliases = Object.keys(paths);
462
+ if (aliases.length === 0) continue;
463
+ return {
464
+ value: aliases.join(","),
465
+ confidence: "high",
466
+ sampleSize: aliases.length,
467
+ consistency: 100
468
+ };
469
+ } catch {
470
+ }
469
471
  }
472
+ return void 0;
470
473
  }
471
474
 
472
475
  // src/detect-stack.ts
@@ -502,15 +505,21 @@ var FRAMEWORK_MAPPINGS = [
502
505
  { dep: "next", name: "nextjs" },
503
506
  { dep: "expo", name: "expo" },
504
507
  { dep: "react-native", name: "react-native", excludeDep: "expo" },
508
+ { dep: "@angular/core", name: "angular" },
505
509
  { dep: "@sveltejs/kit", name: "sveltekit" },
506
510
  { dep: "svelte", name: "svelte" },
507
511
  { dep: "astro", name: "astro" },
508
- { dep: "vue", name: "vue" },
512
+ { dep: "@remix-run/react", name: "remix" },
513
+ { dep: "nuxt", name: "nuxt" },
514
+ { dep: "vue", name: "vue", excludeDep: "nuxt" },
515
+ { dep: "gatsby", name: "gatsby" },
509
516
  { dep: "react", name: "react", excludeDep: "next" }
510
517
  ];
511
518
  var BACKEND_MAPPINGS = [
519
+ { dep: "@nestjs/core", name: "nestjs" },
512
520
  { dep: "express", name: "express" },
513
521
  { dep: "fastify", name: "fastify" },
522
+ { dep: "koa", name: "koa" },
514
523
  { dep: "hono", name: "hono" },
515
524
  { dep: "@supabase/supabase-js", name: "supabase" },
516
525
  { dep: "firebase", name: "firebase" },
@@ -636,6 +645,15 @@ function detectTestRunner(allDeps) {
636
645
  if ("jest" in allDeps) {
637
646
  return { name: "jest", version: extractMajorVersion(allDeps.jest) };
638
647
  }
648
+ if ("@playwright/test" in allDeps) {
649
+ return { name: "playwright", version: extractMajorVersion(allDeps["@playwright/test"]) };
650
+ }
651
+ if ("cypress" in allDeps) {
652
+ return { name: "cypress", version: extractMajorVersion(allDeps.cypress) };
653
+ }
654
+ if ("mocha" in allDeps) {
655
+ return { name: "mocha", version: extractMajorVersion(allDeps.mocha) };
656
+ }
639
657
  return void 0;
640
658
  }
641
659
  function detectLibraries(allDeps) {
@@ -935,7 +953,7 @@ async function scan(projectPath, _options) {
935
953
  }
936
954
 
937
955
  // src/index.ts
938
- var VERSION = "0.1.0";
956
+ var VERSION = "0.2.3";
939
957
  export {
940
958
  VERSION,
941
959
  aggregateConventions,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../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.ts","../src/scan-package.ts","../src/index.ts"],"sourcesContent":["import {\n type CodebaseStatistics,\n confidenceFromConsistency,\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 formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;\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 formatter,\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 '.expo',\n 'android',\n 'ios',\n 'Pods',\n '.gradle',\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 item = queue.shift();\n if (!item) break;\n const { absolutePath, depth } = item;\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 formatter = detectFormatter(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 ...(formatter && { formatter }),\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 detectFormatter(allDeps: Record<string, string>): StackItem | undefined {\n if ('prettier' in allDeps) {\n return { name: 'prettier', version: extractMajorVersion(allDeps.prettier) };\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 { 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 = Record<string, never>;\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","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","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 type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport { scanPackage } from './scan-package.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"],"mappings":"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","names":["readdir","join","readdir","join","readFile","join","confidenceFromConsistency","confidenceFromConsistency","readFile","join","join","readFile","join","join","confidenceFromConsistency","f","confidenceFromConsistency","readdir","readFile","join","relative","readFile","join","readdir","relative","resolve","resolve","rootPkg"]}
1
+ {"version":3,"sources":["../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.ts","../src/scan-package.ts","../src/index.ts"],"sourcesContent":["import {\n type CodebaseStatistics,\n confidenceFromConsistency,\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 tsPackage = packages.find((p) => p.stack.language.name === 'typescript');\n const language = tsPackage ? tsPackage.stack.language : 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 formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;\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 formatter,\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 '.expo',\n 'android',\n 'ios',\n 'Pods',\n '.gradle',\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 item = queue.shift();\n if (!item) break;\n const { absolutePath, depth } = item;\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 or jsconfig.json paths configuration.\n * Checks tsconfig.json first, then falls back to jsconfig.json for JavaScript projects.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n for (const configFile of ['tsconfig.json', 'jsconfig.json']) {\n try {\n const raw = await readFile(join(projectPath, configFile), 'utf-8');\n const config = JSON.parse(raw) as TsConfigSubset;\n const paths = config.compilerOptions?.paths;\n if (!paths) continue;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) continue;\n\n return {\n value: aliases.join(','),\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {}\n }\n return undefined;\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: '@angular/core', name: 'angular' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: '@remix-run/react', name: 'remix' },\n { dep: 'nuxt', name: 'nuxt' },\n { dep: 'vue', name: 'vue', excludeDep: 'nuxt' },\n { dep: 'gatsby', name: 'gatsby' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: '@nestjs/core', name: 'nestjs' },\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'koa', name: 'koa' },\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 formatter = detectFormatter(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 ...(formatter && { formatter }),\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 detectFormatter(allDeps: Record<string, string>): StackItem | undefined {\n if ('prettier' in allDeps) {\n return { name: 'prettier', version: extractMajorVersion(allDeps.prettier) };\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 if ('@playwright/test' in allDeps) {\n return { name: 'playwright', version: extractMajorVersion(allDeps['@playwright/test']) };\n }\n if ('cypress' in allDeps) {\n return { name: 'cypress', version: extractMajorVersion(allDeps.cypress) };\n }\n if ('mocha' in allDeps) {\n return { name: 'mocha', version: extractMajorVersion(allDeps.mocha) };\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 { 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 = Record<string, never>;\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","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","declare const __PACKAGE_VERSION__: string;\nexport const VERSION: string = __PACKAGE_VERSION__;\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 type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport { scanPackage } from './scan-package.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"],"mappings":"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","names":["readdir","join","readdir","join","readFile","join","confidenceFromConsistency","confidenceFromConsistency","readFile","join","join","readFile","join","join","confidenceFromConsistency","f","confidenceFromConsistency","readdir","readFile","join","relative","readFile","join","readdir","relative","resolve","resolve","rootPkg"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viberails/scanner",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Project scanning for viberails",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -25,7 +25,7 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@viberails/types": "0.2.1"
28
+ "@viberails/types": "0.2.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^25.3.5"