@viberails/scanner 0.2.0 → 0.2.1
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 +29 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +29 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -78,6 +78,7 @@ function aggregateStacks(packages) {
|
|
|
78
78
|
const styling = packages.find((p) => p.stack.styling)?.stack.styling;
|
|
79
79
|
const backend = packages.find((p) => p.stack.backend)?.stack.backend;
|
|
80
80
|
const linter = packages.find((p) => p.stack.linter)?.stack.linter;
|
|
81
|
+
const formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;
|
|
81
82
|
const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;
|
|
82
83
|
return {
|
|
83
84
|
language,
|
|
@@ -87,6 +88,7 @@ function aggregateStacks(packages) {
|
|
|
87
88
|
styling,
|
|
88
89
|
backend,
|
|
89
90
|
linter,
|
|
91
|
+
formatter,
|
|
90
92
|
testRunner
|
|
91
93
|
};
|
|
92
94
|
}
|
|
@@ -204,7 +206,12 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
204
206
|
"coverage",
|
|
205
207
|
".turbo",
|
|
206
208
|
".cache",
|
|
207
|
-
".output"
|
|
209
|
+
".output",
|
|
210
|
+
".expo",
|
|
211
|
+
"android",
|
|
212
|
+
"ios",
|
|
213
|
+
"Pods",
|
|
214
|
+
".gradle"
|
|
208
215
|
]);
|
|
209
216
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
210
217
|
".ts",
|
|
@@ -231,7 +238,9 @@ async function walkDirectory(projectPath, maxDepth = 4) {
|
|
|
231
238
|
return results;
|
|
232
239
|
}
|
|
233
240
|
while (queue.length > 0) {
|
|
234
|
-
const
|
|
241
|
+
const item = queue.shift();
|
|
242
|
+
if (!item) break;
|
|
243
|
+
const { absolutePath, depth } = item;
|
|
235
244
|
const sourceFileNames = [];
|
|
236
245
|
try {
|
|
237
246
|
const entries = await (0, import_promises.readdir)(absolutePath, { withFileTypes: true });
|
|
@@ -577,6 +586,7 @@ async function detectStack(projectPath, additionalDeps) {
|
|
|
577
586
|
const backend = detectFirst(allDeps, BACKEND_MAPPINGS);
|
|
578
587
|
const packageManager = await detectPackageManager(projectPath);
|
|
579
588
|
const linter = detectLinter(allDeps);
|
|
589
|
+
const formatter = detectFormatter(allDeps);
|
|
580
590
|
const testRunner = detectTestRunner(allDeps);
|
|
581
591
|
const libraries = detectLibraries(allDeps);
|
|
582
592
|
return {
|
|
@@ -586,6 +596,7 @@ async function detectStack(projectPath, additionalDeps) {
|
|
|
586
596
|
...backend && { backend },
|
|
587
597
|
packageManager,
|
|
588
598
|
...linter && { linter },
|
|
599
|
+
...formatter && { formatter },
|
|
589
600
|
...testRunner && { testRunner },
|
|
590
601
|
libraries
|
|
591
602
|
};
|
|
@@ -644,6 +655,18 @@ function detectLinter(allDeps) {
|
|
|
644
655
|
}
|
|
645
656
|
return void 0;
|
|
646
657
|
}
|
|
658
|
+
function detectFormatter(allDeps) {
|
|
659
|
+
if ("prettier" in allDeps) {
|
|
660
|
+
return { name: "prettier", version: extractMajorVersion(allDeps.prettier) };
|
|
661
|
+
}
|
|
662
|
+
if ("@biomejs/biome" in allDeps) {
|
|
663
|
+
return {
|
|
664
|
+
name: "biome",
|
|
665
|
+
version: extractMajorVersion(allDeps["@biomejs/biome"])
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
647
670
|
function detectTestRunner(allDeps) {
|
|
648
671
|
if ("vitest" in allDeps) {
|
|
649
672
|
return { name: "vitest", version: extractMajorVersion(allDeps.vitest) };
|
|
@@ -876,6 +899,10 @@ async function resolvePackages(projectRoot, dirs) {
|
|
|
876
899
|
return packages;
|
|
877
900
|
}
|
|
878
901
|
|
|
902
|
+
// src/scan.ts
|
|
903
|
+
var import_promises7 = require("fs/promises");
|
|
904
|
+
var import_node_path8 = require("path");
|
|
905
|
+
|
|
879
906
|
// src/scan-package.ts
|
|
880
907
|
var import_node_path7 = require("path");
|
|
881
908
|
async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
@@ -899,8 +926,6 @@ async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
|
899
926
|
}
|
|
900
927
|
|
|
901
928
|
// src/scan.ts
|
|
902
|
-
var import_promises7 = require("fs/promises");
|
|
903
|
-
var import_node_path8 = require("path");
|
|
904
929
|
async function scan(projectPath, _options) {
|
|
905
930
|
const root = (0, import_node_path8.resolve)(projectPath);
|
|
906
931
|
try {
|
package/dist/index.cjs.map
CHANGED
|
@@ -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-package.ts","../src/scan.ts"],"sourcesContent":["export const VERSION = '0.1.0';\n\nexport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nexport { computeStatistics } from './compute-statistics.js';\nexport { detectConventions } from './detect-conventions.js';\nexport { detectStack, extractMajorVersion } from './detect-stack.js';\nexport { detectStructure } from './detect-structure.js';\nexport { detectWorkspace } from './detect-workspace.js';\nexport { scanPackage } from './scan-package.js';\nexport type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport type { PackageJson } from './utils/read-package-json.js';\nexport { readPackageJson } from './utils/read-package-json.js';\nexport type { WalkedDirectory } from './utils/walk-directory.js';\nexport { walkDirectory } from './utils/walk-directory.js';\n","import {\n confidenceFromConsistency,\n type CodebaseStatistics,\n type DetectedConvention,\n type DetectedStack,\n type DetectedStructure,\n type PackageScanResult,\n} from '@viberails/types';\n\n/** Framework priority order — higher-priority frameworks become the primary. */\nconst FRAMEWORK_PRIORITY = [\n 'nextjs',\n 'sveltekit',\n 'astro',\n 'expo',\n 'react-native',\n 'svelte',\n 'vue',\n 'react',\n];\n\n/**\n * Combines per-package stacks into a single aggregate stack.\n *\n * TypeScript wins over JavaScript if any package uses it.\n * The highest-priority framework becomes the primary; others go into libraries.\n */\nexport function aggregateStacks(packages: PackageScanResult[]): DetectedStack {\n if (packages.length === 1) return packages[0].stack;\n\n const language = packages.some((p) => p.stack.language.name === 'typescript')\n ? packages.find((p) => p.stack.language.name === 'typescript')!.stack.language\n : packages[0].stack.language;\n\n const packageManager = packages[0].stack.packageManager;\n\n const frameworkPackages = packages.filter((p) => p.stack.framework);\n let framework: (typeof packages)[0]['stack']['framework'];\n if (frameworkPackages.length > 0) {\n frameworkPackages.sort((a, b) => {\n const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework!.name);\n const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework!.name);\n return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);\n });\n framework = frameworkPackages[0].stack.framework;\n }\n\n const libraryMap = new Map<string, { name: string; version?: string }>();\n\n for (const pkg of packages) {\n if (pkg.stack.framework && pkg.stack.framework.name !== framework?.name) {\n libraryMap.set(pkg.stack.framework.name, pkg.stack.framework);\n }\n for (const lib of pkg.stack.libraries) {\n if (!libraryMap.has(lib.name)) {\n libraryMap.set(lib.name, lib);\n }\n }\n }\n\n const styling = packages.find((p) => p.stack.styling)?.stack.styling;\n const backend = packages.find((p) => p.stack.backend)?.stack.backend;\n const linter = packages.find((p) => p.stack.linter)?.stack.linter;\n const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;\n\n return {\n language,\n packageManager,\n framework,\n libraries: [...libraryMap.values()],\n styling,\n backend,\n linter,\n testRunner,\n };\n}\n\n/**\n * Combines per-package structures into a single aggregate structure.\n *\n * Directory paths are prefixed with the package's relativePath.\n */\nexport function aggregateStructures(packages: PackageScanResult[]): DetectedStructure {\n if (packages.length === 1) return packages[0].structure;\n\n const srcDir = packages.some((p) => p.structure.srcDir) ? 'src' : undefined;\n\n const directories = packages.flatMap((pkg) =>\n pkg.structure.directories.map((dir) => ({\n ...dir,\n path: pkg.relativePath ? `${pkg.relativePath}/${dir.path}` : dir.path,\n })),\n );\n\n const testPatterns = packages\n .map((p) => p.structure.testPattern)\n .filter((t): t is DetectedConvention<string> => t !== undefined);\n\n let testPattern: DetectedConvention<string> | undefined;\n if (testPatterns.length > 0) {\n const counts = new Map<string, { count: number; pattern: DetectedConvention<string> }>();\n for (const tp of testPatterns) {\n const existing = counts.get(tp.value);\n if (existing) {\n existing.count++;\n } else {\n counts.set(tp.value, { count: 1, pattern: tp });\n }\n }\n let best = { count: 0, pattern: testPatterns[0] };\n for (const entry of counts.values()) {\n if (entry.count > best.count) best = entry;\n }\n testPattern = best.pattern;\n }\n\n return { srcDir, directories, testPattern };\n}\n\n/**\n * Combines per-package conventions into aggregate conventions.\n *\n * When all packages agree on a convention, reports it with averaged consistency.\n * When packages disagree, scales consistency by agreement ratio.\n * Omits conventions present in fewer than half of packages.\n */\nexport function aggregateConventions(\n packages: PackageScanResult[],\n): Record<string, DetectedConvention> {\n if (packages.length === 1) return packages[0].conventions;\n\n const allKeys = new Set<string>();\n for (const pkg of packages) {\n for (const key of Object.keys(pkg.conventions)) {\n allKeys.add(key);\n }\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n for (const key of allKeys) {\n const entries = packages\n .map((p) => p.conventions[key])\n .filter((c): c is DetectedConvention => c !== undefined);\n\n if (entries.length < packages.length / 2) continue;\n\n const valueCounts = new Map<\n string,\n { count: number; totalConsistency: number; totalSamples: number }\n >();\n for (const entry of entries) {\n const existing = valueCounts.get(entry.value);\n if (existing) {\n existing.count++;\n existing.totalConsistency += entry.consistency;\n existing.totalSamples += entry.sampleSize;\n } else {\n valueCounts.set(entry.value, {\n count: 1,\n totalConsistency: entry.consistency,\n totalSamples: entry.sampleSize,\n });\n }\n }\n\n let majorityValue = '';\n let majorityData = { count: 0, totalConsistency: 0, totalSamples: 0 };\n for (const [value, data] of valueCounts) {\n if (data.count > majorityData.count) {\n majorityValue = value;\n majorityData = data;\n }\n }\n\n const agreement = majorityData.count / entries.length;\n const avgConsistency = majorityData.totalConsistency / majorityData.count;\n const consistency = Math.round(avgConsistency * agreement);\n\n result[key] = {\n value: majorityValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: majorityData.totalSamples,\n consistency,\n };\n }\n\n return result;\n}\n\n/**\n * Combines per-package statistics into aggregate statistics.\n *\n * Sums totals, recomputes averages, merges largest files with path prefixing.\n */\nexport function aggregateStatistics(packages: PackageScanResult[]): CodebaseStatistics {\n if (packages.length === 1) return packages[0].statistics;\n\n const totalFiles = packages.reduce((sum, p) => sum + p.statistics.totalFiles, 0);\n const totalLines = packages.reduce((sum, p) => sum + p.statistics.totalLines, 0);\n const averageFileLines = totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0;\n\n const largestFiles = packages\n .flatMap((pkg) =>\n pkg.statistics.largestFiles.map((f) => ({\n path: pkg.relativePath ? `${pkg.relativePath}/${f.path}` : f.path,\n lines: f.lines,\n })),\n )\n .sort((a, b) => b.lines - a.lines)\n .slice(0, 5);\n\n const filesByExtension: Record<string, number> = {};\n for (const pkg of packages) {\n for (const [ext, count] of Object.entries(pkg.statistics.filesByExtension)) {\n filesByExtension[ext] = (filesByExtension[ext] ?? 0) + count;\n }\n }\n\n return { totalFiles, totalLines, averageFileLines, largestFiles, filesByExtension };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport type { CodebaseStatistics, FileStatistic } from '@viberails/types';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { SOURCE_EXTENSIONS, walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Counts lines in a file by reading its contents.\n *\n * @param filePath - Absolute path to the file.\n * @returns Number of lines, or 0 if the file can't be read.\n */\nasync function countLines(filePath: string): Promise<number> {\n try {\n const content = await readFile(filePath, 'utf-8');\n if (content.length === 0) return 0;\n let count = 0;\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10) count++;\n }\n // A file with no trailing newline has one more line than newline count\n if (content.charCodeAt(content.length - 1) !== 10) count++;\n return count;\n } catch {\n return 0;\n }\n}\n\n/**\n * Collects root-level source file names from the project directory.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns Array of source file names in the root directory.\n */\nasync function getRootSourceFiles(projectPath: string): Promise<string[]> {\n try {\n const entries = await readdir(projectPath, { withFileTypes: true });\n const sourceFiles: string[] = [];\n for (const entry of entries) {\n if (entry.isFile()) {\n const ext = extname(entry.name);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFiles.push(entry.name);\n }\n }\n }\n return sourceFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Computes quantitative statistics about a project's source files.\n *\n * Reads each source file and produces aggregate metrics including\n * file counts, line counts, and extension breakdown.\n *\n * @param projectPath - Absolute path to the project root.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns Statistics about the codebase.\n */\nexport async function computeStatistics(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<CodebaseStatistics> {\n const directories = dirs ?? (await walkDirectory(projectPath));\n const rootFiles = await getRootSourceFiles(projectPath);\n\n // Collect all file paths and extensions\n const filesToProcess: Array<{ relativePath: string; absolutePath: string; ext: string }> = [];\n\n // Root-level source files\n for (const name of rootFiles) {\n filesToProcess.push({\n relativePath: name,\n absolutePath: join(projectPath, name),\n ext: extname(name),\n });\n }\n\n // Files from subdirectories\n for (const dir of directories) {\n for (const name of dir.sourceFileNames) {\n filesToProcess.push({\n relativePath: `${dir.relativePath}/${name}`,\n absolutePath: join(dir.absolutePath, name),\n ext: extname(name),\n });\n }\n }\n\n const totalFiles = filesToProcess.length;\n\n if (totalFiles === 0) {\n return {\n totalFiles: 0,\n totalLines: 0,\n averageFileLines: 0,\n largestFiles: [],\n filesByExtension: {},\n };\n }\n\n // Count lines for all files concurrently\n const lineResults = await Promise.all(\n filesToProcess.map(async (file) => ({\n path: file.relativePath,\n lines: await countLines(file.absolutePath),\n ext: file.ext,\n })),\n );\n\n let totalLines = 0;\n const filesByExtension: Record<string, number> = {};\n const allFiles: FileStatistic[] = [];\n\n for (const result of lineResults) {\n totalLines += result.lines;\n filesByExtension[result.ext] = (filesByExtension[result.ext] ?? 0) + 1;\n allFiles.push({ path: result.path, lines: result.lines });\n }\n\n // Sort descending by lines, take top 5\n allFiles.sort((a, b) => b.lines - a.lines);\n const largestFiles = allFiles.slice(0, 5);\n\n return {\n totalFiles,\n totalLines,\n averageFileLines: Math.round(totalLines / totalFiles),\n largestFiles,\n filesByExtension,\n };\n}\n","import { readdir } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\n\n/** Directories to always skip during scanning. */\nconst IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n '.viberails',\n 'coverage',\n '.turbo',\n '.cache',\n '.output',\n]);\n\n/** Source file extensions to count. */\nexport const SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.vue',\n '.svelte',\n '.astro',\n]);\n\nexport interface WalkedDirectory {\n /** Path relative to project root, using forward slashes. */\n relativePath: string;\n /** Absolute path. */\n absolutePath: string;\n /** Number of source files directly in this directory. */\n sourceFileCount: number;\n /** Names of source files in this directory. */\n sourceFileNames: string[];\n /** Depth relative to the project root (1 = direct children). */\n depth: number;\n}\n\n/**\n * Walks a project directory tree using BFS, collecting directory info.\n *\n * @param projectPath - Absolute path to the project root.\n * @param maxDepth - Maximum directory depth to traverse (default 4).\n * @returns Flat list of all visited directories (excluding root itself).\n */\nexport async function walkDirectory(\n projectPath: string,\n maxDepth: number = 4,\n): Promise<WalkedDirectory[]> {\n const results: WalkedDirectory[] = [];\n const queue: Array<{ absolutePath: string; depth: number }> = [];\n\n try {\n const rootEntries = await readdir(projectPath, { withFileTypes: true });\n for (const entry of rootEntries) {\n if (entry.isDirectory() && !entry.isSymbolicLink() && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(projectPath, entry.name), depth: 1 });\n }\n }\n } catch {\n return results;\n }\n\n while (queue.length > 0) {\n const { absolutePath, depth } = queue.shift()!;\n const sourceFileNames: string[] = [];\n\n try {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isSymbolicLink()) continue;\n\n if (entry.isDirectory() && depth < maxDepth && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(absolutePath, entry.name), depth: depth + 1 });\n } else if (entry.isFile()) {\n const dotIndex = entry.name.lastIndexOf('.');\n if (dotIndex > 0) {\n const ext = entry.name.substring(dotIndex);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFileNames.push(entry.name);\n }\n }\n }\n }\n } catch {\n continue;\n }\n\n const rel = relative(projectPath, absolutePath).split('\\\\').join('/');\n results.push({\n relativePath: rel,\n absolutePath,\n sourceFileCount: sourceFileNames.length,\n sourceFileNames,\n depth,\n });\n }\n\n return results;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyFilename } from './utils/classify-filename.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects coding conventions used in a project by analyzing file names\n * and configuration files.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param structure - Previously detected directory structure, used to identify\n * directories by role.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns A record of detected conventions keyed by convention name.\n * Only statistical conventions with sampleSize >= 3 are included.\n */\nexport async function detectConventions(\n projectPath: string,\n structure: DetectedStructure,\n dirs?: WalkedDirectory[],\n): Promise<Record<string, DetectedConvention>> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n const fileNaming = detectFileNaming(dirs);\n if (fileNaming) result.fileNaming = fileNaming;\n\n const componentNaming = detectComponentNaming(dirs, structure);\n if (componentNaming) result.componentNaming = componentNaming;\n\n const hookNaming = detectHookNaming(dirs, structure);\n if (hookNaming) result.hookNaming = hookNaming;\n\n // importAlias is binary (present or not) — bypasses sampleSize threshold\n const importAlias = await detectImportAlias(projectPath);\n if (importAlias) result.importAlias = importAlias;\n\n return result;\n}\n\n/** Strips the file extension, handling multi-dot names like foo.test.ts. */\nfunction stripExtension(filename: string): string {\n const firstDot = filename.indexOf('.');\n return firstDot > 0 ? filename.substring(0, firstDot) : filename;\n}\n\n/**\n * Detects the dominant file naming convention across all directories\n * with 3+ source files. Only returns conventions with consistency >= 70%\n * (medium or high confidence).\n */\nfunction detectFileNaming(dirs: WalkedDirectory[]): DetectedConvention | undefined {\n const conventionCounts = new Map<string, number>();\n let total = 0;\n\n for (const dir of dirs) {\n if (dir.sourceFileCount < 3) continue;\n\n for (const filename of dir.sourceFileNames) {\n if (filename.includes('.test.') || filename.includes('.spec.')) continue;\n const bare = stripExtension(filename);\n if (bare === 'index') continue;\n\n const convention = classifyFilename(bare);\n total++;\n if (convention !== 'unknown') {\n conventionCounts.set(convention, (conventionCounts.get(convention) ?? 0) + 1);\n }\n }\n }\n\n if (total < 3) return undefined;\n\n const sorted = [...conventionCounts.entries()].sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0) return undefined;\n\n const [dominantConvention, dominantCount] = sorted[0];\n const consistency = Math.round((dominantCount / total) * 100);\n const confidence = confidenceFromConsistency(consistency);\n\n if (confidence === 'low') return undefined;\n\n return {\n value: dominantConvention,\n confidence,\n sampleSize: total,\n consistency,\n };\n}\n\n/**\n * Detects component naming convention by checking if .tsx files in\n * component directories use PascalCase filenames.\n */\nfunction detectComponentNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const componentPaths = new Set(\n structure.directories.filter((d) => d.role === 'components').map((d) => d.path),\n );\n\n const tsxFiles: string[] = [];\n for (const dir of dirs) {\n if (!componentPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n if (f.endsWith('.tsx')) tsxFiles.push(f);\n }\n }\n\n if (tsxFiles.length < 3) return undefined;\n\n const pascalCount = tsxFiles.filter((f) => /^[A-Z]/.test(stripExtension(f))).length;\n const consistency = Math.round((pascalCount / tsxFiles.length) * 100);\n const dominantValue = pascalCount >= tsxFiles.length / 2 ? 'PascalCase' : 'camelCase';\n\n return {\n value: dominantValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: tsxFiles.length,\n consistency,\n };\n}\n\n/**\n * Detects hook naming convention by checking if hook files use\n * kebab-case (use-*) or camelCase (useXxx) prefix.\n */\nfunction detectHookNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const hookPaths = new Set(\n structure.directories.filter((d) => d.role === 'hooks').map((d) => d.path),\n );\n\n const hookFiles: string[] = [];\n for (const dir of dirs) {\n if (!hookPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n const bare = stripExtension(f);\n if (bare.startsWith('use-') || /^use[A-Z]/.test(bare)) {\n hookFiles.push(f);\n }\n }\n }\n\n if (hookFiles.length < 3) return undefined;\n\n let kebabCount = 0;\n let camelCount = 0;\n\n for (const filename of hookFiles) {\n const bare = stripExtension(filename);\n if (bare.startsWith('use-')) {\n kebabCount++;\n } else if (/^use[A-Z]/.test(bare)) {\n camelCount++;\n }\n }\n\n const total = hookFiles.length;\n const isDominantKebab = kebabCount >= camelCount;\n const dominantCount = isDominantKebab ? kebabCount : camelCount;\n const consistency = Math.round((dominantCount / total) * 100);\n\n return {\n value: isDominantKebab ? 'use-*' : 'useXxx',\n confidence: confidenceFromConsistency(consistency),\n sampleSize: total,\n consistency,\n };\n}\n\n/** Minimal shape for the tsconfig subset we read. */\ninterface TsConfigSubset {\n compilerOptions?: {\n paths?: Record<string, string[]>;\n };\n}\n\n/**\n * Detects import alias patterns from tsconfig.json paths configuration.\n * Returns undefined if tsconfig.json is missing or has no paths.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n try {\n const raw = await readFile(join(projectPath, 'tsconfig.json'), 'utf-8');\n const tsconfig = JSON.parse(raw) as TsConfigSubset;\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) return undefined;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) return undefined;\n\n return {\n value: aliases.join(','),\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Naming convention types for filenames.\n */\nexport type FilenameConvention =\n | 'kebab-case'\n | 'camelCase'\n | 'PascalCase'\n | 'snake_case'\n | 'unknown';\n\nconst PATTERNS = [\n { convention: 'PascalCase', regex: /^[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'camelCase', regex: /^[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'kebab-case', regex: /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/ },\n { convention: 'snake_case', regex: /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/ },\n] as const;\n\n/**\n * Classifies a filename (without extension) into a naming convention.\n *\n * Single lowercase words like 'utils' return 'unknown' because they are\n * ambiguous — they could be kebab-case, snake_case, or camelCase without\n * a disambiguating structural marker.\n *\n * @param filename - The bare filename with no extension (e.g. 'user-profile').\n * @returns The detected naming convention, or 'unknown' if ambiguous.\n */\nexport function classifyFilename(filename: string): FilenameConvention {\n for (const { convention, regex } of PATTERNS) {\n if (regex.test(filename)) return convention;\n }\n return 'unknown';\n}\n","import { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedStack, StackItem } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Extracts the major version number from a semver range string.\n *\n * @param range - A semver range such as `\"^15.0.3\"`, `\"~2.1.0\"`, or `\"3.x\"`.\n * @returns The major version string (e.g. `\"15\"`), or `undefined` if extraction fails.\n */\nexport function extractMajorVersion(range: string): string | undefined {\n const match = range.match(/(\\d+)/);\n return match?.[1];\n}\n\n/** Check whether a file exists at the given path. */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Detection mapping tables\n// ---------------------------------------------------------------------------\n\ninterface FrameworkMapping {\n /** Package name to look for in dependencies. */\n dep: string;\n /** Name to use in the StackItem. */\n name: string;\n /** If set, the dep must NOT be present for this rule to match. */\n excludeDep?: string;\n}\n\nconst FRAMEWORK_MAPPINGS: FrameworkMapping[] = [\n { dep: 'next', name: 'nextjs' },\n { dep: 'expo', name: 'expo' },\n { dep: 'react-native', name: 'react-native', excludeDep: 'expo' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: 'vue', name: 'vue' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'hono', name: 'hono' },\n { dep: '@supabase/supabase-js', name: 'supabase' },\n { dep: 'firebase', name: 'firebase' },\n { dep: '@prisma/client', name: 'prisma' },\n { dep: 'prisma', name: 'prisma' },\n { dep: 'drizzle-orm', name: 'drizzle' },\n];\n\nconst STYLING_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'tailwindcss', name: 'tailwindcss' },\n { dep: 'styled-components', name: 'styled-components' },\n { dep: '@emotion/react', name: 'emotion' },\n { dep: 'sass', name: 'sass' },\n];\n\nconst LIBRARY_MAPPINGS: Array<{ deps: string[]; name: string }> = [\n { deps: ['zod'], name: 'zod' },\n { deps: ['@trpc/server', '@trpc/client'], name: 'trpc' },\n { deps: ['@tanstack/react-query'], name: 'react-query' },\n];\n\nconst LOCK_FILE_MAP: Array<{ file: string; name: string }> = [\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'bun.lockb', name: 'bun' },\n { file: 'package-lock.json', name: 'npm' },\n];\n\n// ---------------------------------------------------------------------------\n// Main detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the technology stack of a project by reading its package.json\n * and checking for lock files and configuration files.\n *\n * When `additionalDeps` is provided, they are merged as a base layer\n * beneath the package's own deps. This allows monorepo root-level deps\n * (e.g. typescript, eslint) to be visible during per-package scanning.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param additionalDeps - Optional base dependencies merged under package deps.\n * @returns The detected technology stack.\n */\nexport async function detectStack(\n projectPath: string,\n additionalDeps?: Record<string, string>,\n): Promise<DetectedStack> {\n const pkg = await readPackageJson(projectPath);\n const allDeps: Record<string, string> = {\n ...additionalDeps,\n ...pkg?.dependencies,\n ...pkg?.devDependencies,\n };\n\n const framework = detectFramework(allDeps);\n const language = await detectLanguage(projectPath, allDeps);\n const styling = detectFirst(allDeps, STYLING_MAPPINGS);\n const backend = detectFirst(allDeps, BACKEND_MAPPINGS);\n const packageManager = await detectPackageManager(projectPath);\n const linter = detectLinter(allDeps);\n const testRunner = detectTestRunner(allDeps);\n const libraries = detectLibraries(allDeps);\n\n return {\n ...(framework && { framework }),\n language,\n ...(styling && { styling }),\n ...(backend && { backend }),\n packageManager,\n ...(linter && { linter }),\n ...(testRunner && { testRunner }),\n libraries,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Individual detectors\n// ---------------------------------------------------------------------------\n\nfunction detectFramework(allDeps: Record<string, string>): StackItem | undefined {\n for (const mapping of FRAMEWORK_MAPPINGS) {\n if (!(mapping.dep in allDeps)) continue;\n if (mapping.excludeDep && mapping.excludeDep in allDeps) continue;\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n return undefined;\n}\n\nasync function detectLanguage(\n projectPath: string,\n allDeps: Record<string, string>,\n): Promise<StackItem> {\n if ('typescript' in allDeps) {\n return {\n name: 'typescript',\n version: extractMajorVersion(allDeps.typescript),\n };\n }\n if (await fileExists(join(projectPath, 'tsconfig.json'))) {\n return { name: 'typescript' };\n }\n return { name: 'javascript' };\n}\n\nfunction detectFirst(\n allDeps: Record<string, string>,\n mappings: Array<{ dep: string; name: string }>,\n): StackItem | undefined {\n for (const mapping of mappings) {\n if (mapping.dep in allDeps) {\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n }\n return undefined;\n}\n\nasync function detectPackageManager(projectPath: string): Promise<StackItem> {\n for (const entry of LOCK_FILE_MAP) {\n if (await fileExists(join(projectPath, entry.file))) {\n return { name: entry.name };\n }\n }\n return { name: 'npm' };\n}\n\nfunction detectLinter(allDeps: Record<string, string>): StackItem | undefined {\n if ('eslint' in allDeps) {\n return { name: 'eslint', version: extractMajorVersion(allDeps.eslint) };\n }\n if ('@biomejs/biome' in allDeps) {\n return {\n name: 'biome',\n version: extractMajorVersion(allDeps['@biomejs/biome']),\n };\n }\n return undefined;\n}\n\nfunction detectTestRunner(allDeps: Record<string, string>): StackItem | undefined {\n if ('vitest' in allDeps) {\n return { name: 'vitest', version: extractMajorVersion(allDeps.vitest) };\n }\n if ('jest' in allDeps) {\n return { name: 'jest', version: extractMajorVersion(allDeps.jest) };\n }\n return undefined;\n}\n\nfunction detectLibraries(allDeps: Record<string, string>): StackItem[] {\n const libs: StackItem[] = [];\n for (const mapping of LIBRARY_MAPPINGS) {\n const found = mapping.deps.find((dep) => dep in allDeps);\n if (found) {\n libs.push({\n name: mapping.name,\n version: extractMajorVersion(allDeps[found]),\n });\n }\n }\n return libs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Minimal interface for the fields we read from package.json.\n */\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Safely reads and parses a package.json file from the given directory.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.\n */\nexport async function readPackageJson(projectPath: string): Promise<PackageJson | null> {\n try {\n const raw = await readFile(join(projectPath, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n","import type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyDirectory } from './utils/classify-directory.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects the directory structure and organization of a project.\n *\n * Classifies directories by role, detects whether a src/ directory is in use,\n * and identifies test file patterns.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns The detected directory structure.\n */\nexport async function detectStructure(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<DetectedStructure> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n // Detect srcDir\n const hasSrcDir = dirs.some((d) => d.relativePath === 'src' || d.relativePath.startsWith('src/'));\n const srcDir = hasSrcDir ? 'src' : undefined;\n\n // Classify directories\n const directories = dirs.map((d) => classifyDirectory(d)).filter((d) => d !== null);\n\n // Detect test pattern\n const testPattern = detectTestPattern(dirs);\n\n return {\n ...(srcDir !== undefined && { srcDir }),\n directories,\n ...(testPattern !== undefined && { testPattern }),\n };\n}\n\n/**\n * Detects the dominant test file naming pattern from all source files.\n * Returns undefined if fewer than 3 test files are found.\n */\nfunction detectTestPattern(\n dirs: Array<{ sourceFileNames: string[] }>,\n): DetectedConvention<string> | undefined {\n const allFiles = dirs.flatMap((d) => d.sourceFileNames);\n const testFiles = allFiles.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n\n if (testFiles.length < 3) return undefined;\n\n const dotTestCount = testFiles.filter((f) => f.includes('.test.')).length;\n const dotSpecCount = testFiles.filter((f) => f.includes('.spec.')).length;\n\n const isDotTest = dotTestCount >= dotSpecCount;\n const dominantSep = isDotTest ? '.test.' : '.spec.';\n const dominantCount = isDotTest ? dotTestCount : dotSpecCount;\n const consistency = Math.round((dominantCount / testFiles.length) * 100);\n\n // Find most common extension among dominant test files\n const extCounts = new Map<string, number>();\n for (const f of testFiles.filter((f) => f.includes(dominantSep))) {\n const ext = f.substring(f.lastIndexOf('.'));\n extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n const topExt = [...extCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? '.ts';\n\n const sep = isDotTest ? 'test' : 'spec';\n\n return {\n value: `*.${sep}${topExt}`,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: testFiles.length,\n consistency,\n };\n}\n","import type { Confidence, DirectoryRole } from '@viberails/types';\nimport type { WalkedDirectory } from './walk-directory.js';\n\nexport interface ClassifiedDirectory {\n path: string;\n role: DirectoryRole;\n fileCount: number;\n confidence: Confidence;\n}\n\ninterface RolePattern {\n role: DirectoryRole;\n pathPatterns: string[];\n}\n\nconst ROLE_PATTERNS: RolePattern[] = [\n { role: 'pages', pathPatterns: ['src/app', 'src/pages', 'app', 'pages'] },\n { role: 'components', pathPatterns: ['src/components', 'components'] },\n { role: 'hooks', pathPatterns: ['src/hooks', 'hooks'] },\n {\n role: 'utils',\n pathPatterns: ['src/lib', 'src/utils', 'src/helpers', 'lib', 'utils', 'helpers'],\n },\n { role: 'types', pathPatterns: ['src/types', 'types', 'src/@types', '@types'] },\n { role: 'tests', pathPatterns: ['__tests__', 'tests', 'test', 'src/__tests__', 'src/tests'] },\n { role: 'styles', pathPatterns: ['src/styles', 'styles', 'src/css', 'css'] },\n { role: 'api', pathPatterns: ['src/api', 'api', 'src/app/api', 'app/api'] },\n { role: 'config', pathPatterns: ['config', 'src/config'] },\n];\n\n/**\n * Classifies a directory by its role in the project.\n *\n * @param dir - A walked directory entry.\n * @returns Classified directory info, or null if the directory should be skipped.\n */\nexport function classifyDirectory(dir: WalkedDirectory): ClassifiedDirectory | null {\n // Try name-based matching first\n const nameMatch = matchByName(dir.relativePath);\n if (nameMatch) {\n const confidence: Confidence = dir.sourceFileCount > 0 ? 'high' : 'low';\n return {\n path: dir.relativePath,\n role: nameMatch,\n fileCount: dir.sourceFileCount,\n confidence,\n };\n }\n\n // Skip directories with no source files if no name match\n if (dir.sourceFileCount === 0) return null;\n\n // Content-based heuristics\n const contentRole = inferFromContent(dir);\n if (contentRole) return contentRole;\n\n // Has source files but no classification\n return {\n path: dir.relativePath,\n role: 'unknown',\n fileCount: dir.sourceFileCount,\n confidence: 'low',\n };\n}\n\n/**\n * Matches a relative path against known role patterns.\n * Supports suffix matching so monorepo paths like `apps/web/lib`\n * match patterns like `lib`.\n * Returns the role if matched, or null.\n */\nfunction matchByName(relativePath: string): DirectoryRole | null {\n for (const { role, pathPatterns } of ROLE_PATTERNS) {\n for (const pattern of pathPatterns) {\n if (relativePath === pattern || relativePath.endsWith(`/${pattern}`)) {\n return role;\n }\n }\n }\n return null;\n}\n\n/**\n * Infers directory role from file contents/names.\n */\nfunction inferFromContent(dir: WalkedDirectory): ClassifiedDirectory | null {\n const { sourceFileNames, sourceFileCount } = dir;\n\n // Check for hook files (use-* kebab or useXxx camelCase prefix)\n // Require at least 2 matching files to avoid misclassifying directories\n // with a single hook utility (e.g. lib/ with one useXxx file)\n const hookFiles = sourceFileNames.filter((f) => {\n const name = f.split('.')[0];\n return name.startsWith('use-') || /^use[A-Z]/.test(name);\n });\n if (hookFiles.length >= 2 && hookFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'hooks',\n fileCount: sourceFileCount,\n confidence: hookFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n // Check for test files — require at least 2 to avoid false positives\n const testFiles = sourceFileNames.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n if (testFiles.length >= 2 && testFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'tests',\n fileCount: sourceFileCount,\n confidence: testFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n return null;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { DetectedWorkspace, WorkspacePackage } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Detects workspace configuration for monorepo projects.\n *\n * Checks for `pnpm-workspace.yaml` first, then falls back to\n * `package.json` `workspaces` field. Returns `undefined` for\n * single-package projects.\n *\n * @param projectRoot - Absolute path to the project root.\n * @returns Detected workspace info, or `undefined` if not a monorepo.\n */\nexport async function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined> {\n const patterns = await readWorkspacePatterns(projectRoot);\n if (!patterns || patterns.length === 0) return undefined;\n\n const packageDirs = await resolvePatterns(projectRoot, patterns);\n const packages = await resolvePackages(projectRoot, packageDirs);\n\n if (packages.length === 0) return undefined;\n\n // Resolve internal deps: check if any dependency name matches a workspace package\n const packageNames = new Set(packages.map((p) => p.name));\n for (const pkg of packages) {\n pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));\n }\n\n return { patterns, packages };\n}\n\n/**\n * Reads workspace glob patterns from pnpm-workspace.yaml or package.json.\n */\nasync function readWorkspacePatterns(projectRoot: string): Promise<string[] | undefined> {\n // Try pnpm-workspace.yaml first\n try {\n const yaml = await readFile(join(projectRoot, 'pnpm-workspace.yaml'), 'utf-8');\n return parsePnpmWorkspaceYaml(yaml);\n } catch {\n // Not found, try package.json\n }\n\n // Fall back to package.json workspaces field\n const pkg = await readPackageJson(projectRoot);\n if (!pkg) return undefined;\n\n const raw = pkg as Record<string, unknown>;\n const workspaces = raw.workspaces;\n\n if (Array.isArray(workspaces)) {\n return workspaces.filter((w): w is string => typeof w === 'string');\n }\n\n // Handle { packages: [...] } format\n if (workspaces && typeof workspaces === 'object' && 'packages' in workspaces) {\n const nested = (workspaces as { packages: unknown }).packages;\n if (Array.isArray(nested)) {\n return nested.filter((w): w is string => typeof w === 'string');\n }\n }\n\n return undefined;\n}\n\n/**\n * Parses workspace patterns from pnpm-workspace.yaml content.\n * Handles the common format: `packages:\\n - 'packages/*'`\n */\nfunction parsePnpmWorkspaceYaml(content: string): string[] {\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (trimmed === 'packages:') {\n inPackages = true;\n continue;\n }\n\n // Stop at next top-level key\n if (inPackages && trimmed.length > 0 && !trimmed.startsWith('-')) {\n break;\n }\n\n if (inPackages && trimmed.startsWith('-')) {\n // Extract the pattern, stripping quotes and leading dash\n const value = trimmed\n .slice(1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n if (value) patterns.push(value);\n }\n }\n\n return patterns;\n}\n\n/**\n * Resolves workspace glob patterns to actual directory paths.\n * Supports simple `*` wildcard matching (e.g. `packages/*`).\n */\nasync function resolvePatterns(projectRoot: string, patterns: string[]): Promise<string[]> {\n const dirs: string[] = [];\n\n for (const pattern of patterns) {\n if (pattern.endsWith('/*')) {\n // Simple wildcard: list children of the parent directory\n const parent = join(projectRoot, pattern.slice(0, -2));\n try {\n const entries = await readdir(parent, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n dirs.push(join(parent, entry.name));\n }\n }\n } catch {\n // Parent directory doesn't exist, skip\n }\n } else {\n // Literal path\n dirs.push(join(projectRoot, pattern));\n }\n }\n\n return dirs;\n}\n\n/**\n * Resolves workspace directories to WorkspacePackage objects.\n * Skips directories without a valid package.json.\n */\nasync function resolvePackages(projectRoot: string, dirs: string[]): Promise<WorkspacePackage[]> {\n const packages: WorkspacePackage[] = [];\n\n for (const dir of dirs) {\n const pkg = await readPackageJson(dir);\n if (!pkg?.name) continue;\n\n // Collect all dependency names as potential internal deps\n const allDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n\n packages.push({\n name: pkg.name,\n path: dir,\n relativePath: relative(projectRoot, dir),\n internalDeps: allDeps,\n });\n }\n\n return packages;\n}\n","import { resolve } from 'node:path';\nimport type { PackageScanResult } from '@viberails/types';\nimport { computeStatistics } from './compute-statistics.js';\nimport { detectConventions } from './detect-conventions.js';\nimport { detectStack } from './detect-stack.js';\nimport { detectStructure } from './detect-structure.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Scans a single package directory and returns per-package scan results.\n *\n * Reuses all existing detector functions scoped to the package directory.\n * In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,\n * eslint) as a base layer — package-specific deps overlay on top.\n *\n * @param packagePath - Absolute path to the package directory.\n * @param name - Package name from package.json.\n * @param relativePath - Path relative to workspace root (empty string for single-package).\n * @param rootDeps - Optional root-level dependencies merged as a base layer.\n * @returns Per-package scan result.\n */\nexport async function scanPackage(\n packagePath: string,\n name: string,\n relativePath: string,\n rootDeps?: Record<string, string>,\n): Promise<PackageScanResult> {\n const root = resolve(packagePath);\n const dirs = await walkDirectory(root, 4);\n\n const [stack, structure, statistics] = await Promise.all([\n detectStack(root, rootDeps),\n detectStructure(root, dirs),\n computeStatistics(root, dirs),\n ]);\n\n const conventions = await detectConventions(root, structure, dirs);\n\n return {\n name,\n root,\n relativePath,\n stack,\n structure,\n conventions,\n statistics,\n };\n}\n","import { stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\nimport type { ScanResult } from '@viberails/types';\nimport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nimport { detectWorkspace } from './detect-workspace.js';\nimport { scanPackage } from './scan-package.js';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Options for the scan function.\n */\nexport type ScanOptions = {};\n\n/**\n * Scans a project directory and returns a comprehensive analysis of its\n * stack, structure, conventions, and statistics.\n *\n * For monorepos, each workspace package is scanned independently and\n * results are aggregated. For single-package projects, the root is\n * scanned as a single package.\n *\n * @param projectPath - Absolute or relative path to the project root.\n * @param options - Optional scan configuration.\n * @returns Complete scan result for the project.\n * @throws If the project path does not exist or is not a directory.\n */\nexport async function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult> {\n const root = resolve(projectPath);\n\n // Validate that the path exists and is a directory\n try {\n const st = await stat(root);\n if (!st.isDirectory()) {\n throw new Error(`Project path is not a directory: ${root}`);\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith('Project path is not')) {\n throw err;\n }\n throw new Error(`Project path does not exist: ${root}`);\n }\n\n const workspace = await detectWorkspace(root);\n\n if (workspace && workspace.packages.length > 0) {\n // Read root deps for sharing with workspace packages\n const rootPkg = await readPackageJson(root);\n const rootDeps: Record<string, string> = {\n ...rootPkg?.dependencies,\n ...rootPkg?.devDependencies,\n };\n\n // Scan each workspace package in parallel\n const packages = await Promise.all(\n workspace.packages.map((wp) => scanPackage(wp.path, wp.name, wp.relativePath, rootDeps)),\n );\n\n return {\n root,\n stack: aggregateStacks(packages),\n structure: aggregateStructures(packages),\n conventions: aggregateConventions(packages),\n statistics: aggregateStatistics(packages),\n workspace,\n packages,\n };\n }\n\n // Single-package project\n const rootPkg = await readPackageJson(root);\n const name = rootPkg?.name ?? basename(root);\n const pkg = await scanPackage(root, name, '');\n\n return {\n root,\n stack: pkg.stack,\n structure: pkg.structure,\n conventions: pkg.conventions,\n statistics: pkg.statistics,\n packages: [pkg],\n };\n}\n"],"mappings":"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","names":["import_promises","import_node_path","import_promises","import_node_path","import_types","import_promises","import_node_path","import_promises","import_node_path","import_types","f","import_promises","import_node_path","import_node_path","import_promises","import_node_path","rootPkg"]}
|
|
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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -119,25 +119,10 @@ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]):
|
|
|
119
119
|
*/
|
|
120
120
|
declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
|
|
121
121
|
|
|
122
|
-
/**
|
|
123
|
-
* Scans a single package directory and returns per-package scan results.
|
|
124
|
-
*
|
|
125
|
-
* Reuses all existing detector functions scoped to the package directory.
|
|
126
|
-
* In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
|
|
127
|
-
* eslint) as a base layer — package-specific deps overlay on top.
|
|
128
|
-
*
|
|
129
|
-
* @param packagePath - Absolute path to the package directory.
|
|
130
|
-
* @param name - Package name from package.json.
|
|
131
|
-
* @param relativePath - Path relative to workspace root (empty string for single-package).
|
|
132
|
-
* @param rootDeps - Optional root-level dependencies merged as a base layer.
|
|
133
|
-
* @returns Per-package scan result.
|
|
134
|
-
*/
|
|
135
|
-
declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
|
|
136
|
-
|
|
137
122
|
/**
|
|
138
123
|
* Options for the scan function.
|
|
139
124
|
*/
|
|
140
|
-
type ScanOptions =
|
|
125
|
+
type ScanOptions = Record<string, never>;
|
|
141
126
|
/**
|
|
142
127
|
* Scans a project directory and returns a comprehensive analysis of its
|
|
143
128
|
* stack, structure, conventions, and statistics.
|
|
@@ -153,6 +138,21 @@ type ScanOptions = {};
|
|
|
153
138
|
*/
|
|
154
139
|
declare function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult>;
|
|
155
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Scans a single package directory and returns per-package scan results.
|
|
143
|
+
*
|
|
144
|
+
* Reuses all existing detector functions scoped to the package directory.
|
|
145
|
+
* In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
|
|
146
|
+
* eslint) as a base layer — package-specific deps overlay on top.
|
|
147
|
+
*
|
|
148
|
+
* @param packagePath - Absolute path to the package directory.
|
|
149
|
+
* @param name - Package name from package.json.
|
|
150
|
+
* @param relativePath - Path relative to workspace root (empty string for single-package).
|
|
151
|
+
* @param rootDeps - Optional root-level dependencies merged as a base layer.
|
|
152
|
+
* @returns Per-package scan result.
|
|
153
|
+
*/
|
|
154
|
+
declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
|
|
155
|
+
|
|
156
156
|
/**
|
|
157
157
|
* Minimal interface for the fields we read from package.json.
|
|
158
158
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -119,25 +119,10 @@ declare function detectStructure(projectPath: string, dirs?: WalkedDirectory[]):
|
|
|
119
119
|
*/
|
|
120
120
|
declare function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined>;
|
|
121
121
|
|
|
122
|
-
/**
|
|
123
|
-
* Scans a single package directory and returns per-package scan results.
|
|
124
|
-
*
|
|
125
|
-
* Reuses all existing detector functions scoped to the package directory.
|
|
126
|
-
* In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
|
|
127
|
-
* eslint) as a base layer — package-specific deps overlay on top.
|
|
128
|
-
*
|
|
129
|
-
* @param packagePath - Absolute path to the package directory.
|
|
130
|
-
* @param name - Package name from package.json.
|
|
131
|
-
* @param relativePath - Path relative to workspace root (empty string for single-package).
|
|
132
|
-
* @param rootDeps - Optional root-level dependencies merged as a base layer.
|
|
133
|
-
* @returns Per-package scan result.
|
|
134
|
-
*/
|
|
135
|
-
declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
|
|
136
|
-
|
|
137
122
|
/**
|
|
138
123
|
* Options for the scan function.
|
|
139
124
|
*/
|
|
140
|
-
type ScanOptions =
|
|
125
|
+
type ScanOptions = Record<string, never>;
|
|
141
126
|
/**
|
|
142
127
|
* Scans a project directory and returns a comprehensive analysis of its
|
|
143
128
|
* stack, structure, conventions, and statistics.
|
|
@@ -153,6 +138,21 @@ type ScanOptions = {};
|
|
|
153
138
|
*/
|
|
154
139
|
declare function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult>;
|
|
155
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Scans a single package directory and returns per-package scan results.
|
|
143
|
+
*
|
|
144
|
+
* Reuses all existing detector functions scoped to the package directory.
|
|
145
|
+
* In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,
|
|
146
|
+
* eslint) as a base layer — package-specific deps overlay on top.
|
|
147
|
+
*
|
|
148
|
+
* @param packagePath - Absolute path to the package directory.
|
|
149
|
+
* @param name - Package name from package.json.
|
|
150
|
+
* @param relativePath - Path relative to workspace root (empty string for single-package).
|
|
151
|
+
* @param rootDeps - Optional root-level dependencies merged as a base layer.
|
|
152
|
+
* @returns Per-package scan result.
|
|
153
|
+
*/
|
|
154
|
+
declare function scanPackage(packagePath: string, name: string, relativePath: string, rootDeps?: Record<string, string>): Promise<PackageScanResult>;
|
|
155
|
+
|
|
156
156
|
/**
|
|
157
157
|
* Minimal interface for the fields we read from package.json.
|
|
158
158
|
*/
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ function aggregateStacks(packages) {
|
|
|
40
40
|
const styling = packages.find((p) => p.stack.styling)?.stack.styling;
|
|
41
41
|
const backend = packages.find((p) => p.stack.backend)?.stack.backend;
|
|
42
42
|
const linter = packages.find((p) => p.stack.linter)?.stack.linter;
|
|
43
|
+
const formatter = packages.find((p) => p.stack.formatter)?.stack.formatter;
|
|
43
44
|
const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;
|
|
44
45
|
return {
|
|
45
46
|
language,
|
|
@@ -49,6 +50,7 @@ function aggregateStacks(packages) {
|
|
|
49
50
|
styling,
|
|
50
51
|
backend,
|
|
51
52
|
linter,
|
|
53
|
+
formatter,
|
|
52
54
|
testRunner
|
|
53
55
|
};
|
|
54
56
|
}
|
|
@@ -166,7 +168,12 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
166
168
|
"coverage",
|
|
167
169
|
".turbo",
|
|
168
170
|
".cache",
|
|
169
|
-
".output"
|
|
171
|
+
".output",
|
|
172
|
+
".expo",
|
|
173
|
+
"android",
|
|
174
|
+
"ios",
|
|
175
|
+
"Pods",
|
|
176
|
+
".gradle"
|
|
170
177
|
]);
|
|
171
178
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
172
179
|
".ts",
|
|
@@ -193,7 +200,9 @@ async function walkDirectory(projectPath, maxDepth = 4) {
|
|
|
193
200
|
return results;
|
|
194
201
|
}
|
|
195
202
|
while (queue.length > 0) {
|
|
196
|
-
const
|
|
203
|
+
const item = queue.shift();
|
|
204
|
+
if (!item) break;
|
|
205
|
+
const { absolutePath, depth } = item;
|
|
197
206
|
const sourceFileNames = [];
|
|
198
207
|
try {
|
|
199
208
|
const entries = await readdir(absolutePath, { withFileTypes: true });
|
|
@@ -539,6 +548,7 @@ async function detectStack(projectPath, additionalDeps) {
|
|
|
539
548
|
const backend = detectFirst(allDeps, BACKEND_MAPPINGS);
|
|
540
549
|
const packageManager = await detectPackageManager(projectPath);
|
|
541
550
|
const linter = detectLinter(allDeps);
|
|
551
|
+
const formatter = detectFormatter(allDeps);
|
|
542
552
|
const testRunner = detectTestRunner(allDeps);
|
|
543
553
|
const libraries = detectLibraries(allDeps);
|
|
544
554
|
return {
|
|
@@ -548,6 +558,7 @@ async function detectStack(projectPath, additionalDeps) {
|
|
|
548
558
|
...backend && { backend },
|
|
549
559
|
packageManager,
|
|
550
560
|
...linter && { linter },
|
|
561
|
+
...formatter && { formatter },
|
|
551
562
|
...testRunner && { testRunner },
|
|
552
563
|
libraries
|
|
553
564
|
};
|
|
@@ -606,6 +617,18 @@ function detectLinter(allDeps) {
|
|
|
606
617
|
}
|
|
607
618
|
return void 0;
|
|
608
619
|
}
|
|
620
|
+
function detectFormatter(allDeps) {
|
|
621
|
+
if ("prettier" in allDeps) {
|
|
622
|
+
return { name: "prettier", version: extractMajorVersion(allDeps.prettier) };
|
|
623
|
+
}
|
|
624
|
+
if ("@biomejs/biome" in allDeps) {
|
|
625
|
+
return {
|
|
626
|
+
name: "biome",
|
|
627
|
+
version: extractMajorVersion(allDeps["@biomejs/biome"])
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return void 0;
|
|
631
|
+
}
|
|
609
632
|
function detectTestRunner(allDeps) {
|
|
610
633
|
if ("vitest" in allDeps) {
|
|
611
634
|
return { name: "vitest", version: extractMajorVersion(allDeps.vitest) };
|
|
@@ -838,6 +861,10 @@ async function resolvePackages(projectRoot, dirs) {
|
|
|
838
861
|
return packages;
|
|
839
862
|
}
|
|
840
863
|
|
|
864
|
+
// src/scan.ts
|
|
865
|
+
import { stat } from "fs/promises";
|
|
866
|
+
import { basename, resolve as resolve2 } from "path";
|
|
867
|
+
|
|
841
868
|
// src/scan-package.ts
|
|
842
869
|
import { resolve } from "path";
|
|
843
870
|
async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
@@ -861,8 +888,6 @@ async function scanPackage(packagePath, name, relativePath, rootDeps) {
|
|
|
861
888
|
}
|
|
862
889
|
|
|
863
890
|
// src/scan.ts
|
|
864
|
-
import { stat } from "fs/promises";
|
|
865
|
-
import { basename, resolve as resolve2 } from "path";
|
|
866
891
|
async function scan(projectPath, _options) {
|
|
867
892
|
const root = resolve2(projectPath);
|
|
868
893
|
try {
|
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-package.ts","../src/scan.ts","../src/index.ts"],"sourcesContent":["import {\n confidenceFromConsistency,\n type CodebaseStatistics,\n type DetectedConvention,\n type DetectedStack,\n type DetectedStructure,\n type PackageScanResult,\n} from '@viberails/types';\n\n/** Framework priority order — higher-priority frameworks become the primary. */\nconst FRAMEWORK_PRIORITY = [\n 'nextjs',\n 'sveltekit',\n 'astro',\n 'expo',\n 'react-native',\n 'svelte',\n 'vue',\n 'react',\n];\n\n/**\n * Combines per-package stacks into a single aggregate stack.\n *\n * TypeScript wins over JavaScript if any package uses it.\n * The highest-priority framework becomes the primary; others go into libraries.\n */\nexport function aggregateStacks(packages: PackageScanResult[]): DetectedStack {\n if (packages.length === 1) return packages[0].stack;\n\n const language = packages.some((p) => p.stack.language.name === 'typescript')\n ? packages.find((p) => p.stack.language.name === 'typescript')!.stack.language\n : packages[0].stack.language;\n\n const packageManager = packages[0].stack.packageManager;\n\n const frameworkPackages = packages.filter((p) => p.stack.framework);\n let framework: (typeof packages)[0]['stack']['framework'];\n if (frameworkPackages.length > 0) {\n frameworkPackages.sort((a, b) => {\n const aIdx = FRAMEWORK_PRIORITY.indexOf(a.stack.framework!.name);\n const bIdx = FRAMEWORK_PRIORITY.indexOf(b.stack.framework!.name);\n return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);\n });\n framework = frameworkPackages[0].stack.framework;\n }\n\n const libraryMap = new Map<string, { name: string; version?: string }>();\n\n for (const pkg of packages) {\n if (pkg.stack.framework && pkg.stack.framework.name !== framework?.name) {\n libraryMap.set(pkg.stack.framework.name, pkg.stack.framework);\n }\n for (const lib of pkg.stack.libraries) {\n if (!libraryMap.has(lib.name)) {\n libraryMap.set(lib.name, lib);\n }\n }\n }\n\n const styling = packages.find((p) => p.stack.styling)?.stack.styling;\n const backend = packages.find((p) => p.stack.backend)?.stack.backend;\n const linter = packages.find((p) => p.stack.linter)?.stack.linter;\n const testRunner = packages.find((p) => p.stack.testRunner)?.stack.testRunner;\n\n return {\n language,\n packageManager,\n framework,\n libraries: [...libraryMap.values()],\n styling,\n backend,\n linter,\n testRunner,\n };\n}\n\n/**\n * Combines per-package structures into a single aggregate structure.\n *\n * Directory paths are prefixed with the package's relativePath.\n */\nexport function aggregateStructures(packages: PackageScanResult[]): DetectedStructure {\n if (packages.length === 1) return packages[0].structure;\n\n const srcDir = packages.some((p) => p.structure.srcDir) ? 'src' : undefined;\n\n const directories = packages.flatMap((pkg) =>\n pkg.structure.directories.map((dir) => ({\n ...dir,\n path: pkg.relativePath ? `${pkg.relativePath}/${dir.path}` : dir.path,\n })),\n );\n\n const testPatterns = packages\n .map((p) => p.structure.testPattern)\n .filter((t): t is DetectedConvention<string> => t !== undefined);\n\n let testPattern: DetectedConvention<string> | undefined;\n if (testPatterns.length > 0) {\n const counts = new Map<string, { count: number; pattern: DetectedConvention<string> }>();\n for (const tp of testPatterns) {\n const existing = counts.get(tp.value);\n if (existing) {\n existing.count++;\n } else {\n counts.set(tp.value, { count: 1, pattern: tp });\n }\n }\n let best = { count: 0, pattern: testPatterns[0] };\n for (const entry of counts.values()) {\n if (entry.count > best.count) best = entry;\n }\n testPattern = best.pattern;\n }\n\n return { srcDir, directories, testPattern };\n}\n\n/**\n * Combines per-package conventions into aggregate conventions.\n *\n * When all packages agree on a convention, reports it with averaged consistency.\n * When packages disagree, scales consistency by agreement ratio.\n * Omits conventions present in fewer than half of packages.\n */\nexport function aggregateConventions(\n packages: PackageScanResult[],\n): Record<string, DetectedConvention> {\n if (packages.length === 1) return packages[0].conventions;\n\n const allKeys = new Set<string>();\n for (const pkg of packages) {\n for (const key of Object.keys(pkg.conventions)) {\n allKeys.add(key);\n }\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n for (const key of allKeys) {\n const entries = packages\n .map((p) => p.conventions[key])\n .filter((c): c is DetectedConvention => c !== undefined);\n\n if (entries.length < packages.length / 2) continue;\n\n const valueCounts = new Map<\n string,\n { count: number; totalConsistency: number; totalSamples: number }\n >();\n for (const entry of entries) {\n const existing = valueCounts.get(entry.value);\n if (existing) {\n existing.count++;\n existing.totalConsistency += entry.consistency;\n existing.totalSamples += entry.sampleSize;\n } else {\n valueCounts.set(entry.value, {\n count: 1,\n totalConsistency: entry.consistency,\n totalSamples: entry.sampleSize,\n });\n }\n }\n\n let majorityValue = '';\n let majorityData = { count: 0, totalConsistency: 0, totalSamples: 0 };\n for (const [value, data] of valueCounts) {\n if (data.count > majorityData.count) {\n majorityValue = value;\n majorityData = data;\n }\n }\n\n const agreement = majorityData.count / entries.length;\n const avgConsistency = majorityData.totalConsistency / majorityData.count;\n const consistency = Math.round(avgConsistency * agreement);\n\n result[key] = {\n value: majorityValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: majorityData.totalSamples,\n consistency,\n };\n }\n\n return result;\n}\n\n/**\n * Combines per-package statistics into aggregate statistics.\n *\n * Sums totals, recomputes averages, merges largest files with path prefixing.\n */\nexport function aggregateStatistics(packages: PackageScanResult[]): CodebaseStatistics {\n if (packages.length === 1) return packages[0].statistics;\n\n const totalFiles = packages.reduce((sum, p) => sum + p.statistics.totalFiles, 0);\n const totalLines = packages.reduce((sum, p) => sum + p.statistics.totalLines, 0);\n const averageFileLines = totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0;\n\n const largestFiles = packages\n .flatMap((pkg) =>\n pkg.statistics.largestFiles.map((f) => ({\n path: pkg.relativePath ? `${pkg.relativePath}/${f.path}` : f.path,\n lines: f.lines,\n })),\n )\n .sort((a, b) => b.lines - a.lines)\n .slice(0, 5);\n\n const filesByExtension: Record<string, number> = {};\n for (const pkg of packages) {\n for (const [ext, count] of Object.entries(pkg.statistics.filesByExtension)) {\n filesByExtension[ext] = (filesByExtension[ext] ?? 0) + count;\n }\n }\n\n return { totalFiles, totalLines, averageFileLines, largestFiles, filesByExtension };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport type { CodebaseStatistics, FileStatistic } from '@viberails/types';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { SOURCE_EXTENSIONS, walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Counts lines in a file by reading its contents.\n *\n * @param filePath - Absolute path to the file.\n * @returns Number of lines, or 0 if the file can't be read.\n */\nasync function countLines(filePath: string): Promise<number> {\n try {\n const content = await readFile(filePath, 'utf-8');\n if (content.length === 0) return 0;\n let count = 0;\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10) count++;\n }\n // A file with no trailing newline has one more line than newline count\n if (content.charCodeAt(content.length - 1) !== 10) count++;\n return count;\n } catch {\n return 0;\n }\n}\n\n/**\n * Collects root-level source file names from the project directory.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns Array of source file names in the root directory.\n */\nasync function getRootSourceFiles(projectPath: string): Promise<string[]> {\n try {\n const entries = await readdir(projectPath, { withFileTypes: true });\n const sourceFiles: string[] = [];\n for (const entry of entries) {\n if (entry.isFile()) {\n const ext = extname(entry.name);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFiles.push(entry.name);\n }\n }\n }\n return sourceFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Computes quantitative statistics about a project's source files.\n *\n * Reads each source file and produces aggregate metrics including\n * file counts, line counts, and extension breakdown.\n *\n * @param projectPath - Absolute path to the project root.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns Statistics about the codebase.\n */\nexport async function computeStatistics(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<CodebaseStatistics> {\n const directories = dirs ?? (await walkDirectory(projectPath));\n const rootFiles = await getRootSourceFiles(projectPath);\n\n // Collect all file paths and extensions\n const filesToProcess: Array<{ relativePath: string; absolutePath: string; ext: string }> = [];\n\n // Root-level source files\n for (const name of rootFiles) {\n filesToProcess.push({\n relativePath: name,\n absolutePath: join(projectPath, name),\n ext: extname(name),\n });\n }\n\n // Files from subdirectories\n for (const dir of directories) {\n for (const name of dir.sourceFileNames) {\n filesToProcess.push({\n relativePath: `${dir.relativePath}/${name}`,\n absolutePath: join(dir.absolutePath, name),\n ext: extname(name),\n });\n }\n }\n\n const totalFiles = filesToProcess.length;\n\n if (totalFiles === 0) {\n return {\n totalFiles: 0,\n totalLines: 0,\n averageFileLines: 0,\n largestFiles: [],\n filesByExtension: {},\n };\n }\n\n // Count lines for all files concurrently\n const lineResults = await Promise.all(\n filesToProcess.map(async (file) => ({\n path: file.relativePath,\n lines: await countLines(file.absolutePath),\n ext: file.ext,\n })),\n );\n\n let totalLines = 0;\n const filesByExtension: Record<string, number> = {};\n const allFiles: FileStatistic[] = [];\n\n for (const result of lineResults) {\n totalLines += result.lines;\n filesByExtension[result.ext] = (filesByExtension[result.ext] ?? 0) + 1;\n allFiles.push({ path: result.path, lines: result.lines });\n }\n\n // Sort descending by lines, take top 5\n allFiles.sort((a, b) => b.lines - a.lines);\n const largestFiles = allFiles.slice(0, 5);\n\n return {\n totalFiles,\n totalLines,\n averageFileLines: Math.round(totalLines / totalFiles),\n largestFiles,\n filesByExtension,\n };\n}\n","import { readdir } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\n\n/** Directories to always skip during scanning. */\nconst IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n '.viberails',\n 'coverage',\n '.turbo',\n '.cache',\n '.output',\n]);\n\n/** Source file extensions to count. */\nexport const SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.vue',\n '.svelte',\n '.astro',\n]);\n\nexport interface WalkedDirectory {\n /** Path relative to project root, using forward slashes. */\n relativePath: string;\n /** Absolute path. */\n absolutePath: string;\n /** Number of source files directly in this directory. */\n sourceFileCount: number;\n /** Names of source files in this directory. */\n sourceFileNames: string[];\n /** Depth relative to the project root (1 = direct children). */\n depth: number;\n}\n\n/**\n * Walks a project directory tree using BFS, collecting directory info.\n *\n * @param projectPath - Absolute path to the project root.\n * @param maxDepth - Maximum directory depth to traverse (default 4).\n * @returns Flat list of all visited directories (excluding root itself).\n */\nexport async function walkDirectory(\n projectPath: string,\n maxDepth: number = 4,\n): Promise<WalkedDirectory[]> {\n const results: WalkedDirectory[] = [];\n const queue: Array<{ absolutePath: string; depth: number }> = [];\n\n try {\n const rootEntries = await readdir(projectPath, { withFileTypes: true });\n for (const entry of rootEntries) {\n if (entry.isDirectory() && !entry.isSymbolicLink() && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(projectPath, entry.name), depth: 1 });\n }\n }\n } catch {\n return results;\n }\n\n while (queue.length > 0) {\n const { absolutePath, depth } = queue.shift()!;\n const sourceFileNames: string[] = [];\n\n try {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isSymbolicLink()) continue;\n\n if (entry.isDirectory() && depth < maxDepth && !IGNORED_DIRS.has(entry.name)) {\n queue.push({ absolutePath: join(absolutePath, entry.name), depth: depth + 1 });\n } else if (entry.isFile()) {\n const dotIndex = entry.name.lastIndexOf('.');\n if (dotIndex > 0) {\n const ext = entry.name.substring(dotIndex);\n if (SOURCE_EXTENSIONS.has(ext)) {\n sourceFileNames.push(entry.name);\n }\n }\n }\n }\n } catch {\n continue;\n }\n\n const rel = relative(projectPath, absolutePath).split('\\\\').join('/');\n results.push({\n relativePath: rel,\n absolutePath,\n sourceFileCount: sourceFileNames.length,\n sourceFileNames,\n depth,\n });\n }\n\n return results;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyFilename } from './utils/classify-filename.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects coding conventions used in a project by analyzing file names\n * and configuration files.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param structure - Previously detected directory structure, used to identify\n * directories by role.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns A record of detected conventions keyed by convention name.\n * Only statistical conventions with sampleSize >= 3 are included.\n */\nexport async function detectConventions(\n projectPath: string,\n structure: DetectedStructure,\n dirs?: WalkedDirectory[],\n): Promise<Record<string, DetectedConvention>> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n const result: Record<string, DetectedConvention> = {};\n\n const fileNaming = detectFileNaming(dirs);\n if (fileNaming) result.fileNaming = fileNaming;\n\n const componentNaming = detectComponentNaming(dirs, structure);\n if (componentNaming) result.componentNaming = componentNaming;\n\n const hookNaming = detectHookNaming(dirs, structure);\n if (hookNaming) result.hookNaming = hookNaming;\n\n // importAlias is binary (present or not) — bypasses sampleSize threshold\n const importAlias = await detectImportAlias(projectPath);\n if (importAlias) result.importAlias = importAlias;\n\n return result;\n}\n\n/** Strips the file extension, handling multi-dot names like foo.test.ts. */\nfunction stripExtension(filename: string): string {\n const firstDot = filename.indexOf('.');\n return firstDot > 0 ? filename.substring(0, firstDot) : filename;\n}\n\n/**\n * Detects the dominant file naming convention across all directories\n * with 3+ source files. Only returns conventions with consistency >= 70%\n * (medium or high confidence).\n */\nfunction detectFileNaming(dirs: WalkedDirectory[]): DetectedConvention | undefined {\n const conventionCounts = new Map<string, number>();\n let total = 0;\n\n for (const dir of dirs) {\n if (dir.sourceFileCount < 3) continue;\n\n for (const filename of dir.sourceFileNames) {\n if (filename.includes('.test.') || filename.includes('.spec.')) continue;\n const bare = stripExtension(filename);\n if (bare === 'index') continue;\n\n const convention = classifyFilename(bare);\n total++;\n if (convention !== 'unknown') {\n conventionCounts.set(convention, (conventionCounts.get(convention) ?? 0) + 1);\n }\n }\n }\n\n if (total < 3) return undefined;\n\n const sorted = [...conventionCounts.entries()].sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0) return undefined;\n\n const [dominantConvention, dominantCount] = sorted[0];\n const consistency = Math.round((dominantCount / total) * 100);\n const confidence = confidenceFromConsistency(consistency);\n\n if (confidence === 'low') return undefined;\n\n return {\n value: dominantConvention,\n confidence,\n sampleSize: total,\n consistency,\n };\n}\n\n/**\n * Detects component naming convention by checking if .tsx files in\n * component directories use PascalCase filenames.\n */\nfunction detectComponentNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const componentPaths = new Set(\n structure.directories.filter((d) => d.role === 'components').map((d) => d.path),\n );\n\n const tsxFiles: string[] = [];\n for (const dir of dirs) {\n if (!componentPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n if (f.endsWith('.tsx')) tsxFiles.push(f);\n }\n }\n\n if (tsxFiles.length < 3) return undefined;\n\n const pascalCount = tsxFiles.filter((f) => /^[A-Z]/.test(stripExtension(f))).length;\n const consistency = Math.round((pascalCount / tsxFiles.length) * 100);\n const dominantValue = pascalCount >= tsxFiles.length / 2 ? 'PascalCase' : 'camelCase';\n\n return {\n value: dominantValue,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: tsxFiles.length,\n consistency,\n };\n}\n\n/**\n * Detects hook naming convention by checking if hook files use\n * kebab-case (use-*) or camelCase (useXxx) prefix.\n */\nfunction detectHookNaming(\n dirs: WalkedDirectory[],\n structure: DetectedStructure,\n): DetectedConvention | undefined {\n const hookPaths = new Set(\n structure.directories.filter((d) => d.role === 'hooks').map((d) => d.path),\n );\n\n const hookFiles: string[] = [];\n for (const dir of dirs) {\n if (!hookPaths.has(dir.relativePath)) continue;\n for (const f of dir.sourceFileNames) {\n const bare = stripExtension(f);\n if (bare.startsWith('use-') || /^use[A-Z]/.test(bare)) {\n hookFiles.push(f);\n }\n }\n }\n\n if (hookFiles.length < 3) return undefined;\n\n let kebabCount = 0;\n let camelCount = 0;\n\n for (const filename of hookFiles) {\n const bare = stripExtension(filename);\n if (bare.startsWith('use-')) {\n kebabCount++;\n } else if (/^use[A-Z]/.test(bare)) {\n camelCount++;\n }\n }\n\n const total = hookFiles.length;\n const isDominantKebab = kebabCount >= camelCount;\n const dominantCount = isDominantKebab ? kebabCount : camelCount;\n const consistency = Math.round((dominantCount / total) * 100);\n\n return {\n value: isDominantKebab ? 'use-*' : 'useXxx',\n confidence: confidenceFromConsistency(consistency),\n sampleSize: total,\n consistency,\n };\n}\n\n/** Minimal shape for the tsconfig subset we read. */\ninterface TsConfigSubset {\n compilerOptions?: {\n paths?: Record<string, string[]>;\n };\n}\n\n/**\n * Detects import alias patterns from tsconfig.json paths configuration.\n * Returns undefined if tsconfig.json is missing or has no paths.\n */\nasync function detectImportAlias(projectPath: string): Promise<DetectedConvention | undefined> {\n try {\n const raw = await readFile(join(projectPath, 'tsconfig.json'), 'utf-8');\n const tsconfig = JSON.parse(raw) as TsConfigSubset;\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) return undefined;\n\n const aliases = Object.keys(paths);\n if (aliases.length === 0) return undefined;\n\n return {\n value: aliases.join(','),\n confidence: 'high',\n sampleSize: aliases.length,\n consistency: 100,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Naming convention types for filenames.\n */\nexport type FilenameConvention =\n | 'kebab-case'\n | 'camelCase'\n | 'PascalCase'\n | 'snake_case'\n | 'unknown';\n\nconst PATTERNS = [\n { convention: 'PascalCase', regex: /^[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'camelCase', regex: /^[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*$/ },\n { convention: 'kebab-case', regex: /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/ },\n { convention: 'snake_case', regex: /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/ },\n] as const;\n\n/**\n * Classifies a filename (without extension) into a naming convention.\n *\n * Single lowercase words like 'utils' return 'unknown' because they are\n * ambiguous — they could be kebab-case, snake_case, or camelCase without\n * a disambiguating structural marker.\n *\n * @param filename - The bare filename with no extension (e.g. 'user-profile').\n * @returns The detected naming convention, or 'unknown' if ambiguous.\n */\nexport function classifyFilename(filename: string): FilenameConvention {\n for (const { convention, regex } of PATTERNS) {\n if (regex.test(filename)) return convention;\n }\n return 'unknown';\n}\n","import { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DetectedStack, StackItem } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Extracts the major version number from a semver range string.\n *\n * @param range - A semver range such as `\"^15.0.3\"`, `\"~2.1.0\"`, or `\"3.x\"`.\n * @returns The major version string (e.g. `\"15\"`), or `undefined` if extraction fails.\n */\nexport function extractMajorVersion(range: string): string | undefined {\n const match = range.match(/(\\d+)/);\n return match?.[1];\n}\n\n/** Check whether a file exists at the given path. */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Detection mapping tables\n// ---------------------------------------------------------------------------\n\ninterface FrameworkMapping {\n /** Package name to look for in dependencies. */\n dep: string;\n /** Name to use in the StackItem. */\n name: string;\n /** If set, the dep must NOT be present for this rule to match. */\n excludeDep?: string;\n}\n\nconst FRAMEWORK_MAPPINGS: FrameworkMapping[] = [\n { dep: 'next', name: 'nextjs' },\n { dep: 'expo', name: 'expo' },\n { dep: 'react-native', name: 'react-native', excludeDep: 'expo' },\n { dep: '@sveltejs/kit', name: 'sveltekit' },\n { dep: 'svelte', name: 'svelte' },\n { dep: 'astro', name: 'astro' },\n { dep: 'vue', name: 'vue' },\n { dep: 'react', name: 'react', excludeDep: 'next' },\n];\n\nconst BACKEND_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'express', name: 'express' },\n { dep: 'fastify', name: 'fastify' },\n { dep: 'hono', name: 'hono' },\n { dep: '@supabase/supabase-js', name: 'supabase' },\n { dep: 'firebase', name: 'firebase' },\n { dep: '@prisma/client', name: 'prisma' },\n { dep: 'prisma', name: 'prisma' },\n { dep: 'drizzle-orm', name: 'drizzle' },\n];\n\nconst STYLING_MAPPINGS: Array<{ dep: string; name: string }> = [\n { dep: 'tailwindcss', name: 'tailwindcss' },\n { dep: 'styled-components', name: 'styled-components' },\n { dep: '@emotion/react', name: 'emotion' },\n { dep: 'sass', name: 'sass' },\n];\n\nconst LIBRARY_MAPPINGS: Array<{ deps: string[]; name: string }> = [\n { deps: ['zod'], name: 'zod' },\n { deps: ['@trpc/server', '@trpc/client'], name: 'trpc' },\n { deps: ['@tanstack/react-query'], name: 'react-query' },\n];\n\nconst LOCK_FILE_MAP: Array<{ file: string; name: string }> = [\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'bun.lockb', name: 'bun' },\n { file: 'package-lock.json', name: 'npm' },\n];\n\n// ---------------------------------------------------------------------------\n// Main detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the technology stack of a project by reading its package.json\n * and checking for lock files and configuration files.\n *\n * When `additionalDeps` is provided, they are merged as a base layer\n * beneath the package's own deps. This allows monorepo root-level deps\n * (e.g. typescript, eslint) to be visible during per-package scanning.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param additionalDeps - Optional base dependencies merged under package deps.\n * @returns The detected technology stack.\n */\nexport async function detectStack(\n projectPath: string,\n additionalDeps?: Record<string, string>,\n): Promise<DetectedStack> {\n const pkg = await readPackageJson(projectPath);\n const allDeps: Record<string, string> = {\n ...additionalDeps,\n ...pkg?.dependencies,\n ...pkg?.devDependencies,\n };\n\n const framework = detectFramework(allDeps);\n const language = await detectLanguage(projectPath, allDeps);\n const styling = detectFirst(allDeps, STYLING_MAPPINGS);\n const backend = detectFirst(allDeps, BACKEND_MAPPINGS);\n const packageManager = await detectPackageManager(projectPath);\n const linter = detectLinter(allDeps);\n const testRunner = detectTestRunner(allDeps);\n const libraries = detectLibraries(allDeps);\n\n return {\n ...(framework && { framework }),\n language,\n ...(styling && { styling }),\n ...(backend && { backend }),\n packageManager,\n ...(linter && { linter }),\n ...(testRunner && { testRunner }),\n libraries,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Individual detectors\n// ---------------------------------------------------------------------------\n\nfunction detectFramework(allDeps: Record<string, string>): StackItem | undefined {\n for (const mapping of FRAMEWORK_MAPPINGS) {\n if (!(mapping.dep in allDeps)) continue;\n if (mapping.excludeDep && mapping.excludeDep in allDeps) continue;\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n return undefined;\n}\n\nasync function detectLanguage(\n projectPath: string,\n allDeps: Record<string, string>,\n): Promise<StackItem> {\n if ('typescript' in allDeps) {\n return {\n name: 'typescript',\n version: extractMajorVersion(allDeps.typescript),\n };\n }\n if (await fileExists(join(projectPath, 'tsconfig.json'))) {\n return { name: 'typescript' };\n }\n return { name: 'javascript' };\n}\n\nfunction detectFirst(\n allDeps: Record<string, string>,\n mappings: Array<{ dep: string; name: string }>,\n): StackItem | undefined {\n for (const mapping of mappings) {\n if (mapping.dep in allDeps) {\n return {\n name: mapping.name,\n version: extractMajorVersion(allDeps[mapping.dep]),\n };\n }\n }\n return undefined;\n}\n\nasync function detectPackageManager(projectPath: string): Promise<StackItem> {\n for (const entry of LOCK_FILE_MAP) {\n if (await fileExists(join(projectPath, entry.file))) {\n return { name: entry.name };\n }\n }\n return { name: 'npm' };\n}\n\nfunction detectLinter(allDeps: Record<string, string>): StackItem | undefined {\n if ('eslint' in allDeps) {\n return { name: 'eslint', version: extractMajorVersion(allDeps.eslint) };\n }\n if ('@biomejs/biome' in allDeps) {\n return {\n name: 'biome',\n version: extractMajorVersion(allDeps['@biomejs/biome']),\n };\n }\n return undefined;\n}\n\nfunction detectTestRunner(allDeps: Record<string, string>): StackItem | undefined {\n if ('vitest' in allDeps) {\n return { name: 'vitest', version: extractMajorVersion(allDeps.vitest) };\n }\n if ('jest' in allDeps) {\n return { name: 'jest', version: extractMajorVersion(allDeps.jest) };\n }\n return undefined;\n}\n\nfunction detectLibraries(allDeps: Record<string, string>): StackItem[] {\n const libs: StackItem[] = [];\n for (const mapping of LIBRARY_MAPPINGS) {\n const found = mapping.deps.find((dep) => dep in allDeps);\n if (found) {\n libs.push({\n name: mapping.name,\n version: extractMajorVersion(allDeps[found]),\n });\n }\n }\n return libs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Minimal interface for the fields we read from package.json.\n */\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Safely reads and parses a package.json file from the given directory.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @returns Parsed package.json contents, or `null` if the file doesn't exist or is invalid JSON.\n */\nexport async function readPackageJson(projectPath: string): Promise<PackageJson | null> {\n try {\n const raw = await readFile(join(projectPath, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n","import type { DetectedConvention, DetectedStructure } from '@viberails/types';\nimport { confidenceFromConsistency } from '@viberails/types';\nimport { classifyDirectory } from './utils/classify-directory.js';\nimport type { WalkedDirectory } from './utils/walk-directory.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Detects the directory structure and organization of a project.\n *\n * Classifies directories by role, detects whether a src/ directory is in use,\n * and identifies test file patterns.\n *\n * @param projectPath - Absolute path to the project root directory.\n * @param dirs - Pre-walked directory list. If not provided, walks the directory tree.\n * @returns The detected directory structure.\n */\nexport async function detectStructure(\n projectPath: string,\n dirs?: WalkedDirectory[],\n): Promise<DetectedStructure> {\n if (!dirs) {\n dirs = await walkDirectory(projectPath, 4);\n }\n\n // Detect srcDir\n const hasSrcDir = dirs.some((d) => d.relativePath === 'src' || d.relativePath.startsWith('src/'));\n const srcDir = hasSrcDir ? 'src' : undefined;\n\n // Classify directories\n const directories = dirs.map((d) => classifyDirectory(d)).filter((d) => d !== null);\n\n // Detect test pattern\n const testPattern = detectTestPattern(dirs);\n\n return {\n ...(srcDir !== undefined && { srcDir }),\n directories,\n ...(testPattern !== undefined && { testPattern }),\n };\n}\n\n/**\n * Detects the dominant test file naming pattern from all source files.\n * Returns undefined if fewer than 3 test files are found.\n */\nfunction detectTestPattern(\n dirs: Array<{ sourceFileNames: string[] }>,\n): DetectedConvention<string> | undefined {\n const allFiles = dirs.flatMap((d) => d.sourceFileNames);\n const testFiles = allFiles.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n\n if (testFiles.length < 3) return undefined;\n\n const dotTestCount = testFiles.filter((f) => f.includes('.test.')).length;\n const dotSpecCount = testFiles.filter((f) => f.includes('.spec.')).length;\n\n const isDotTest = dotTestCount >= dotSpecCount;\n const dominantSep = isDotTest ? '.test.' : '.spec.';\n const dominantCount = isDotTest ? dotTestCount : dotSpecCount;\n const consistency = Math.round((dominantCount / testFiles.length) * 100);\n\n // Find most common extension among dominant test files\n const extCounts = new Map<string, number>();\n for (const f of testFiles.filter((f) => f.includes(dominantSep))) {\n const ext = f.substring(f.lastIndexOf('.'));\n extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n const topExt = [...extCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? '.ts';\n\n const sep = isDotTest ? 'test' : 'spec';\n\n return {\n value: `*.${sep}${topExt}`,\n confidence: confidenceFromConsistency(consistency),\n sampleSize: testFiles.length,\n consistency,\n };\n}\n","import type { Confidence, DirectoryRole } from '@viberails/types';\nimport type { WalkedDirectory } from './walk-directory.js';\n\nexport interface ClassifiedDirectory {\n path: string;\n role: DirectoryRole;\n fileCount: number;\n confidence: Confidence;\n}\n\ninterface RolePattern {\n role: DirectoryRole;\n pathPatterns: string[];\n}\n\nconst ROLE_PATTERNS: RolePattern[] = [\n { role: 'pages', pathPatterns: ['src/app', 'src/pages', 'app', 'pages'] },\n { role: 'components', pathPatterns: ['src/components', 'components'] },\n { role: 'hooks', pathPatterns: ['src/hooks', 'hooks'] },\n {\n role: 'utils',\n pathPatterns: ['src/lib', 'src/utils', 'src/helpers', 'lib', 'utils', 'helpers'],\n },\n { role: 'types', pathPatterns: ['src/types', 'types', 'src/@types', '@types'] },\n { role: 'tests', pathPatterns: ['__tests__', 'tests', 'test', 'src/__tests__', 'src/tests'] },\n { role: 'styles', pathPatterns: ['src/styles', 'styles', 'src/css', 'css'] },\n { role: 'api', pathPatterns: ['src/api', 'api', 'src/app/api', 'app/api'] },\n { role: 'config', pathPatterns: ['config', 'src/config'] },\n];\n\n/**\n * Classifies a directory by its role in the project.\n *\n * @param dir - A walked directory entry.\n * @returns Classified directory info, or null if the directory should be skipped.\n */\nexport function classifyDirectory(dir: WalkedDirectory): ClassifiedDirectory | null {\n // Try name-based matching first\n const nameMatch = matchByName(dir.relativePath);\n if (nameMatch) {\n const confidence: Confidence = dir.sourceFileCount > 0 ? 'high' : 'low';\n return {\n path: dir.relativePath,\n role: nameMatch,\n fileCount: dir.sourceFileCount,\n confidence,\n };\n }\n\n // Skip directories with no source files if no name match\n if (dir.sourceFileCount === 0) return null;\n\n // Content-based heuristics\n const contentRole = inferFromContent(dir);\n if (contentRole) return contentRole;\n\n // Has source files but no classification\n return {\n path: dir.relativePath,\n role: 'unknown',\n fileCount: dir.sourceFileCount,\n confidence: 'low',\n };\n}\n\n/**\n * Matches a relative path against known role patterns.\n * Supports suffix matching so monorepo paths like `apps/web/lib`\n * match patterns like `lib`.\n * Returns the role if matched, or null.\n */\nfunction matchByName(relativePath: string): DirectoryRole | null {\n for (const { role, pathPatterns } of ROLE_PATTERNS) {\n for (const pattern of pathPatterns) {\n if (relativePath === pattern || relativePath.endsWith(`/${pattern}`)) {\n return role;\n }\n }\n }\n return null;\n}\n\n/**\n * Infers directory role from file contents/names.\n */\nfunction inferFromContent(dir: WalkedDirectory): ClassifiedDirectory | null {\n const { sourceFileNames, sourceFileCount } = dir;\n\n // Check for hook files (use-* kebab or useXxx camelCase prefix)\n // Require at least 2 matching files to avoid misclassifying directories\n // with a single hook utility (e.g. lib/ with one useXxx file)\n const hookFiles = sourceFileNames.filter((f) => {\n const name = f.split('.')[0];\n return name.startsWith('use-') || /^use[A-Z]/.test(name);\n });\n if (hookFiles.length >= 2 && hookFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'hooks',\n fileCount: sourceFileCount,\n confidence: hookFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n // Check for test files — require at least 2 to avoid false positives\n const testFiles = sourceFileNames.filter((f) => f.includes('.test.') || f.includes('.spec.'));\n if (testFiles.length >= 2 && testFiles.length / sourceFileCount >= 0.5) {\n return {\n path: dir.relativePath,\n role: 'tests',\n fileCount: sourceFileCount,\n confidence: testFiles.length / sourceFileCount >= 0.9 ? 'high' : 'medium',\n };\n }\n\n return null;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { DetectedWorkspace, WorkspacePackage } from '@viberails/types';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Detects workspace configuration for monorepo projects.\n *\n * Checks for `pnpm-workspace.yaml` first, then falls back to\n * `package.json` `workspaces` field. Returns `undefined` for\n * single-package projects.\n *\n * @param projectRoot - Absolute path to the project root.\n * @returns Detected workspace info, or `undefined` if not a monorepo.\n */\nexport async function detectWorkspace(projectRoot: string): Promise<DetectedWorkspace | undefined> {\n const patterns = await readWorkspacePatterns(projectRoot);\n if (!patterns || patterns.length === 0) return undefined;\n\n const packageDirs = await resolvePatterns(projectRoot, patterns);\n const packages = await resolvePackages(projectRoot, packageDirs);\n\n if (packages.length === 0) return undefined;\n\n // Resolve internal deps: check if any dependency name matches a workspace package\n const packageNames = new Set(packages.map((p) => p.name));\n for (const pkg of packages) {\n pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));\n }\n\n return { patterns, packages };\n}\n\n/**\n * Reads workspace glob patterns from pnpm-workspace.yaml or package.json.\n */\nasync function readWorkspacePatterns(projectRoot: string): Promise<string[] | undefined> {\n // Try pnpm-workspace.yaml first\n try {\n const yaml = await readFile(join(projectRoot, 'pnpm-workspace.yaml'), 'utf-8');\n return parsePnpmWorkspaceYaml(yaml);\n } catch {\n // Not found, try package.json\n }\n\n // Fall back to package.json workspaces field\n const pkg = await readPackageJson(projectRoot);\n if (!pkg) return undefined;\n\n const raw = pkg as Record<string, unknown>;\n const workspaces = raw.workspaces;\n\n if (Array.isArray(workspaces)) {\n return workspaces.filter((w): w is string => typeof w === 'string');\n }\n\n // Handle { packages: [...] } format\n if (workspaces && typeof workspaces === 'object' && 'packages' in workspaces) {\n const nested = (workspaces as { packages: unknown }).packages;\n if (Array.isArray(nested)) {\n return nested.filter((w): w is string => typeof w === 'string');\n }\n }\n\n return undefined;\n}\n\n/**\n * Parses workspace patterns from pnpm-workspace.yaml content.\n * Handles the common format: `packages:\\n - 'packages/*'`\n */\nfunction parsePnpmWorkspaceYaml(content: string): string[] {\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (trimmed === 'packages:') {\n inPackages = true;\n continue;\n }\n\n // Stop at next top-level key\n if (inPackages && trimmed.length > 0 && !trimmed.startsWith('-')) {\n break;\n }\n\n if (inPackages && trimmed.startsWith('-')) {\n // Extract the pattern, stripping quotes and leading dash\n const value = trimmed\n .slice(1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n if (value) patterns.push(value);\n }\n }\n\n return patterns;\n}\n\n/**\n * Resolves workspace glob patterns to actual directory paths.\n * Supports simple `*` wildcard matching (e.g. `packages/*`).\n */\nasync function resolvePatterns(projectRoot: string, patterns: string[]): Promise<string[]> {\n const dirs: string[] = [];\n\n for (const pattern of patterns) {\n if (pattern.endsWith('/*')) {\n // Simple wildcard: list children of the parent directory\n const parent = join(projectRoot, pattern.slice(0, -2));\n try {\n const entries = await readdir(parent, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n dirs.push(join(parent, entry.name));\n }\n }\n } catch {\n // Parent directory doesn't exist, skip\n }\n } else {\n // Literal path\n dirs.push(join(projectRoot, pattern));\n }\n }\n\n return dirs;\n}\n\n/**\n * Resolves workspace directories to WorkspacePackage objects.\n * Skips directories without a valid package.json.\n */\nasync function resolvePackages(projectRoot: string, dirs: string[]): Promise<WorkspacePackage[]> {\n const packages: WorkspacePackage[] = [];\n\n for (const dir of dirs) {\n const pkg = await readPackageJson(dir);\n if (!pkg?.name) continue;\n\n // Collect all dependency names as potential internal deps\n const allDeps = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n\n packages.push({\n name: pkg.name,\n path: dir,\n relativePath: relative(projectRoot, dir),\n internalDeps: allDeps,\n });\n }\n\n return packages;\n}\n","import { resolve } from 'node:path';\nimport type { PackageScanResult } from '@viberails/types';\nimport { computeStatistics } from './compute-statistics.js';\nimport { detectConventions } from './detect-conventions.js';\nimport { detectStack } from './detect-stack.js';\nimport { detectStructure } from './detect-structure.js';\nimport { walkDirectory } from './utils/walk-directory.js';\n\n/**\n * Scans a single package directory and returns per-package scan results.\n *\n * Reuses all existing detector functions scoped to the package directory.\n * In monorepos, `rootDeps` provides root-level dependencies (e.g. typescript,\n * eslint) as a base layer — package-specific deps overlay on top.\n *\n * @param packagePath - Absolute path to the package directory.\n * @param name - Package name from package.json.\n * @param relativePath - Path relative to workspace root (empty string for single-package).\n * @param rootDeps - Optional root-level dependencies merged as a base layer.\n * @returns Per-package scan result.\n */\nexport async function scanPackage(\n packagePath: string,\n name: string,\n relativePath: string,\n rootDeps?: Record<string, string>,\n): Promise<PackageScanResult> {\n const root = resolve(packagePath);\n const dirs = await walkDirectory(root, 4);\n\n const [stack, structure, statistics] = await Promise.all([\n detectStack(root, rootDeps),\n detectStructure(root, dirs),\n computeStatistics(root, dirs),\n ]);\n\n const conventions = await detectConventions(root, structure, dirs);\n\n return {\n name,\n root,\n relativePath,\n stack,\n structure,\n conventions,\n statistics,\n };\n}\n","import { stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\nimport type { ScanResult } from '@viberails/types';\nimport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nimport { detectWorkspace } from './detect-workspace.js';\nimport { scanPackage } from './scan-package.js';\nimport { readPackageJson } from './utils/read-package-json.js';\n\n/**\n * Options for the scan function.\n */\nexport type ScanOptions = {};\n\n/**\n * Scans a project directory and returns a comprehensive analysis of its\n * stack, structure, conventions, and statistics.\n *\n * For monorepos, each workspace package is scanned independently and\n * results are aggregated. For single-package projects, the root is\n * scanned as a single package.\n *\n * @param projectPath - Absolute or relative path to the project root.\n * @param options - Optional scan configuration.\n * @returns Complete scan result for the project.\n * @throws If the project path does not exist or is not a directory.\n */\nexport async function scan(projectPath: string, _options?: ScanOptions): Promise<ScanResult> {\n const root = resolve(projectPath);\n\n // Validate that the path exists and is a directory\n try {\n const st = await stat(root);\n if (!st.isDirectory()) {\n throw new Error(`Project path is not a directory: ${root}`);\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith('Project path is not')) {\n throw err;\n }\n throw new Error(`Project path does not exist: ${root}`);\n }\n\n const workspace = await detectWorkspace(root);\n\n if (workspace && workspace.packages.length > 0) {\n // Read root deps for sharing with workspace packages\n const rootPkg = await readPackageJson(root);\n const rootDeps: Record<string, string> = {\n ...rootPkg?.dependencies,\n ...rootPkg?.devDependencies,\n };\n\n // Scan each workspace package in parallel\n const packages = await Promise.all(\n workspace.packages.map((wp) => scanPackage(wp.path, wp.name, wp.relativePath, rootDeps)),\n );\n\n return {\n root,\n stack: aggregateStacks(packages),\n structure: aggregateStructures(packages),\n conventions: aggregateConventions(packages),\n statistics: aggregateStatistics(packages),\n workspace,\n packages,\n };\n }\n\n // Single-package project\n const rootPkg = await readPackageJson(root);\n const name = rootPkg?.name ?? basename(root);\n const pkg = await scanPackage(root, name, '');\n\n return {\n root,\n stack: pkg.stack,\n structure: pkg.structure,\n conventions: pkg.conventions,\n statistics: pkg.statistics,\n packages: [pkg],\n };\n}\n","export const VERSION = '0.1.0';\n\nexport {\n aggregateConventions,\n aggregateStacks,\n aggregateStatistics,\n aggregateStructures,\n} from './aggregate.js';\nexport { computeStatistics } from './compute-statistics.js';\nexport { detectConventions } from './detect-conventions.js';\nexport { detectStack, extractMajorVersion } from './detect-stack.js';\nexport { detectStructure } from './detect-structure.js';\nexport { detectWorkspace } from './detect-workspace.js';\nexport { scanPackage } from './scan-package.js';\nexport type { ScanOptions } from './scan.js';\nexport { scan } from './scan.js';\nexport type { PackageJson } from './utils/read-package-json.js';\nexport { readPackageJson } from './utils/read-package-json.js';\nexport type { WalkedDirectory } from './utils/walk-directory.js';\nexport { walkDirectory } from './utils/walk-directory.js';\n"],"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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viberails/scanner",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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.
|
|
28
|
+
"@viberails/types": "0.2.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^25.3.5"
|