@omfalos/mokosh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +139 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +139 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +1135 -0
- package/dist/index.d.ts +1135 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +26 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcp.d.mts +15 -0
- package/dist/mcp.d.ts +15 -0
- package/dist/mcp.js +28 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mcp.mjs +28 -0
- package/dist/mcp.mjs.map +1 -0
- package/package.json +103 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/parser/classify.ts","../src/const.ts","../src/coverage.ts","../src/exporters/mermaid.ts","../src/graph/api-surface.ts","../src/graph/call-graph/index.ts","../src/graph/change-impact-cache.ts","../src/graph/features/index.ts","../src/graph/features/feature-graph.ts","../src/graph/analyzer.ts","../src/graph/model.ts","../src/graph/responsibility/infer-role.ts","../src/graph/responsibility/index.ts","../src/graph/symbol-traversal.ts","../src/graph/type-graph.ts","../src/graph/workspace/index.ts","../src/graph/workspace/detectors/npm.ts","../src/graph/workspace/shared.ts","../src/graph/workspace/fs-utils.ts","../src/graph/workspace/detectors/nx.ts","../src/graph/workspace/detectors/pnpm.ts","../src/graph/workspace/detectors/turborepo.ts","../src/graph/workspace/detectors/yarn.ts","../src/graph/workspace/registry.ts","../src/parser/registry.ts","../src/query/matchers.ts","../src/query/filter.ts","../src/query/parser.ts","../src/tags/applier.ts","../src/tags/strategies/index.ts","../src/tags/strategies/cypress.ts","../src/tags/strategies/ts-ast-utils.ts","../src/tags/strategies/gherkin.ts","../src/tags/strategies/glob.ts","../src/tags/strategies/go.ts","../src/tags/strategies/jest.ts","../src/tags/strategies/playwright.ts","../src/tags/strategies/pytest.ts","../src/tags/strategies/vitest.ts","../src/tags/identifier.ts","../src/graph/builder.ts","../src/git.ts","../src/parser/lockfile.ts","../src/parser/file-type.ts","../src/parser/lang/coffee.ts","../src/parser/lang/gherkin.ts","../src/parser/lang/go.ts","../src/parser/lang/ls.ts","../src/parser/utils.ts","../src/parser/lang/lua.ts","../src/parser/lang/python.ts","../src/parser/lang/typescript.ts","../src/parser/complexity.ts","../src/parser/tagging/index.ts","../src/parser/style/barrel.ts","../src/parser/style/css.ts","../src/parser/style/scss.ts","../src/parser/style/stylus.ts","../src/parser/style/index.ts","../src/parser.ts","../src/graph/enrichment.ts","../src/graph/resolver.ts","../src/graph/lang-resolvers/go.ts","../src/graph/lang-resolvers/lua.ts","../src/graph/lang-resolvers/python.ts","../src/tags/proposer.ts","../src/index.ts","../src/cli/args.ts","../src/cli/const.ts","../src/cli/commands/utils.ts","../src/cli/commands/affected-tests.ts","../src/cli/commands/api-surface.ts","../src/cli/commands/apply-tags.ts","../src/cli/commands/call-graph.ts","../src/cli/commands/callers.ts","../src/cli/commands/check-cycles.ts","../src/cli/commands/detect-features.ts","../src/cli/commands/feature-graph.ts","../src/cli/commands/find-uncovered.ts","../src/cli/commands/find-unused.ts","../src/cli/commands/graph-output.ts","../src/cli/commands/module-responsibility.ts","../src/cli/commands/propose-tags.ts","../src/cli/commands/type-graph.ts","../src/cli/config.ts","../src/cli/graph-loader.ts","../src/cli/help.ts","../src/cli/runner.ts","../src/cli.ts"],"sourcesContent":["/** Loads and applies mokosh.config.* files, activating user-defined matchers, patterns, and thresholds. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n setBarrelThreshold,\n} from \"./parser/classify\";\nimport type { TagFramework } from \"./tags/strategies\";\n\n/**\n * @description Top-level configuration for mokosh. All fields are optional; unset fields\n * fall back to built-in defaults. Load this object via `loadMokoshConfig`, then activate\n * it with `applyConfig` before calling `createImportMap`.\n */\nexport interface MokoshConfig {\n /** Additional directories to skip when scanning (merged with built-in defaults). */\n ignoreDirs?: string[];\n /** Additional file extensions to scan (merged with built-in defaults). */\n extensions?: string[];\n /** Override the default cache path (`mokosh-cache/graph.json`). */\n cachePath?: string;\n /** Default entry points used when none are provided on the CLI. */\n entryPoints?: string[];\n /** Additional basename substrings that mark a file as `\"config\"` category. */\n configMatchers?: string[];\n /** Additional basename substrings that mark a file as `\"test\"` category (e.g. `\".unit.\"`). */\n testPatterns?: string[];\n /** Additional import specifiers that indicate a test file (e.g. `\"@my-org/test-utils\"`). */\n testLibraries?: string[];\n /** Ratio of export-statements to total statements required for `\"barrel\"` classification. Default: `0.8`. */\n barrelThreshold?: number;\n /** When true, enriches each node with `commitCount90d` and `lastAuthor` via git log. Only fetched for new/modified files. */\n gitStats?: boolean;\n /**\n * Tag-applier configuration for `--apply-tags`. Controls which format is written into\n * test files. Defaults to `{ framework: \"vitest\" }` when unset.\n */\n tagApplier?: {\n /**\n * Fallback test framework whose tag format to use for TS/JS files. Each file's actual\n * framework is auto-detected from its imports (`@playwright/test`, `cypress`,\n * `@jest/globals`, `vitest`), so a single repo can mix frameworks and each file is tagged\n * in its own native format. This value is only used when a file has no detectable\n * framework import (e.g. `globals: true` configs with no explicit import).\n * - `\"vitest\"` — injects `{ tags: [...] }` in describe/test/it options (default)\n * - `\"playwright\"` — injects `{ tag: [\"@name\"] }` with `@` prefix convention\n * - `\"cypress\"` — injects `{ tags: [\"@name\"] }` for use with `@cypress/grep`\n * - `\"jest\"` — writes a `/** @group name *\\/` docblock for use with `jest-runner-groups`\n */\n framework?: TagFramework;\n /**\n * Path-glob pattern (project-relative, e.g. `\"tests/e2e/**\"`) to fallback framework. Checked\n * in object key order, first match wins, before falling back further to `framework`. Only\n * consulted when a file's own imports don't reveal a framework — lets different directories\n * default to different frameworks (e.g. e2e tests using Playwright globals, unit tests using\n * Jest globals) instead of sharing one project-wide default.\n */\n frameworkOverrides?: Record<string, TagFramework>;\n };\n /** Path to the Istanbul/v8 `coverage-summary.json` file, relative to the project root. When set, `coveragePct` is populated on each node after the graph is built. */\n coverageReportPath?: string;\n /** Default line-coverage threshold (0–100) used by `find_uncovered`. Defaults to `80` when not specified. */\n coverageThreshold?: number;\n}\n\nconst CONFIG_FILENAMES = [\"mokosh.config.js\", \"mokosh.config.cjs\", \"mokosh.config.json\"];\n\n/**\n * @description Loads a mokosh config file, probing standard filenames in `rootDirOrPath` or reading an explicit path when `isExplicitPath` is true.\n * JS/CJS configs may export a plain object or a factory function; the MCP server passes `allowJs: false` to prevent arbitrary code execution.\n * @param {string} rootDirOrPath - Directory to probe for standard config filenames, or absolute path to the config file when `isExplicitPath` is true.\n * @param {{ allowJs?: boolean; isExplicitPath?: boolean }} options - `allowJs` (default `true`) controls whether `.js`/`.cjs` files are loaded; `isExplicitPath` treats the first arg as a direct file path.\n * @returns {MokoshConfig} The parsed config, or an empty object when no config file is found.\n */\nexport function loadMokoshConfig(\n rootDirOrPath: string,\n { allowJs = true, isExplicitPath = false }: { allowJs?: boolean; isExplicitPath?: boolean } = {},\n): MokoshConfig {\n if (isExplicitPath) {\n const filePath = path.resolve(rootDirOrPath);\n if (!fs.existsSync(filePath)) return {};\n if (filePath.endsWith(\".json\")) return readJsonConfig(filePath);\n if (allowJs) return readJsConfig(filePath);\n return {};\n }\n\n for (const filename of CONFIG_FILENAMES) {\n const filePath = path.resolve(rootDirOrPath, filename);\n if (!fs.existsSync(filePath)) continue;\n if (filename.endsWith(\".json\")) return readJsonConfig(filePath);\n if (allowJs) return readJsConfig(filePath);\n }\n\n return {};\n}\n\n/** Parses a JSON config file into a `MokoshConfig`. */\nfunction readJsonConfig(filePath: string): MokoshConfig {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as MokoshConfig;\n}\n\n/**\n * @description Requires a JS/CJS config file and normalises its export.\n * Unwraps `.default` for ESM-interop, and calls the export if it is a factory function.\n * @param {string} filePath - Absolute path to the `.js` or `.cjs` config file\n * @returns {MokoshConfig} The resolved config object\n */\nfunction readJsConfig(filePath: string): MokoshConfig {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n let exported = require(filePath) as MokoshConfig | ((defaults: MokoshConfig) => MokoshConfig);\n if (exported && typeof exported === \"object\" && \"default\" in exported) {\n exported = (exported as { default: typeof exported }).default;\n }\n return typeof exported === \"function\" ? exported({}) : (exported as MokoshConfig);\n}\n\n/**\n * @description Applies a `MokoshConfig` to the global registries that control classification and scanning.\n * Call this after `loadMokoshConfig` and before `createImportMap`.\n * @param {MokoshConfig} config - The loaded config whose matchers, patterns, libraries, and thresholds are registered.\n */\nexport function applyConfig(config: MokoshConfig): void {\n for (const pattern of config.configMatchers ?? []) {\n registerConfigMatcher(pattern);\n }\n for (const pattern of config.testPatterns ?? []) {\n registerTestPattern(pattern);\n }\n for (const lib of config.testLibraries ?? []) {\n registerTestLibrary(lib);\n }\n if (config.barrelThreshold !== undefined) {\n setBarrelThreshold(config.barrelThreshold);\n }\n}\n","/** A config matcher: substring, regex, or predicate tested against the lowercase basename. */\nexport type ConfigMatcher = string | RegExp | ((baseName: string) => boolean);\n\nconst builtinConfigMatchers: ConfigMatcher[] = [\n \".config.\",\n \"biome.json\",\n \"tsconfig.json\",\n \"package.json\",\n \".prettierrc\",\n \".eslintrc\",\n];\n\nconst userConfigMatchers: ConfigMatcher[] = [];\n\n/**\n * @description Registers a custom config-file matcher used when categorising nodes.\n * Accepts a substring, regex, or predicate tested against the lowercase basename.\n * Call this before running `createImportMap` — e.g. in a `mokosh.config.ts`.\n * @param matcher - A substring, `RegExp`, or predicate function tested against the lowercase file basename.\n * @example\n * // Match any file whose basename contains \".myconfig.\"\n * registerConfigMatcher(\".myconfig.\");\n *\n * // Match via regex\n * registerConfigMatcher(/^vite\\.config\\./);\n *\n * // Match via predicate\n * registerConfigMatcher((name) => name.startsWith(\"jest.config\"));\n */\nexport function registerConfigMatcher(matcher: ConfigMatcher): void {\n userConfigMatchers.push(matcher);\n}\n\n/**\n * @description Tests a lowercase basename against all built-in and user-registered config matchers.\n * @param baseName - The lowercase file basename to test, e.g. `\"tsconfig.json\"`.\n * @returns `true` if any registered matcher matches the basename.\n */\nexport function isConfigFile(baseName: string): boolean {\n return [...builtinConfigMatchers, ...userConfigMatchers].some((matcher) => {\n if (typeof matcher === \"string\") return baseName.includes(matcher);\n if (matcher instanceof RegExp) return matcher.test(baseName);\n return matcher(baseName);\n });\n}\n\n// ─── Test-pattern registry ────────────────────────────────────────────────────\n\nconst builtinTestPatterns: string[] = [\".test.\", \".spec.\", \"-test.\", \"-spec.\"];\nconst userTestPatterns: string[] = [];\n\n/**\n * @description Registers an additional basename substring that marks a file as a test.\n * @param pattern - A substring matched against the file basename, e.g. `\".unit.\"`.\n */\nexport function registerTestPattern(pattern: string): void {\n userTestPatterns.push(pattern);\n}\n\n/**\n * @description Returns all test-file basename patterns (built-in + user-registered).\n * @returns Combined array of substring patterns used to identify test files by basename.\n */\nexport function getTestPatterns(): string[] {\n return [...builtinTestPatterns, ...userTestPatterns];\n}\n\n// ─── Testing-library registry ─────────────────────────────────────────────────\n\nconst builtinTestLibraries: string[] = [\n \"jest\",\n \"vitest\",\n \"playwright\",\n \"cypress\",\n \"@testing-library/\",\n];\nconst userTestLibraries: string[] = [];\n\n/**\n * @description Registers an additional import specifier that indicates a test file.\n * @param lib - An import specifier substring, e.g. `\"@my-org/test-utils\"`.\n */\nexport function registerTestLibrary(lib: string): void {\n userTestLibraries.push(lib);\n}\n\n/**\n * @description Returns all testing-library import prefixes (built-in + user-registered).\n * @returns Combined array of import specifier substrings used to detect test files by their imports.\n */\nexport function getTestLibraries(): string[] {\n return [...builtinTestLibraries, ...userTestLibraries];\n}\n\n// ─── Barrel-threshold registry ────────────────────────────────────────────────\n\nlet currentBarrelThreshold = 0.8;\n\n/**\n * @description Sets the minimum ratio of export-statements to total statements required\n * to classify a file as a barrel. Default is `0.8` (80%).\n * @param threshold - A value between 0 and 1; files where exports exceed this fraction of all statements are classified as barrels.\n */\nexport function setBarrelThreshold(threshold: number): void {\n currentBarrelThreshold = threshold;\n}\n\n/**\n * @description Returns the current barrel-detection threshold.\n * @returns The ratio (0–1) above which a file is classified as a barrel.\n */\nexport function getBarrelThreshold(): number {\n return currentBarrelThreshold;\n}\n","export const DEFAULT_IGNORE_DIRS: readonly string[] = [\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n];\n\nexport const DEFAULT_EXTENSIONS: readonly string[] = [\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".css\",\n \".scss\",\n \".sass\",\n \".less\",\n \".styl\",\n \".coffee\",\n \".ls\",\n \".lua\",\n \".py\",\n \".go\",\n \".feature\",\n];\n\nexport interface ScanOptions {\n /** Replaces the default ignore-dir list. Use `additionalIgnoreDirs` to extend instead. */\n ignoreDirs?: string[];\n /** Replaces the default extension list. Use `additionalExtensions` to extend instead. */\n extensions?: string[];\n /** Merged with `DEFAULT_IGNORE_DIRS` (additive). */\n additionalIgnoreDirs?: string[];\n /** Merged with `DEFAULT_EXTENSIONS` (additive). */\n additionalExtensions?: string[];\n}","/** Reads Istanbul/v8 coverage-summary.json and returns a map of file paths to line-coverage percentages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\ninterface CoverageSummaryEntry {\n lines?: { pct?: number };\n [key: string]: unknown;\n}\n\n/**\n * @description Reads an Istanbul/v8 `coverage-summary.json` file and returns a map of\n * project-relative file paths to their line-coverage percentage (0–100).\n * Returns an empty map when the file is missing, unreadable, or malformed — the\n * caller can always proceed safely with no coverage data.\n * @param rootDir - Absolute path to the project root; used to make paths relative.\n * @param reportPath - Path to the coverage summary JSON, relative to `rootDir`.\n * @returns A map of `relativePath → lineCoveragePct`.\n */\nexport function loadCoverageMap(rootDir: string, reportPath: string): Map<string, number> {\n const absoluteReport = path.resolve(rootDir, reportPath);\n try {\n const raw = fs.readFileSync(absoluteReport, \"utf-8\");\n const summary = JSON.parse(raw) as Record<string, CoverageSummaryEntry>;\n const map = new Map<string, number>();\n for (const [absPath, entry] of Object.entries(summary)) {\n if (absPath === \"total\") continue;\n const pct = entry?.lines?.pct;\n if (typeof pct !== \"number\") continue;\n const relative = path.relative(rootDir, absPath);\n map.set(relative, pct);\n }\n return map;\n } catch {\n return new Map();\n }\n}\n","/** GraphExporter implementation that renders dependency graphs as Mermaid flowchart diagrams. */\nimport type { Graph } from \"../graph\";\nimport type { GraphExporter } from \"./types\";\n\n/**\n * @description GraphExporter implementation that renders dependency graphs as Mermaid diagrams.\n * Use this directly or pass it anywhere a GraphExporter is accepted.\n */\nexport const MermaidExporter: GraphExporter = {\n /**\n * @description Serializes the dependency graph into a Mermaid `graph TD` diagram,\n * rendering import edges as arrows and style imports with a labelled edge variant.\n * @param graph - The dependency graph whose nodes and edges to serialize.\n * @returns A Mermaid diagram string starting with `graph TD`.\n */\n serialize(graph: Graph): string {\n const lines: string[] = [\"graph TD\"];\n const visitedEdges = new Set<string>();\n\n for (const node of graph.nodes.values()) {\n const nodeLabel = `\"${node.path}\"`;\n for (const imp of node.imports) {\n if (!imp.toPath) continue;\n const targetLabel = `\"${imp.toPath}\"`;\n const edgeKey = `${node.path} -> ${imp.toPath}`;\n\n if (!visitedEdges.has(edgeKey)) {\n const edgeStyle = imp.isStyle ? \"-- styles -->\" : \"-->\";\n lines.push(` ${nodeLabel} ${edgeStyle} ${targetLabel}`);\n visitedEdges.add(edgeKey);\n }\n }\n }\n return lines.join(\"\\n\");\n },\n};\n\n/**\n * @description Convenience wrapper around MermaidExporter.serialize.\n * @param graph - The dependency graph to render.\n * @returns A Mermaid `graph TD` diagram string.\n */\nexport function toMermaid(graph: Graph): string {\n return MermaidExporter.serialize(graph);\n}\n","/** Detects entry-point files and builds an API surface describing all public exports reachable from them. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { ExportedSymbol } from \"../types/node\";\nimport type { Graph } from \"./model\";\n\n/**\n * Coarse kind of a public export derived from its type signature prefix.\n * Used to distinguish runtime values from type-only exports without parsing the full signature.\n */\nexport type ExportKind =\n | \"function\"\n | \"class\"\n | \"interface\"\n | \"type\"\n | \"enum\"\n | \"const\"\n | \"namespace\"\n | \"unknown\";\n\n/** A single named export surfaced by an entry point, resolved to its original defining file. */\nexport interface PublicExport {\n /** Exported symbol name. */\n name: string;\n /** Project-relative path of the file that originally defines this symbol. */\n definedIn: string;\n /** Coarse kind derived from the signature prefix. */\n kind: ExportKind;\n /** JSDoc summary when present on the defining export. */\n doc?: string;\n /** Type signature string when present (e.g. `\"interface FileNode\"`, `\"class Graph\"`). */\n signature?: string;\n}\n\n/** The complete API surface report for one or more entry points. */\nexport interface ApiSurface {\n /** Project-relative paths used as public entry points for this report. */\n entryPoints: string[];\n /** All symbols accessible from any entry point via direct declaration or `export *` chains. */\n publicExports: PublicExport[];\n /**\n * All non-test files transitively reachable from any entry point (excluding the entry points\n * themselves). These form the implementation surface backing the public API.\n */\n internalFiles: string[];\n /**\n * Non-test files NOT reachable from any entry point — separate consumers (CLI, MCP server),\n * config, or truly unused files. Not automatically dead code.\n */\n unreachableFromEntry: string[];\n /**\n * Test files in the graph that are not reachable from any entry point.\n * Shown separately so they don't inflate the `unreachableFromEntry` signal.\n */\n testFiles: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves a `package.json` field value (from `exports` or `main`) to a project-relative\n * path present in the graph. Tries the field as-is, then converts `dist/…js` → `src/…ts`.\n *\n * @param {string} field - Raw field value (e.g. `\"./dist/index.js\"`).\n * @param {Graph} graph - Graph to probe.\n * @returns {string | null} Project-relative path, or `null` if not found.\n */\nfunction tryResolveSrcEquiv(field: string, graph: Graph): string | null {\n const rel = field.replace(/^\\.\\//, \"\");\n if (graph.nodes.has(rel)) return rel;\n const srcEquiv = rel.replace(/^dist\\//, \"src/\").replace(/\\.(js|mjs|cjs)$/, \".ts\");\n if (graph.nodes.has(srcEquiv)) return srcEquiv;\n return null;\n}\n\n/**\n * Resolves a single value from `package.json exports[subpath]` to a graph path.\n * Handles both plain strings and conditional-export objects (`{ import, require, default }`).\n *\n * @param {unknown} value - Value for one subpath entry in the `exports` map.\n * @param {Graph} graph - Graph to probe.\n * @returns {string | null} Project-relative path, or `null` if not resolvable.\n */\nfunction resolveExportsValue(value: unknown, graph: Graph): string | null {\n if (typeof value === \"string\") return tryResolveSrcEquiv(value, graph);\n if (value && typeof value === \"object\") {\n // Conditional exports: prefer import > require > default\n const cond = value as Record<string, unknown>;\n for (const key of [\"import\", \"require\", \"default\"]) {\n const resolved = resolveExportsValue(cond[key], graph);\n if (resolved) return resolved;\n }\n }\n return null;\n}\n\n/**\n * Infers a coarse `ExportKind` from the leading keyword of a type signature string.\n *\n * @param {string | undefined} signature - Raw signature string from an `ExportedSymbol`.\n * @returns {ExportKind} Inferred kind, or `\"unknown\"` when the signature is absent or unrecognised.\n */\nfunction inferExportKind(signature: string | undefined): ExportKind {\n if (!signature) return \"unknown\";\n const trimmed = signature.trimStart();\n if (trimmed.startsWith(\"interface \")) return \"interface\";\n if (trimmed.startsWith(\"class \")) return \"class\";\n if (trimmed.startsWith(\"enum \")) return \"enum\";\n if (trimmed.startsWith(\"type \")) return \"type\";\n if (trimmed.startsWith(\"namespace \")) return \"namespace\";\n if (\n trimmed.startsWith(\"const \") ||\n trimmed.startsWith(\"let \") ||\n trimmed.startsWith(\"var \") ||\n trimmed.startsWith(\"readonly \")\n )\n return \"const\";\n // Function signatures: leading `(`, async keyword, or contains `=>`\n if (\n trimmed.startsWith(\"(\") ||\n trimmed.startsWith(\"async \") ||\n trimmed.startsWith(\"function \") ||\n trimmed.includes(\"=>\")\n )\n return \"function\";\n return \"unknown\";\n}\n\n/**\n * Walks the `export * from` and named `export { … } from` chains starting at each entry\n * point and returns every symbol name accessible to consumers of those entry points.\n *\n * Wildcard re-exports (`export * from \"./module\"` — edge with no `symbols`) propagate all\n * exports of the target file and recurse into that file's own re-export edges.\n * Named re-exports (`export { foo } from \"./module\"` — edge with `symbols: [\"foo\"]`) add\n * only those names without recursing, because the constraint is already fully specified.\n *\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {Set<string>} All symbol names accessible from the entry points.\n */\nfunction collectAccessibleSymbolNames(graph: Graph, entryPoints: string[]): Set<string> {\n const accessible = new Set<string>();\n // Only visit a file via wildcard path once to avoid cycles and redundant work\n const wildcardVisited = new Set<string>();\n const queue: string[] = [...entryPoints];\n\n while (queue.length) {\n const current = queue.shift() as string;\n if (wildcardVisited.has(current)) continue;\n wildcardVisited.add(current);\n\n const node = graph.nodes.get(current);\n if (!node) continue;\n\n // Direct exports declared in this file (catches concrete declarations in entry points)\n for (const sym of node.exports) accessible.add(sym.name);\n\n // Follow re-export edges.\n // The TypeScript parser represents `export * from \"…\"` as symbols: [\"*\"].\n // Named re-exports like `export { foo } from \"…\"` carry the actual names.\n for (const imp of node.imports) {\n if (imp.type !== \"re-export\" || imp.isExternal || !imp.toPath) continue;\n\n const isWildcard = !imp.symbols?.length || imp.symbols.includes(\"*\");\n if (isWildcard) {\n // Wildcard re-export: expose all target exports and recurse into that file\n const target = graph.nodes.get(imp.toPath);\n if (target) {\n for (const sym of target.exports) accessible.add(sym.name);\n }\n queue.push(imp.toPath);\n } else {\n // Named re-export: expose only the listed names, do not recurse\n for (const name of imp.symbols as string[]) accessible.add(name);\n }\n }\n }\n\n return accessible;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Attempts to auto-detect the primary public entry point by reading `package.json` from `root`.\n * Handles modern conditional-exports objects as well as plain `main`/`module` fields.\n * Converts `dist/index.js` → `src/index.ts` before checking the graph.\n * Falls back to common candidates when `package.json` is absent or unparseable.\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string} root - Absolute path to the project root.\n * @returns {string | null} Project-relative path of the detected entry point, or `null` if none found.\n */\nexport function detectEntryPoint(graph: Graph, root: string): string | null {\n const all = detectAllEntryPoints(graph, root);\n return all[0] ?? null;\n}\n\n/**\n * Detects all public entry points for a project by reading the `package.json exports` map.\n * Each sub-path (`.`, `./utils`, etc.) is resolved to a project-relative graph path.\n * Falls back to `main`/`module` fields, then to common `src/index.ts` candidates.\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string} root - Absolute path to the project root.\n * @returns {string[]} Ordered list of project-relative paths for all detected entry points.\n */\nexport function detectAllEntryPoints(graph: Graph, root: string): string[] {\n const found: string[] = [];\n\n const pkgPath = path.join(root, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\")) as {\n main?: string;\n module?: string;\n exports?: unknown;\n };\n\n // Modern packages: parse exports map (handles conditional exports)\n if (pkg.exports && typeof pkg.exports === \"object\" && !Array.isArray(pkg.exports)) {\n for (const value of Object.values(pkg.exports as Record<string, unknown>)) {\n const resolved = resolveExportsValue(value, graph);\n if (resolved && !found.includes(resolved)) found.push(resolved);\n }\n } else if (typeof pkg.exports === \"string\") {\n const resolved = tryResolveSrcEquiv(pkg.exports, graph);\n if (resolved) found.push(resolved);\n }\n\n // Legacy fallbacks: main / module\n if (found.length === 0) {\n for (const field of [pkg.main, pkg.module].filter(Boolean) as string[]) {\n const resolved = tryResolveSrcEquiv(field, graph);\n if (resolved && !found.includes(resolved)) found.push(resolved);\n }\n }\n } catch {\n // ignore parse/IO errors\n }\n }\n\n // Last resort: well-known candidates\n if (found.length === 0) {\n for (const candidate of [\"src/index.ts\", \"src/index.js\", \"index.ts\", \"index.js\"]) {\n if (graph.nodes.has(candidate)) {\n found.push(candidate);\n break;\n }\n }\n }\n\n return found;\n}\n\n/**\n * Builds an API surface report for one or more public entry points.\n *\n * **Public exports** are collected by walking `export * from` wildcard chains and named\n * `export { … } from` edges — not just the `exports` array of the entry node. This means\n * barrel re-export patterns (the common TypeScript library layout) are handled correctly.\n * Each symbol is resolved to the file that concretely defines it (has `signature` or `doc`\n * on a non-barrel node); barrel intermediaries are skipped.\n *\n * **File partitioning** (all graph nodes, each in exactly one bucket):\n * - `entryPoints` themselves\n * - `internalFiles` — reachable from any entry point, non-test\n * - `testFiles` — not reachable from any entry point, `category === \"test\"`\n * - `unreachableFromEntry` — not reachable from any entry point, non-test (may be separate consumers or dead code)\n *\n * @param {Graph} graph - The built dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of the public entry point files.\n * @returns {ApiSurface} The API surface report.\n * @throws {Error} If any entry point is not present in the graph.\n */\n/**\n * @description Walks outgoing imports from every entry point and returns the set of all\n * files reachable from them, including the entry points themselves.\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {Set<string>} Every file path reachable from any entry point.\n */\nfunction collectReachableFiles(graph: Graph, entryPoints: string[]): Set<string> {\n const reachableFiles = new Set<string>(entryPoints);\n for (const entryPoint of entryPoints) {\n graph.traverse(\n entryPoint,\n (node) => {\n reachableFiles.add(node.path);\n return true;\n },\n { direction: \"outgoing\" },\n );\n }\n return reachableFiles;\n}\n\n/** The best concrete definition found for an exported symbol name. */\ninterface SymbolDefinition {\n file: string;\n symbol: ExportedSymbol;\n}\n\n/**\n * @description Builds a map of symbol name → best concrete definition among all reachable,\n * non-entry files. \"Best\" means having a signature/doc on a non-barrel file, so `definedIn`\n * points at the actual implementation rather than a re-exporting barrel.\n * @param {Graph} graph - The dependency graph.\n * @param {Set<string>} reachableFiles - Files reachable from any entry point.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points, excluded from consideration.\n * @returns {Map<string, SymbolDefinition>} Symbol name → its best concrete definition.\n */\nfunction buildDefinitionsMap(\n graph: Graph,\n reachableFiles: Set<string>,\n entryPoints: string[],\n): Map<string, SymbolDefinition> {\n const definitions = new Map<string, SymbolDefinition>();\n for (const filePath of reachableFiles) {\n if (entryPoints.includes(filePath)) continue;\n const node = graph.nodes.get(filePath);\n if (!node) continue;\n const isBarrel = node.category === \"barrel\";\n for (const exportedSymbol of node.exports) {\n const existingDefinition = definitions.get(exportedSymbol.name);\n const hasConcreteSignature = !!(exportedSymbol.signature || exportedSymbol.doc);\n if (!existingDefinition || (hasConcreteSignature && !isBarrel)) {\n definitions.set(exportedSymbol.name, { file: filePath, symbol: exportedSymbol });\n }\n }\n }\n return definitions;\n}\n\n/**\n * @description Builds the sorted `publicExports` list for every accessible symbol name,\n * preferring the best concrete definition found by `buildDefinitionsMap` and falling back\n * to the entry node's own `ExportedSymbol` when no better definition exists.\n * @param {Set<string>} accessibleNames - All symbol names accessible from the entry points.\n * @param {Map<string, SymbolDefinition>} definitions - Symbol name → best concrete definition.\n * @param {Graph} graph - The dependency graph.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {PublicExport[]} Public exports sorted alphabetically by name.\n */\nfunction buildPublicExports(\n accessibleNames: Set<string>,\n definitions: Map<string, SymbolDefinition>,\n graph: Graph,\n entryPoints: string[],\n): PublicExport[] {\n const publicExports: PublicExport[] = [];\n for (const name of accessibleNames) {\n const definition = definitions.get(name);\n const entrySymbol = entryPoints\n .flatMap((entryPoint) => graph.nodes.get(entryPoint)?.exports ?? [])\n .find((exportedSymbol) => exportedSymbol.name === name);\n const symbol = definition?.symbol ?? entrySymbol;\n\n const definedIn =\n definition?.file ??\n entryPoints.find((entryPoint) =>\n graph.nodes.get(entryPoint)?.exports.some((exportedSymbol) => exportedSymbol.name === name),\n ) ??\n (entryPoints[0] as string);\n\n const publicExport: PublicExport = {\n name,\n definedIn,\n kind: inferExportKind(symbol?.signature),\n };\n if (symbol?.doc) publicExport.doc = symbol.doc;\n if (symbol?.signature) publicExport.signature = symbol.signature;\n publicExports.push(publicExport);\n }\n publicExports.sort((exportA, exportB) => exportA.name.localeCompare(exportB.name));\n return publicExports;\n}\n\n/** File-path partitions of the whole graph relative to reachability and test status. */\ninterface NodePartitions {\n internalFiles: string[];\n unreachableFromEntry: string[];\n testFiles: string[];\n}\n\n/**\n * @description Partitions every file in the graph into implementation files backing the\n * public API (`internalFiles`), non-test files unreachable from any entry point\n * (`unreachableFromEntry`), and unreachable test files (`testFiles`).\n * @param {Graph} graph - The dependency graph.\n * @param {Set<string>} reachableFiles - Files reachable from any entry point.\n * @param {string[]} entryPoints - Project-relative paths of all public entry points.\n * @returns {NodePartitions} The three file-path partitions.\n */\nfunction partitionNodes(\n graph: Graph,\n reachableFiles: Set<string>,\n entryPoints: string[],\n): NodePartitions {\n const isTestNode = (filePath: string) => graph.nodes.get(filePath)?.category === \"test\";\n\n const internalFiles = [...reachableFiles].filter(\n (filePath) => !entryPoints.includes(filePath) && !isTestNode(filePath),\n );\n\n const unreachableFiles = [...graph.nodes.keys()].filter(\n (filePath) => !reachableFiles.has(filePath),\n );\n const unreachableFromEntry = unreachableFiles.filter((filePath) => !isTestNode(filePath));\n const testFiles = unreachableFiles.filter((filePath) => isTestNode(filePath));\n\n return { internalFiles, unreachableFromEntry, testFiles };\n}\n\nexport function buildApiSurface(graph: Graph, entryPoints: string[]): ApiSurface {\n if (entryPoints.length === 0)\n throw new Error(\"buildApiSurface requires at least one entry point\");\n\n for (const entryPoint of entryPoints) {\n if (!graph.nodes.has(entryPoint))\n throw new Error(`Entry point not found in graph: ${entryPoint}`);\n }\n\n const reachableFiles = collectReachableFiles(graph, entryPoints);\n const definitions = buildDefinitionsMap(graph, reachableFiles, entryPoints);\n\n // Handles `export * from` wildcards that the parser doesn't expand into the entry node's\n // own `exports` array.\n const accessibleNames = collectAccessibleSymbolNames(graph, entryPoints);\n\n const publicExports = buildPublicExports(accessibleNames, definitions, graph, entryPoints);\n const { internalFiles, unreachableFromEntry, testFiles } = partitionNodes(\n graph,\n reachableFiles,\n entryPoints,\n );\n\n return { entryPoints, publicExports, internalFiles, unreachableFromEntry, testFiles };\n}\n","/** Queries the call-edge graph to find callers and callees at the function level. */\nimport type { Graph } from \"../model\";\nimport type { CalleeEntry, CallerEntry, FunctionCallInfo } from \"./types\";\n\nexport type { CalleeEntry, CallerEntry, FunctionCallInfo } from \"./types\";\n\n/**\n * Queries the call graph for a named function, returning its callers and callees.\n *\n * Callers are found by scanning every node's `callEdges` for edges whose `to`\n * field matches `functionName`. Callees are found by looking at the defining\n * file's `callEdges` for edges whose `from` field matches `functionName`.\n *\n * Call edges are populated only for TypeScript/JavaScript files. Functions in\n * other language files will return empty `callers` and `callees` arrays.\n *\n * @param {Graph} graph - The import graph that carries `callEdges` on each node.\n * @param {string} functionName - Exact name of the function to look up.\n * @returns {FunctionCallInfo} Caller/callee lists; `definedIn` is `null` if the function is not exported.\n */\nexport function queryCallGraph(graph: Graph, functionName: string): FunctionCallInfo {\n let definedIn: string | null = null;\n const callers: CallerEntry[] = [];\n\n for (const node of graph.nodes.values()) {\n if (node.exports.some((exportedSym) => exportedSym.name === functionName)) {\n definedIn = node.path;\n }\n\n for (const edge of node.callEdges ?? []) {\n if (edge.to === functionName) {\n callers.push({ file: node.path, callerFunction: edge.from });\n }\n }\n }\n\n const callees: CalleeEntry[] = [];\n if (definedIn) {\n const defNode = graph.nodes.get(definedIn);\n for (const edge of defNode?.callEdges ?? []) {\n if (edge.from === functionName) {\n callees.push({ file: edge.toFile, calleeFunction: edge.to });\n }\n }\n }\n\n return { functionName, definedIn, callers, callees };\n}\n","/** Pre-computed blast-radius cache: maps each file to the set of files that would be affected if it changed. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Graph } from \"./model\";\n\n/**\n * Pre-computed blast-radius map for every file in the graph.\n *\n * At small scales (< ~500 nodes) the live `graph.traverse` approach used by\n * `get_affected` is fast enough (< 1ms per query). The cache pays off when:\n * - The same file is queried many times in one session (O(1) vs O(n) per call)\n * - The codebase exceeds ~1000 nodes and traversal cost becomes noticeable\n * - The cache is persisted to disk and reused across MCP restarts\n */\nexport interface ChangeImpactCache {\n /**\n * Map from project-relative file path to the list of all files that are\n * transitively affected if that file changes (incoming traversal).\n */\n impact: Map<string, string[]>;\n /**\n * Fingerprint of the graph this cache was built from.\n * Used to detect stale caches without re-traversing the graph.\n */\n graphHash: string;\n}\n\n/** Wire format written to `.mokosh/change-impact-cache.json`. */\ninterface SerializedChangeImpactCache {\n graphHash: string;\n impact: [string, string[]][];\n}\n\n/**\n * Computes a lightweight fingerprint of the graph by hashing the sorted list of\n * `path:mtime:size` tuples for every node. Any file addition, deletion, or\n * modification will produce a different hash.\n *\n * @param graph - The graph to fingerprint.\n * @returns A hex string hash.\n */\nexport function computeGraphHash(graph: Graph): string {\n const entries = [...graph.nodes.entries()]\n .sort(([pathA], [pathB]) => pathA.localeCompare(pathB))\n .map(([filePath, node]) => `${filePath}:${node.mtime}:${node.size}`)\n .join(\"|\");\n\n // FNV-1a 32-bit — fast, deterministic, good enough for cache invalidation.\n let hash = 0x811c9dc5;\n for (let i = 0; i < entries.length; i++) {\n hash ^= entries.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash.toString(16).padStart(8, \"0\");\n}\n\n/**\n * Pre-computes the incoming blast-radius for every node in the graph.\n *\n * Runs one incoming traversal per node — O(n²) worst case but performed once.\n * Results are stored in a `Map` for O(1) subsequent lookups.\n *\n * @param graph - The import graph to pre-compute.\n * @returns A `ChangeImpactCache` ready for `queryChangeImpact`.\n */\nexport function buildChangeImpactCache(graph: Graph): ChangeImpactCache {\n const impact = new Map<string, string[]>();\n\n for (const filePath of graph.nodes.keys()) {\n const affected: string[] = [];\n graph.traverse(\n filePath,\n (node) => {\n if (node.path !== filePath) affected.push(node.path);\n return true;\n },\n { direction: \"incoming\" },\n );\n impact.set(filePath, affected);\n }\n\n return { impact, graphHash: computeGraphHash(graph) };\n}\n\n/**\n * Returns the list of files transitively affected by a change in `filePath`.\n * Falls back to an empty array when the file is not in the cache.\n *\n * @param cache - A previously built `ChangeImpactCache`.\n * @param filePath - Project-relative path of the changed file.\n * @returns Sorted list of affected file paths.\n */\nexport function queryChangeImpact(cache: ChangeImpactCache, filePath: string): string[] {\n return cache.impact.get(filePath) ?? [];\n}\n\n/**\n * Returns `true` when `cache` was built from the same graph as `graph`.\n * Use this before trusting a deserialized cache loaded from disk.\n *\n * @param cache - The cache to validate.\n * @param graph - The current graph to compare against.\n * @returns `true` if the cache is still valid for this graph.\n */\nexport function isChangeImpactCacheValid(cache: ChangeImpactCache, graph: Graph): boolean {\n return cache.graphHash === computeGraphHash(graph);\n}\n\n/**\n * Serializes a `ChangeImpactCache` to JSON and writes it to `cachePath`,\n * creating parent directories as needed.\n *\n * @param cache - The cache to persist.\n * @param cachePath - Absolute path to write the JSON file.\n */\nexport function saveChangeImpactCache(cache: ChangeImpactCache, cachePath: string): void {\n const dir = path.dirname(cachePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const serialized: SerializedChangeImpactCache = {\n graphHash: cache.graphHash,\n impact: [...cache.impact.entries()],\n };\n fs.writeFileSync(cachePath, JSON.stringify(serialized));\n}\n\n/**\n * Reads and deserializes a `ChangeImpactCache` from disk.\n * Returns `null` when the file does not exist or cannot be parsed.\n *\n * @param cachePath - Absolute path to the JSON file written by `saveChangeImpactCache`.\n * @returns The deserialized cache, or `null` on failure.\n */\nexport function loadChangeImpactCache(cachePath: string): ChangeImpactCache | null {\n if (!fs.existsSync(cachePath)) return null;\n try {\n const raw = fs.readFileSync(cachePath, \"utf-8\");\n const parsed = JSON.parse(raw) as SerializedChangeImpactCache;\n return {\n graphHash: parsed.graphHash,\n impact: new Map(parsed.impact),\n };\n } catch {\n return null;\n }\n}\n","/** Detects feature-hub files — high-out-degree orchestrators that import many other files — from a dependency graph. */\nimport path from \"node:path\";\nimport type { FileNode } from \"../../types/node\";\n\n/** Controls how aggressively `detectFeatures` promotes files to features. */\nexport interface FeatureDetectionOptions {\n /**\n * Minimum number of internal imports a file must have before it is\n * considered a feature. Lower values surface more candidates;\n * higher values keep the list focused on true feature aggregators.\n * @default 5\n */\n minOutDegree?: number;\n}\n\n/**\n * A file identified as a feature hub — a non-test file with high out-degree\n * (imports many internal modules), acting as an orchestrator or aggregator.\n */\nexport interface FeatureInfo {\n /** Absolute (or project-relative) path of the feature file. */\n path: string;\n /** How many internal files this file imports (its out-degree in the dep graph). */\n outDegree: number;\n /** Auto-generated tag of the form `feature:<basename>`, used for queries and reports. */\n tag: string;\n}\n\n/**\n * @description Counts how many internal imports each file has, producing the raw out-degree data\n * used by `buildFeatureMap` to filter feature candidates.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @returns {Map<string, number>} Map from file path to its internal import count (out-degree).\n */\nfunction buildOutDegreeMap(nodes: Map<string, FileNode>): Map<string, number> {\n const outDegreeMap = new Map<string, number>();\n for (const [filePath, node] of nodes) {\n const count = node.imports.filter((imp) => imp.toPath && !imp.isExternal).length;\n if (count > 0) {\n outDegreeMap.set(filePath, count);\n }\n }\n return outDegreeMap;\n}\n\n/**\n * @description Filters an out-degree map down to the non-test, non-barrel files whose import\n * count meets `minOutDegree`, then builds the `FeatureInfo` record for each.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @param {Map<string, number>} outDegreeMap - Pre-computed internal import counts for every file.\n * @param {number} minOutDegree - Minimum out-degree a file must reach to be included.\n * @returns {Map<string, FeatureInfo>} Map of qualifying feature files; empty if none qualify.\n */\nfunction buildFeatureMap(\n nodes: Map<string, FileNode>,\n outDegreeMap: Map<string, number>,\n minOutDegree: number,\n): Map<string, FeatureInfo> {\n const result = new Map<string, FeatureInfo>();\n for (const [filePath, outDegree] of outDegreeMap) {\n if (outDegree < minOutDegree) continue;\n const node = nodes.get(filePath);\n if (!node || node.category === \"test\" || node.category === \"barrel\") continue;\n const ext = path.extname(filePath);\n const basename = path.basename(filePath, ext);\n const label = basename === \"index\" ? path.basename(path.dirname(filePath)) : basename;\n result.set(filePath, { path: filePath, outDegree, tag: `feature:${label}` });\n }\n return result;\n}\n\n/**\n * @description Scans the dependency graph and promotes non-test, non-barrel files with many\n * imports to \"feature hubs\". Uses a two-pass approach: first count out-degrees, then filter\n * and annotate. The result maps file path → `FeatureInfo` for tag generation or graph annotation.\n * @param {Map<string, FileNode>} nodes - All file nodes in the dependency graph, keyed by file path.\n * @param {FeatureDetectionOptions} [options] - Tuning knobs; currently just `minOutDegree` (default 5).\n * @returns {Map<string, FeatureInfo>} Map of detected feature files; empty if none qualify.\n */\nexport function detectFeatures(\n nodes: Map<string, FileNode>,\n options?: FeatureDetectionOptions,\n): Map<string, FeatureInfo> {\n const minOutDegree = options?.minOutDegree ?? 5;\n\n return buildFeatureMap(nodes, buildOutDegreeMap(nodes), minOutDegree);\n}\n","/** Builds a FeatureGraph grouping graph nodes into feature domains under their respective hub files. */\nimport type { FileNode } from \"../../types/node\";\nimport type { Graph } from \"../model\";\nimport { detectFeatures, type FeatureDetectionOptions, type FeatureInfo } from \"./index\";\n\n/**\n * A group of files that a single feature hub transitively imports.\n * The hub itself is the high-out-degree orchestrator; `files` are its dependencies.\n */\nexport interface FeatureDomain {\n /** Project-relative path of the feature hub file. */\n hub: string;\n /** Number of internal imports the hub has (its out-degree). */\n outDegree: number;\n /** All files transitively imported by the hub, excluding the hub itself. */\n files: string[];\n}\n\n/**\n * Domain-clustered view of the import graph.\n * Provides a token-efficient way to answer \"what files are in domain X?\"\n * without traversing the full graph.\n */\nexport interface FeatureGraph {\n /** Map from feature name (e.g. `\"parser\"`) to its domain info. */\n features: Map<string, FeatureDomain>;\n /**\n * Files not reachable from any feature hub — shared utilities, top-level\n * entry points, or files below the out-degree threshold.\n */\n unassigned: string[];\n}\n\n/**\n * Options for `buildFeatureGraph`. Extends `FeatureDetectionOptions` so callers\n * can pass `{ minOutDegree }` without needing to know this type explicitly.\n */\nexport interface FeatureGraphOptions extends FeatureDetectionOptions {\n /**\n * Comparator used to pick the \"best\" hub when a file is reachable from\n * multiple hubs. Return a negative number when `left` should win over `right`.\n * @default ascending out-degree (most-specific hub wins)\n */\n hubComparator?: (left: FeatureInfo, right: FeatureInfo) => number;\n /**\n * Override the hub-detection function. Defaults to `detectFeatures`.\n * Inject a custom implementation for testing or alternative hub strategies.\n */\n detectFn?: (\n nodes: Map<string, FileNode>,\n options?: FeatureDetectionOptions,\n ) => Map<string, FeatureInfo>;\n}\n\nconst DEFAULT_HUB_COMPARATOR = (left: FeatureInfo, right: FeatureInfo) =>\n left.outDegree - right.outDegree;\n\n/**\n * @param graph - The import graph to cluster.\n * @param hubs - Detected feature hub files.\n * @returns Map from hub path to the set of files reachable from that hub (hub itself excluded).\n */\nfunction collectReachable(graph: Graph, hubs: Map<string, FeatureInfo>): Map<string, Set<string>> {\n const reachable = new Map<string, Set<string>>();\n for (const hub of hubs.values()) {\n const files = new Set<string>();\n graph.traverse(\n hub.path,\n (node) => {\n if (node.path !== hub.path) files.add(node.path);\n return true;\n },\n { direction: \"outgoing\" },\n );\n reachable.set(hub.path, files);\n }\n return reachable;\n}\n\n/**\n * @param nodes - All graph nodes.\n * @param hubs - Detected feature hub files.\n * @param reachable - Pre-computed reachability sets from `collectReachable`.\n * @param comparator - Tiebreak function; lower return value means the hub wins.\n * @returns Map from non-hub file path to the path of its assigned hub.\n */\nfunction assignFilesToHubs(\n nodes: Map<string, FileNode>,\n hubs: Map<string, FeatureInfo>,\n reachable: Map<string, Set<string>>,\n comparator: (left: FeatureInfo, right: FeatureInfo) => number,\n): Map<string, string> {\n const fileToHub = new Map<string, string>();\n for (const [filePath] of nodes) {\n if (hubs.has(filePath)) continue;\n let bestHub: FeatureInfo | null = null;\n for (const hub of hubs.values()) {\n if (!reachable.get(hub.path)?.has(filePath)) continue;\n if (!bestHub || comparator(hub, bestHub) < 0) bestHub = hub;\n }\n if (bestHub) fileToHub.set(filePath, bestHub.path);\n }\n return fileToHub;\n}\n\n/**\n * @param hubs - Detected feature hub files.\n * @param fileToHub - Assignment map from `assignFilesToHubs`.\n * @returns Map from feature name to its `FeatureDomain`.\n */\nfunction buildDomains(\n hubs: Map<string, FeatureInfo>,\n fileToHub: Map<string, string>,\n): Map<string, FeatureDomain> {\n const features = new Map<string, FeatureDomain>();\n for (const hub of hubs.values()) {\n const featureName = hub.tag.replace(\"feature:\", \"\");\n const files: string[] = [];\n for (const [filePath, ownerHub] of fileToHub) {\n if (ownerHub === hub.path) files.push(filePath);\n }\n features.set(featureName, { hub: hub.path, outDegree: hub.outDegree, files });\n }\n return features;\n}\n\n/**\n * @param nodes - All graph nodes.\n * @param hubs - Detected feature hub files.\n * @param fileToHub - Assignment map from `assignFilesToHubs`.\n * @returns File paths that are neither a hub nor claimed by any hub.\n */\nfunction collectUnassigned(\n nodes: Map<string, FileNode>,\n hubs: Map<string, FeatureInfo>,\n fileToHub: Map<string, string>,\n): string[] {\n const unassigned: string[] = [];\n for (const filePath of nodes.keys()) {\n if (!hubs.has(filePath) && !fileToHub.has(filePath)) {\n unassigned.push(filePath);\n }\n }\n return unassigned;\n}\n\n/**\n * Builds a domain-clustered view of the import graph by grouping files under\n * the most specific feature hub that can reach them.\n *\n * Assignment rule: each file is assigned to the hub with the lowest out-degree\n * that can transitively reach it (overridable via `options.hubComparator`).\n *\n * @param graph - The import graph to cluster.\n * @param options - Controls hub detection threshold, assignment comparator, and detectFn override.\n * @returns A `FeatureGraph` with one domain per hub and an `unassigned` list.\n */\nexport function buildFeatureGraph(graph: Graph, options?: FeatureGraphOptions): FeatureGraph {\n const detectFn = options?.detectFn ?? detectFeatures;\n const comparator = options?.hubComparator ?? DEFAULT_HUB_COMPARATOR;\n const hubs = detectFn(graph.nodes, options);\n const reachable = collectReachable(graph, hubs);\n const fileToHub = assignFilesToHubs(graph.nodes, hubs, reachable, comparator);\n return {\n features: buildDomains(hubs, fileToHub),\n unassigned: collectUnassigned(graph.nodes, hubs, fileToHub),\n };\n}\n","/** Analyzes a dependency graph node map for unused files, export-usage hotspots, and circular import chains. */\nimport type { FileNode } from \"../types/node\";\n\n/**\n * @description Utility for analyzing the dependency graph for cycles and unused files.\n * Operates on the raw node map rather than a `Graph` instance so it can be used\n * without the full traversal infrastructure.\n */\nexport class GraphAnalyzer {\n /**\n * @param {Map<string, FileNode>} nodes - The full node map of the graph to analyze, keyed by project-relative file path.\n */\n constructor(private nodes: Map<string, FileNode>) {}\n\n /**\n * @description Returns files from `allFiles` that are absent from the graph — meaning nothing\n * imports them directly or transitively from any entry point, making them deletion candidates.\n * @param {string[]} allFiles - Complete list of project-relative file paths to test against the graph.\n * @returns {string[]} Subset of `allFiles` whose paths do not appear as graph nodes.\n */\n public findUnusedFiles(allFiles: string[]): string[] {\n const usedFiles = new Set(this.nodes.keys());\n return allFiles.filter((file) => !usedFiles.has(file));\n }\n\n /**\n * @description Returns files whose highest single-edge export usage ratio meets or exceeds\n * `threshold`, sorted descending by `maxExportUsage`. Useful for identifying files\n * that consume a large fraction of one dependency's API surface.\n * @param {number} threshold - Minimum `maxExportUsage` value (0–1) for a file to be included.\n * @returns {Array<{ path: string; maxExportUsage: number; tightestDep: string }>} Entries sorted descending by `maxExportUsage`.\n */\n public findHighExportUsage(\n threshold: number,\n ): Array<{ path: string; maxExportUsage: number; tightestDep: string }> {\n const results: Array<{ path: string; maxExportUsage: number; tightestDep: string }> = [];\n\n for (const node of this.nodes.values()) {\n if (node.maxExportUsage === undefined || node.maxExportUsage < threshold) continue;\n const tightest = node.imports.reduce(\n (best, imp) => ((imp.exportUsageRatio ?? 0) > (best?.exportUsageRatio ?? 0) ? imp : best),\n null as (typeof node.imports)[number] | null,\n );\n results.push({\n path: node.path,\n maxExportUsage: node.maxExportUsage,\n tightestDep: tightest?.toPath ?? \"\",\n });\n }\n\n return results.sort((left, right) => right.maxExportUsage - left.maxExportUsage);\n }\n\n /**\n * @description Detects all circular import chains using DFS with a recursion-stack back-edge check.\n * Each returned array is one cycle as an ordered list of file paths ending at the entry that closes the loop.\n * @returns {string[][]} Array of cycles; each cycle is an ordered list of file paths forming a loop.\n */\n public findCycles(): string[][] {\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const recStack = new Set<string>();\n const currentPath: string[] = [];\n\n const find = (current: string) => {\n visited.add(current);\n recStack.add(current);\n currentPath.push(current);\n\n const node = this.nodes.get(current);\n if (node) {\n for (const imp of node.imports) {\n if (!imp.toPath || imp.isExternal) continue;\n\n if (recStack.has(imp.toPath)) {\n // Found a cycle\n const cycleIndex = currentPath.indexOf(imp.toPath);\n cycles.push([...currentPath.slice(cycleIndex), imp.toPath]);\n } else if (!visited.has(imp.toPath)) {\n find(imp.toPath);\n }\n }\n }\n\n recStack.delete(current);\n currentPath.pop();\n };\n\n for (const nodePath of this.nodes.keys()) {\n if (!visited.has(nodePath)) {\n find(nodePath);\n }\n }\n\n return cycles;\n }\n}\n","/** Graph class wrapping the raw node map with DFS traversal, cycle detection, serialization, and reverse-edge helpers. */\nimport type { SerializedGraph, TraversalOptions, TraversalVisitor } from \"../types/graph\";\nimport type { CallEdge, FileNode } from \"../types/node\";\nimport { GraphAnalyzer } from \"./analyzer\";\n\n/**\n * @description Represents the dependency graph of the project.\n * Wraps the raw node map with traversal, cycle detection, serialization, and\n * reverse-index helpers. All node paths are project-relative strings.\n */\nexport class Graph {\n private _incomingEdgesCache: Map<string, string[]> | null = null;\n private _callIncomingCache: Map<string, string[]> | null = null;\n\n /**\n * @param {Map<string, FileNode>} nodes - All parsed file nodes, keyed by project-relative path.\n */\n constructor(public nodes: Map<string, FileNode>) {}\n\n /**\n * @description Serializes the graph into a plain JSON-compatible object\n * that can be written to disk and later restored via `deserialize`.\n * @returns {SerializedGraph} A flat representation of all nodes in the graph.\n */\n public serialize(): SerializedGraph {\n return {\n nodes: Array.from(this.nodes.values()),\n };\n }\n\n /**\n * @description Reconstructs a Graph from a serialized snapshot, rebuilding\n * the internal node map keyed by file path.\n * @param {SerializedGraph} serialized - The plain object produced by `serialize`.\n * @returns {Graph} A fully functional Graph instance.\n */\n public static deserialize(serialized: SerializedGraph): Graph {\n const nodes = new Map<string, FileNode>();\n for (const node of serialized.nodes) {\n nodes.set(node.path, node);\n }\n return new Graph(nodes);\n }\n\n /**\n * @description Lazily builds and caches a reverse index mapping each file path\n * to the list of files that import it. Used internally for incoming traversal.\n * @returns {Map<string, string[]>} Map from target file path to list of importer file paths.\n */\n private getIncomingEdgesMap(): Map<string, string[]> {\n if (this._incomingEdgesCache) return this._incomingEdgesCache;\n const incoming = new Map<string, string[]>();\n for (const node of this.nodes.values()) {\n for (const imp of node.imports) {\n if (imp.toPath) {\n const list = incoming.get(imp.toPath) || [];\n list.push(node.path);\n incoming.set(imp.toPath, list);\n }\n }\n }\n this._incomingEdgesCache = incoming;\n return incoming;\n }\n\n /**\n * @description Core DFS engine. Visits each reachable node once, calling visitor at each step.\n * The caller provides a getNeighbors function so the same loop works for any edge type.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Called for each visited node; return `false` to prune the branch.\n * @param options - `maxDepth` and `direction` (direction is interpreted by the caller's getNeighbors).\n * @param getNeighbors - Returns the next paths to visit from a given path.\n */\n private dfs(\n startPath: string,\n visitor: TraversalVisitor,\n options: TraversalOptions,\n getNeighbors: (path: string) => string[],\n ) {\n const visited = new Set<string>();\n const maxDepth = options.maxDepth ?? Infinity;\n\n const walk = (currentPath: string, depth: number, parentPath: string | null) => {\n if (depth > maxDepth || visited.has(currentPath)) return;\n const node = this.nodes.get(currentPath);\n if (!node) return;\n visited.add(currentPath);\n if (visitor(node, depth, parentPath) === false) return;\n for (const neighbor of getNeighbors(currentPath)) {\n walk(neighbor, depth + 1, currentPath);\n }\n };\n\n walk(startPath, 0, null);\n }\n\n /**\n * @description Performs a DFS on the import dependency graph.\n * Supports both outgoing and incoming (reverse) traversal.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Callback executed for each node; return `false` to stop traversing a branch.\n * @param options - Configuration for `maxDepth` and `direction`.\n */\n public traverse(startPath: string, visitor: TraversalVisitor, options: TraversalOptions = {}) {\n const direction = options.direction ?? \"outgoing\";\n const incoming = direction === \"incoming\" ? this.getIncomingEdgesMap() : null;\n this.dfs(startPath, visitor, options, (path) =>\n direction === \"outgoing\"\n ? ((this.nodes\n .get(path)\n ?.imports.map((importEdge) => importEdge.toPath)\n .filter(Boolean) as string[]) ?? [])\n : (incoming?.get(path) ?? []),\n );\n }\n\n /**\n * @description Builds and caches a reverse index of call edges: target file path → list of\n * source file paths whose exported functions call into it. Computed lazily on first access\n * and reused for the lifetime of this Graph instance.\n * @returns {Map<string, string[]>} Map from target file path to list of source file paths that call into it.\n */\n private getCallIncomingCache(): Map<string, string[]> {\n if (this._callIncomingCache) return this._callIncomingCache;\n const cache = new Map<string, string[]>();\n for (const node of this.nodes.values()) {\n for (const edge of node.callEdges ?? []) {\n const list = cache.get(edge.toFile) ?? [];\n list.push(node.path);\n cache.set(edge.toFile, list);\n }\n }\n this._callIncomingCache = cache;\n return cache;\n }\n\n /**\n * @description Performs a DFS over call edges (exported-function → imported-symbol).\n * Outgoing follows callEdges forward; incoming follows the reverse call index.\n * @param startPath - Project-relative path of the node to start from.\n * @param visitor - Callback executed for each node; return `false` to stop traversing a branch.\n * @param options - Configuration for `maxDepth` and `direction`.\n */\n public traverseCalls(\n startPath: string,\n visitor: TraversalVisitor,\n options: TraversalOptions = {},\n ) {\n const direction = options.direction ?? \"outgoing\";\n const callIncoming = direction === \"incoming\" ? this.getCallIncomingCache() : null;\n this.dfs(startPath, visitor, options, (path) =>\n direction === \"outgoing\"\n ? (this.nodes.get(path)?.callEdges?.map((callEdge) => callEdge.toFile) ?? [])\n : (callIncoming?.get(path) ?? []),\n );\n }\n\n /**\n * @description Returns files whose exported functions call into the given file (one hop).\n * @param filePath - Project-relative path of the target file.\n * @returns Project-relative paths of all direct callers.\n */\n public getCallers(filePath: string): string[] {\n const callers: string[] = [];\n this.traverseCalls(\n filePath,\n (node) => {\n if (node.path !== filePath) callers.push(node.path);\n return true;\n },\n { direction: \"incoming\", maxDepth: 1 },\n );\n return callers;\n }\n\n /**\n * @description Returns all call edges originating from a file.\n * @param filePath - Project-relative path of the source file.\n * @returns The file's call edges, or an empty array if none exist.\n */\n public getCallEdgesFor(filePath: string): CallEdge[] {\n return this.nodes.get(filePath)?.callEdges ?? [];\n }\n\n /**\n * @description Returns the FileNodes that a given file directly imports —\n * the first-hop outgoing neighbours in the import graph.\n * @param path - Project-relative path of the node to look up.\n * @returns Array of FileNodes imported by the given file; empty if the path is unknown.\n */\n public getNeighbors(path: string): FileNode[] {\n const node = this.nodes.get(path);\n if (!node) return [];\n return node.imports\n .map((imp) => this.nodes.get(imp.toPath))\n .filter((node): node is FileNode => node !== undefined);\n }\n\n /**\n * @description Identifies files that are not reachable from any entry point\n * by walking the full import graph forward from each node.\n * @param allFiles - Complete list of project-relative file paths to test.\n * @returns Subset of `allFiles` that nothing imports, directly or transitively.\n */\n public findUnusedFiles(allFiles: string[]): string[] {\n return new GraphAnalyzer(this.nodes).findUnusedFiles(allFiles);\n }\n\n /**\n * @description Detects all circular import chains in the graph using DFS\n * with a back-edge check. Each returned array is one cycle as an ordered path.\n * @returns Array of cycles; each cycle is a list of file paths forming a loop.\n */\n public findCycles(): string[][] {\n return new GraphAnalyzer(this.nodes).findCycles();\n }\n}\n","/** Infers a coarse semantic role for a file node from its path and graph category. */\nimport type { FileNode } from \"../../types/node\";\nimport type { ModuleRole } from \"./types\";\n\n/**\n * Infers a coarse `ModuleRole` from a file's path and graph category.\n * Uses common directory-naming conventions so it works across any project layout.\n *\n * @param {FileNode} node - The file node to classify.\n * @returns {ModuleRole} The best-matching role, defaulting to `\"other\"`.\n */\nexport function inferRole(node: FileNode): ModuleRole {\n if (node.category === \"test\") return \"test\";\n if (node.category === \"config\") return \"config\";\n if (node.category === \"type-only\") return \"types\";\n\n const filePath = node.path;\n\n // Ordered most-specific → least-specific\n if (seg(filePath, \"component\") || seg(filePath, \"components\")) return \"component\";\n if (seg(filePath, \"controller\") || seg(filePath, \"controllers\")) return \"controller\";\n if (seg(filePath, \"middleware\")) return \"middleware\";\n if (seg(filePath, \"router\") || seg(filePath, \"routes\") || seg(filePath, \"route\")) return \"router\";\n if (seg(filePath, \"store\") || seg(filePath, \"stores\")) return \"store\";\n if (seg(filePath, \"service\") || seg(filePath, \"services\")) return \"service\";\n if (seg(filePath, \"handler\") || seg(filePath, \"handlers\")) return \"handler\";\n if (seg(filePath, \"adapter\") || seg(filePath, \"adapters\")) return \"adapter\";\n if (seg(filePath, \"plugin\") || seg(filePath, \"plugins\")) return \"plugin\";\n if (seg(filePath, \"api\")) return \"api\";\n if (seg(filePath, \"cli\") || seg(filePath, \"commands\") || fileBasename(filePath) === \"cli\")\n return \"cli\";\n if (\n seg(filePath, \"util\") ||\n seg(filePath, \"utils\") ||\n seg(filePath, \"helper\") ||\n seg(filePath, \"helpers\")\n )\n return \"util\";\n if (seg(filePath, \"model\") || seg(filePath, \"models\") || fileBasename(filePath) === \"model\")\n return \"model\";\n if (seg(filePath, \"parser\") || seg(filePath, \"parsers\") || fileBasename(filePath) === \"parser\")\n return \"parser\";\n if (fileBasename(filePath) === \"builder\") return \"builder\";\n if (fileBasename(filePath) === \"resolver\") return \"resolver\";\n\n return \"other\";\n}\n\n/**\n * Returns true when `segment` appears as a discrete path component.\n * Matches `/<segment>/` (directory) or `/<segment>.` (file) to avoid false\n * positives on names that merely contain the segment as a substring.\n *\n * @param {string} filePath - Project-relative file path to test.\n * @param {string} segment - Directory or filename stem to look for.\n * @returns {boolean} Whether `segment` is a standalone path component in `filePath`.\n */\nfunction seg(filePath: string, segment: string): boolean {\n return filePath.includes(`/${segment}/`) || filePath.includes(`/${segment}.`);\n}\n\n/**\n * Extracts the basename of a file path with its extension removed.\n *\n * @param {string} filePath - Project-relative file path (e.g. `src/graph/builder.ts`).\n * @returns {string} The stem of the filename (e.g. `builder`).\n */\nfunction fileBasename(filePath: string): string {\n const name = filePath.slice(filePath.lastIndexOf(\"/\") + 1);\n return name.slice(0, name.lastIndexOf(\".\")) || name;\n}\n","/** Builds a ResponsibilityGraph assigning each file a semantic role based on its connectivity and feature membership. */\nimport { buildFeatureGraph } from \"../features/feature-graph\";\nimport type { FeatureDetectionOptions } from \"../features/index\";\nimport type { Graph } from \"../model\";\nimport { inferRole } from \"./infer-role\";\nimport type { ResponsibilityGraph } from \"./types\";\n\nexport type { ModuleResponsibility, ModuleRole, ResponsibilityGraph } from \"./types\";\n\n/**\n * Builds a responsibility map for every file in the graph.\n *\n * Each entry is derived entirely from data already present in the `FileNode`:\n * - `description` comes from the file's leading JSDoc (`FileNode.description`)\n * - `exports` are the exported symbol names\n * - `role` is inferred from file path and category via `inferRole`\n * - `featureHub` is resolved via `buildFeatureGraph` with default options\n *\n * Test files are included with `role: \"test\"` so callers can filter them if needed.\n *\n * @param {Graph} graph - The import graph to derive responsibilities from.\n * @param {FeatureDetectionOptions} [featureOptions] - Options forwarded to `buildFeatureGraph` (e.g. `minOutDegree`).\n * @returns {ResponsibilityGraph} A map from each file path to its `ModuleResponsibility`.\n */\nexport function buildResponsibilityGraph(\n graph: Graph,\n featureOptions?: FeatureDetectionOptions,\n): ResponsibilityGraph {\n const featureGraph = buildFeatureGraph(graph, featureOptions);\n\n // Build a reverse map: file path → feature hub name.\n const fileToHub = new Map<string, string>();\n for (const [featureName, domain] of featureGraph.features) {\n for (const filePath of domain.files) {\n fileToHub.set(filePath, featureName);\n }\n // The hub itself belongs to its own feature.\n fileToHub.set(domain.hub, featureName);\n }\n\n const result: ResponsibilityGraph = new Map();\n for (const node of graph.nodes.values()) {\n const hub = fileToHub.get(node.path);\n result.set(node.path, {\n path: node.path,\n role: inferRole(node),\n ...(node.description ? { description: node.description } : {}),\n exports: node.exports.map((exportedSym) => exportedSym.name),\n ...(hub ? { featureHub: hub } : {}),\n });\n }\n\n return result;\n}\n","import type { ImportEdge } from \"../types/node\";\n\n/**\n * @description Tracks which exported symbols of each visited node are \"affected\" by a change.\n *\n * Enables symbol-level pruning during graph traversal: if a node only imports `foo` and `foo`\n * was not among the changed symbols, that node is not considered affected and traversal stops there.\n */\nexport class SymbolTraversalContext {\n private affectedSymbols = new Map<string, Set<string>>();\n\n /**\n * @param {string} startPath - Relative path of the changed file; seeded with the given affected symbols.\n * @param {string[]} affectedSymbols - Symbol names that are considered changed. Pass `[\"*\"]` to treat the whole file as changed.\n */\n constructor(startPath: string, affectedSymbols: string[]) {\n // Callers that want namespace-import consumers to always be affected should include \"*\" explicitly.\n this.affectedSymbols.set(startPath, new Set([\"default\", ...affectedSymbols]));\n }\n\n /**\n * @description Checks whether `visitedNode` imports any affected symbol from `childPath` and,\n * if so, propagates the affected symbol set to `visitedNode` for the next traversal step.\n *\n * Both roles live in one method to avoid a second pass over the import edges — the check\n * and the update read the same edge, so splitting them would duplicate work.\n * @param {{ path: string; imports: ImportEdge[] }} visitedNode - The node currently being evaluated; its imports are inspected.\n * @param {string} childPath - The path it was reached from; used to look up the current affected symbols.\n * @returns {boolean} `true` if at least one imported symbol is affected and traversal should continue; `false` to prune.\n */\n public updateAffectedSymbols(\n visitedNode: { path: string; imports: ImportEdge[] },\n childPath: string,\n ): boolean {\n const currentSymbols = this.affectedSymbols.get(childPath) || new Set();\n\n const importEdge = visitedNode.imports.find((imp) => imp.toPath === childPath);\n if (!importEdge) return false;\n\n const importedSymbols = importEdge.symbols || [\"*\"];\n const relevantSymbols = new Set<string>();\n\n for (const sym of importedSymbols) {\n if (sym === \"*\" || currentSymbols.has(\"*\") || currentSymbols.has(sym)) {\n relevantSymbols.add(\"*\");\n }\n }\n\n if (relevantSymbols.size === 0) return false;\n\n const existing = this.affectedSymbols.get(visitedNode.path) || new Set();\n for (const symbol of relevantSymbols) existing.add(symbol);\n this.affectedSymbols.set(visitedNode.path, existing);\n return true;\n }\n}\n","/** Builds and queries a TypeGraph of interface, class, enum, and type-alias exports and the files that reference them. */\nimport type { ExportedSymbol, FileNode } from \"../types/node\";\nimport type { Graph } from \"./model\";\n\n/** Structural kind of a type export. */\nexport type TypeKind = \"interface\" | \"class\" | \"enum\" | \"type\";\n\n/**\n * A single type-like export extracted from a TypeScript source file.\n * Only interfaces, classes, enums, and type aliases are included —\n * plain functions and values are excluded.\n */\nexport interface TypeNode {\n /** Exported symbol name (e.g. `\"FileNode\"`). */\n name: string;\n /** Project-relative path of the file that exports this type. */\n file: string;\n /** Structural kind inferred from the export signature. */\n kind: TypeKind;\n /** JSDoc description attached to the export, if present. */\n doc?: string;\n}\n\n/**\n * A directed edge representing that one file imports a specific type from another file.\n */\nexport interface TypeEdge {\n /** Project-relative path of the importing file. */\n fromFile: string;\n /** Name of the imported type. */\n toType: string;\n /** Project-relative path of the file that defines the type. */\n toFile: string;\n}\n\n/**\n * Type-level view of the import graph.\n * Answers \"what types depend on X?\" and \"what does type X depend on?\"\n * at a fraction of the cost of sending the full graph.\n */\nexport interface TypeGraph {\n /**\n * All type-like exports in the graph.\n * Key format: `\"<file>::<typeName>\"` (e.g. `\"src/types/node.ts::FileNode\"`).\n */\n types: Map<string, TypeNode>;\n /** All import edges where the imported symbol is a known type. */\n edges: TypeEdge[];\n}\n\n/**\n * Result of a focused query for one named type.\n * A token-efficient answer to \"what types depend on X?\" and \"what does X use?\"\n */\nexport interface TypeQueryResult {\n /** The type that was queried. */\n type: TypeNode | null;\n /** Project-relative paths of files that import this type. */\n usedByFiles: string[];\n /** Types that the defining file imports from other files. */\n uses: TypeNode[];\n}\n\n/**\n * Infers the `TypeKind` from an export's signature string.\n * Matches the prefix patterns produced by `extractSignature` in the TS parser.\n *\n * @param signature - The signature string from `ExportedSymbol.signature`, if present.\n * @returns The inferred `TypeKind`.\n */\nfunction inferKind(signature: string | undefined): TypeKind {\n if (!signature) return \"type\";\n if (signature.startsWith(\"interface \")) return \"interface\";\n if (signature.startsWith(\"class \")) return \"class\";\n if (signature.startsWith(\"enum \")) return \"enum\";\n return \"type\";\n}\n\n/**\n * Returns `true` when a symbol is a type-like export that should appear in the type graph.\n *\n * Structural types (interface / class / enum) are always included.\n * In `type-only` files every export is treated as a type.\n * Plain functions and values (signatures without a structural prefix) are excluded\n * unless they live in a `type-only` file.\n *\n * @param sym - The exported symbol to test.\n * @param category - The file's category from the import graph.\n * @returns `true` if the symbol should be a `TypeNode`.\n */\nfunction isTypeExport(sym: ExportedSymbol, category: FileNode[\"category\"]): boolean {\n if (category === \"type-only\") return true;\n const sig = sym.signature ?? \"\";\n return sig.startsWith(\"interface \") || sig.startsWith(\"class \") || sig.startsWith(\"enum \");\n}\n\n/**\n * Builds a type-level view of the import graph by extracting all type-like exports\n * and the import edges that connect them.\n *\n * Only TypeScript and JavaScript files are considered — other file types carry no\n * type information usable for this graph.\n *\n * @param graph - The import graph to derive the type graph from.\n * @returns A `TypeGraph` with all type nodes and their dependency edges.\n */\nexport function buildTypeGraph(graph: Graph): TypeGraph {\n const types = new Map<string, TypeNode>();\n\n // Pass 1: collect type nodes from all TS/JS files.\n for (const node of graph.nodes.values()) {\n if (node.type !== \"typescript\" && node.type !== \"javascript\") continue;\n for (const exp of node.exports) {\n if (!isTypeExport(exp, node.category)) continue;\n const key = `${node.path}::${exp.name}`;\n types.set(key, {\n name: exp.name,\n file: node.path,\n kind: inferKind(exp.signature),\n ...(exp.doc ? { doc: exp.doc } : {}),\n });\n }\n }\n\n // Pass 2: build edges from import edges whose symbols resolve to known types.\n const edges: TypeEdge[] = [];\n for (const node of graph.nodes.values()) {\n if (node.type !== \"typescript\" && node.type !== \"javascript\") continue;\n for (const imp of node.imports) {\n if (!imp.toPath || imp.isExternal || !imp.symbols?.length) continue;\n for (const sym of imp.symbols) {\n if (types.has(`${imp.toPath}::${sym}`)) {\n edges.push({ fromFile: node.path, toType: sym, toFile: imp.toPath });\n }\n }\n }\n }\n\n return { types, edges };\n}\n\n/**\n * Queries the type graph for a specific named type, returning its direct dependents\n * (files that import it) and its direct dependencies (types it imports).\n *\n * When `typeName` is not found in the graph, `type` is `null` and both lists are empty.\n *\n * @param typeGraph - A previously built `TypeGraph`.\n * @param typeName - Exact exported name of the type to look up (e.g. `\"FileNode\"`).\n * @returns `TypeQueryResult` with the type node and its one-hop neighbours.\n */\nexport function queryTypeGraph(typeGraph: TypeGraph, typeName: string): TypeQueryResult {\n // Find the canonical TypeNode for the given name (use the first match if there are multiple files).\n let target: TypeNode | null = null;\n for (const typeNode of typeGraph.types.values()) {\n if (typeNode.name === typeName) {\n target = typeNode;\n break;\n }\n }\n\n if (!target) return { type: null, usedByFiles: [], uses: [] };\n\n const usedByFiles = new Set<string>();\n const usesMap = new Map<string, TypeNode>();\n\n for (const edge of typeGraph.edges) {\n // Files that import this type.\n if (edge.toType === typeName && edge.toFile === target.file) {\n usedByFiles.add(edge.fromFile);\n }\n // Types that the defining file itself imports.\n if (edge.fromFile === target.file) {\n const dep = typeGraph.types.get(`${edge.toFile}::${edge.toType}`);\n if (dep) usesMap.set(`${dep.file}::${dep.name}`, dep);\n }\n }\n\n return {\n type: target,\n usedByFiles: Array.from(usedByFiles),\n uses: Array.from(usesMap.values()),\n };\n}\n","/** Runs all registered monorepo detectors and returns the layout describing the detected tool and packages. */\nimport path from \"node:path\";\nimport { npmDetector } from \"./detectors/npm\";\nimport { nxDetector } from \"./detectors/nx\";\nimport { pnpmDetector } from \"./detectors/pnpm\";\n\nimport { turborepoDetector } from \"./detectors/turborepo\";\nimport { yarnDetector } from \"./detectors/yarn\";\nimport type { MonorepoDetector } from \"./registry\";\nimport { getMonorepoDetectors, registerMonorepoDetector } from \"./registry\";\nimport type { MonorepoLayout, WorkspacePackage } from \"./types\";\n\n// Register in priority order: orchestration tools first, then package managers.\nregisterMonorepoDetector(turborepoDetector);\nregisterMonorepoDetector(nxDetector);\nregisterMonorepoDetector(pnpmDetector);\nregisterMonorepoDetector(yarnDetector);\nregisterMonorepoDetector(npmDetector);\n\n/**\n * @description Runs all registered monorepo detectors against `rootDir` and merges\n * their results into a single `MonorepoLayout`. All matching detectors contribute\n * their `type` string and packages — so a Turborepo + pnpm repo will have\n * `types: [\"turborepo\", \"pnpm\"]` and packages from the pnpm detector.\n *\n * Packages are deduplicated by name: the first detector to emit a package name wins.\n * Returns `type: \"none\"` when no detector fires.\n */\nexport function detectMonorepo(\n rootDir: string,\n detectors: readonly MonorepoDetector[] = getMonorepoDetectors(),\n): MonorepoLayout {\n const abs = path.resolve(rootDir);\n const allPackages = new Map<string, WorkspacePackage>();\n const detectedTypes: string[] = [];\n\n for (const detector of detectors) {\n const pkgs = detector.detect(abs);\n if (pkgs === null) continue;\n detectedTypes.push(detector.type);\n for (const pkg of pkgs) {\n if (!allPackages.has(pkg.name)) allPackages.set(pkg.name, pkg);\n }\n }\n\n if (detectedTypes.length === 0) {\n return { root: abs, type: \"none\", types: [], packages: [], packageMap: new Map() };\n }\n\n const packages = Array.from(allPackages.values());\n return {\n root: abs,\n type: detectedTypes[0] as string,\n types: detectedTypes,\n packages,\n packageMap: new Map(packages.map((pkg) => [pkg.name, pkg])),\n };\n}\n\nexport type { MonorepoDetector } from \"./registry\";\nexport { registerMonorepoDetector } from \"./registry\";\nexport type { MonorepoLayout, WorkspacePackage } from \"./types\";\n","/** Monorepo detector for npm workspaces (package.json workspaces field). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects npm workspaces via `package.json` `\"workspaces\"` field.\n * Yields to the yarn detector when `yarn.lock` is present in the same root.\n */\nexport const npmDetector: MonorepoDetector = {\n type: \"npm\",\n detect(rootDir) {\n const pkgPath = path.join(rootDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n let workspaces: string[] | { packages?: string[] } | undefined;\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: string[] | { packages?: string[] };\n };\n workspaces = pkg.workspaces;\n } catch {\n return null;\n }\n\n if (!workspaces) return null;\n\n // Skip if yarn.lock present — yarn detector handles that repo\n if (fs.existsSync(path.join(rootDir, \"yarn.lock\"))) return null;\n\n const patterns: string[] = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);\n\n if (patterns.length === 0) return null;\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Shared helpers for monorepo detectors: package building, entry-point resolution, and glob pattern expansion. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { isDirectory, isFile } from \"./fs-utils\";\nimport type { WorkspacePackage } from \"./types\";\n\nexport { exists } from \"./fs-utils\";\n\n/**\n * @description Reads `package.json` from `pkgRoot` and builds a `WorkspacePackage`.\n * Returns `null` if no `package.json` exists, cannot be parsed, or the `name` field is absent.\n * @param {string} monorepoRoot - Absolute path to the monorepo root, used to compute `relativeRoot`.\n * @param {string} pkgRoot - Absolute path to the package directory to read.\n * @returns {WorkspacePackage | null} The built package descriptor, or `null` on failure.\n */\nexport function buildPackage(monorepoRoot: string, pkgRoot: string): WorkspacePackage | null {\n const pkgJsonPath = path.join(pkgRoot, \"package.json\");\n if (!fs.existsSync(pkgJsonPath)) return null;\n\n let pkgJson: { name?: string; main?: string; exports?: unknown } = {};\n try {\n pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\")) as typeof pkgJson;\n } catch {\n return null;\n }\n\n const name = pkgJson.name;\n if (!name) return null;\n\n return {\n name,\n root: pkgRoot,\n relativeRoot: path.relative(monorepoRoot, pkgRoot),\n entryPoints: resolveEntryPoints(pkgRoot, pkgJson),\n };\n}\n\n/**\n * @description Derives entry point absolute paths from a package's `package.json`.\n * Tries `exports[\".\"]`, `main`, and common conventions (`src/index.ts`, etc.) in that order.\n * Returns the first existing file, or the first candidate as a fallback when nothing exists on disk.\n * @param {string} pkgRoot - Absolute path to the package directory.\n * @param {{ main?: string; exports?: unknown }} pkgJson - Parsed `package.json` object.\n * @returns {string[]} A single-element array containing the resolved entry point absolute path.\n */\nexport function resolveEntryPoints(\n pkgRoot: string,\n pkgJson: { main?: string; exports?: unknown },\n): string[] {\n const candidates: string[] = [];\n\n if (pkgJson.exports) {\n const exp = pkgJson.exports;\n if (typeof exp === \"string\") {\n candidates.push(path.join(pkgRoot, exp));\n } else if (typeof exp === \"object\" && exp !== null) {\n const dot = (exp as Record<string, unknown>)[\".\"];\n if (typeof dot === \"string\") {\n candidates.push(path.join(pkgRoot, dot));\n } else if (typeof dot === \"object\" && dot !== null) {\n const src =\n (dot as Record<string, unknown>).import ??\n (dot as Record<string, unknown>).require ??\n (dot as Record<string, unknown>).default;\n if (typeof src === \"string\") candidates.push(path.join(pkgRoot, src));\n }\n }\n }\n\n if (pkgJson.main) candidates.push(path.join(pkgRoot, pkgJson.main));\n\n for (const c of [\"src/index.ts\", \"src/index.tsx\", \"index.ts\", \"index.tsx\", \"index.js\"]) {\n candidates.push(path.join(pkgRoot, c));\n }\n\n const existing = candidates.filter(isFile);\n return existing.length > 0 ? existing.slice(0, 1) : candidates.slice(0, 1);\n}\n\n/**\n * @description Resolves workspace glob patterns (e.g. `packages/*`) to `WorkspacePackage` entries.\n * Supports `*` (single directory segment) and `**` (recursive). Non-glob patterns are treated as literal paths.\n * @param {string} root - Absolute monorepo root directory used as the base for all patterns.\n * @param {string[]} patterns - Glob patterns from `package.json` `\"workspaces\"` or `pnpm-workspace.yaml`.\n * @returns {WorkspacePackage[]} All resolved packages found under the matching directories.\n */\nexport function resolveGlobPatterns(root: string, patterns: string[]): WorkspacePackage[] {\n const packages: WorkspacePackage[] = [];\n const seen = new Set<string>();\n\n for (const pattern of patterns) {\n const normalised = pattern.replace(/\\/$/, \"\").replace(/^\\.\\//, \"\");\n resolvePattern(root, normalised, seen, packages);\n }\n\n return packages;\n}\n\n/**\n * @description Dispatches a single normalised glob pattern to the appropriate resolver\n * based on whether it contains no wildcard, a `**` recursive glob, or a `*` shallow glob.\n * @param {string} root - Absolute monorepo root used as the base for path resolution.\n * @param {string} pattern - A single normalised pattern (trailing slash and leading `./` already stripped).\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolvePattern(\n root: string,\n pattern: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n if (!pattern.includes(\"*\")) {\n resolveLiteralPattern(root, pattern, seen, packages);\n return;\n }\n\n const segments = pattern.split(\"/\");\n\n if (segments.includes(\"**\")) {\n resolveRecursivePattern(root, segments, seen, packages);\n } else {\n resolveShallowPattern(root, segments, seen, packages);\n }\n}\n\n/**\n * @description Resolves a pattern with no wildcards as a literal directory path relative to `root`.\n * Adds a `WorkspacePackage` if the directory exists and has not been visited before.\n * @param {string} root - Absolute monorepo root used to join the literal path and compute `relativeRoot`.\n * @param {string} pattern - A literal (non-glob) relative path, e.g. `\"packages/core\"`.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveLiteralPattern(\n root: string,\n pattern: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const abs = path.join(root, pattern);\n if (seen.has(abs) || !isDirectory(abs)) return;\n seen.add(abs);\n const pkg = buildPackage(root, abs);\n if (pkg) packages.push(pkg);\n}\n\n/**\n * @description Resolves a `**` glob by walking all subdirectories under the base segment recursively.\n * If the pattern starts with `**` itself the walk begins at `root`; otherwise at the first segment.\n * @param {string} root - Absolute monorepo root passed through to `walkRecursive` for `relativeRoot` computation.\n * @param {string[]} segments - Path segments of the pattern split on `/`, must contain `\"**\"`.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveRecursivePattern(\n root: string,\n segments: string[],\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const base = path.join(root, segments[0] === \"**\" ? \"\" : (segments[0] ?? \"\"));\n walkRecursive(root, base, seen, packages);\n}\n\n/**\n * @description Resolves a single-`*` glob by listing every immediate subdirectory of the base path.\n * The base is everything before the first segment containing `*`, e.g. `packages` for `packages/*`.\n * @param {string} root - Absolute monorepo root used to join path segments and compute `relativeRoot`.\n * @param {string[]} segments - Path segments of the pattern split on `/`, must contain a `*` (but not `**`).\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction resolveShallowPattern(\n root: string,\n segments: string[],\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n const starIdx = segments.findIndex((segment) => segment.includes(\"*\"));\n const base = path.join(root, ...segments.slice(0, starIdx));\n\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(base, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const abs = path.join(base, entry.name);\n if (seen.has(abs)) continue;\n seen.add(abs);\n const pkg = buildPackage(root, abs);\n if (pkg) packages.push(pkg);\n }\n}\n\n/**\n * @description Recursively walks `dir` looking for directories that contain a `package.json`,\n * building a `WorkspacePackage` for each. Skips `node_modules` and hidden directories.\n * @param {string} monorepoRoot - Absolute path to the monorepo root, used to compute `relativeRoot`.\n * @param {string} dir - The directory to walk in this recursion step.\n * @param {Set<string>} seen - Set of already-visited absolute paths; updated in place to prevent duplicates.\n * @param {WorkspacePackage[]} packages - Accumulator array that receives discovered packages.\n */\nfunction walkRecursive(\n monorepoRoot: string,\n dir: string,\n seen: Set<string>,\n packages: WorkspacePackage[],\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n const abs = path.join(dir, entry.name);\n if (fs.existsSync(path.join(abs, \"package.json\")) && !seen.has(abs)) {\n seen.add(abs);\n const pkg = buildPackage(monorepoRoot, abs);\n if (pkg) packages.push(pkg);\n } else {\n walkRecursive(monorepoRoot, abs, seen, packages);\n }\n }\n}\n","/** Filesystem utilities for monorepo detectors: existence checks for files and directories. */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * @description Returns `true` when `name` exists inside `root`.\n * @param {string} root - Absolute directory path to search within.\n * @param {string} name - File or directory name to look for.\n * @returns {boolean} `true` if the entry exists, `false` otherwise.\n */\nexport function exists(root: string, name: string): boolean {\n return fs.existsSync(path.join(root, name));\n}\n\n/**\n * @description Returns `true` when `p` is an existing directory. Never throws.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a directory.\n */\nexport function isDirectory(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/**\n * @description Returns `true` when `p` is an existing regular file. Never throws.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a regular file.\n */\nexport function isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() === true;\n } catch {\n return false;\n }\n}\n","/** Monorepo detector for Nx workspaces (nx.json + project.json files). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { isFile } from \"../fs-utils\";\nimport type { MonorepoDetector } from \"../registry\";\nimport type { WorkspacePackage } from \"../types\";\n\n/**\n * @description Detects Nx workspaces by scanning for `project.json` files under `nx.json`.\n * Supports both package-based repos (with per-project `package.json`) and integrated repos\n * (no per-project `package.json` — name taken from `project.json`).\n */\nexport const nxDetector: MonorepoDetector = {\n type: \"nx\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"nx.json\"))) return null;\n\n const seen = new Set<string>();\n return walkForProjectJsonDirs(rootDir, rootDir, seen, 0)\n .map((pkgRoot) => buildNxPackage(rootDir, pkgRoot, path.join(pkgRoot, \"project.json\")))\n .filter((pkg): pkg is WorkspacePackage => pkg !== null);\n },\n};\n\n/**\n * @description Recursively walks `dir` up to 4 levels deep and returns absolute paths\n * of directories that contain a `project.json`. Skips `node_modules`, `.nx`, `dist`, and\n * hidden directories. Each directory is returned at most once via `seen`.\n * @param {string} rootDir - The monorepo root; unused in the recursion but kept for future use.\n * @param {string} dir - The directory to walk in this recursion step.\n * @param {Set<string>} seen - Set of already-returned absolute paths; updated in place.\n * @param {number} depth - Current recursion depth; returns early when greater than 4.\n * @returns {string[]} Absolute paths of all directories containing `project.json` under `dir`.\n */\nfunction walkForProjectJsonDirs(\n rootDir: string,\n dir: string,\n seen: Set<string>,\n depth: number,\n): string[] {\n if (depth > 4) return [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const found: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const name = entry.name;\n if (name.startsWith(\".\") || name === \"node_modules\" || name === \"dist\" || name === \".nx\")\n continue;\n const fullPath = path.join(dir, name);\n if (fs.existsSync(path.join(fullPath, \"project.json\")) && !seen.has(fullPath)) {\n seen.add(fullPath);\n found.push(fullPath);\n } else {\n found.push(...walkForProjectJsonDirs(rootDir, fullPath, seen, depth + 1));\n }\n }\n return found;\n}\n\ntype NxProjectJson = {\n name?: string;\n sourceRoot?: string;\n targets?: {\n build?: {\n options?: { main?: string; entryFile?: string };\n };\n };\n};\n\n/**\n * @description Builds a `WorkspacePackage` from an Nx `project.json`.\n * If a `package.json` is also present, its `name`, `main`, and `exports` fields take priority\n * over `project.json` — supporting both integrated and package-based Nx repos.\n * @param {string} monorepoRoot - Absolute monorepo root, used to compute `relativeRoot`.\n * @param {string} pkgRoot - Absolute path to the project directory.\n * @param {string} projJsonPath - Absolute path to the `project.json` file to read.\n * @returns {WorkspacePackage | null} The built package, or `null` when no usable name can be determined.\n */\nfunction buildNxPackage(\n monorepoRoot: string,\n pkgRoot: string,\n projJsonPath: string,\n): WorkspacePackage | null {\n let projJson: NxProjectJson = {};\n try {\n projJson = JSON.parse(fs.readFileSync(projJsonPath, \"utf-8\")) as NxProjectJson;\n } catch {\n return null;\n }\n\n let name = projJson.name;\n let pkgMain: string | undefined;\n let pkgExports: unknown;\n\n const pkgJsonPath = path.join(pkgRoot, \"package.json\");\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\")) as {\n name?: string;\n main?: string;\n exports?: unknown;\n };\n name = pkgJson.name ?? name;\n pkgMain = pkgJson.main;\n pkgExports = pkgJson.exports;\n } catch {\n /* use project.json values */\n }\n }\n\n if (!name) return null;\n\n return {\n name,\n root: pkgRoot,\n relativeRoot: path.relative(monorepoRoot, pkgRoot),\n entryPoints: resolveNxEntryPoints(pkgRoot, projJson, pkgMain, pkgExports),\n };\n}\n\n/**\n * @description Derives entry point paths for an Nx project in priority order:\n * `targets.build.options.main` → `package.json` exports/main → `sourceRoot/index.ts`\n * → common `src/index.ts` conventions. Returns the first existing file, or the first candidate\n * as a fallback when nothing exists on disk.\n * @param {string} pkgRoot - Absolute path to the project directory.\n * @param {NxProjectJson} projJson - Parsed `project.json` contents.\n * @param {string} [pkgMain] - The `main` field from `package.json`, if present.\n * @param {unknown} [pkgExports] - The `exports` field from `package.json`, if present.\n * @returns {string[]} A single-element array with the resolved entry point absolute path.\n */\nfunction resolveNxEntryPoints(\n pkgRoot: string,\n projJson: NxProjectJson,\n pkgMain?: string,\n pkgExports?: unknown,\n): string[] {\n const candidates: string[] = [];\n\n const buildMain =\n projJson.targets?.build?.options?.main ?? projJson.targets?.build?.options?.entryFile;\n if (buildMain) {\n const repoRootGuess = path.resolve(pkgRoot, \"../..\");\n candidates.push(path.resolve(repoRootGuess, buildMain));\n candidates.push(path.resolve(pkgRoot, buildMain));\n }\n\n if (pkgExports) {\n const exp = pkgExports;\n if (typeof exp === \"string\") candidates.push(path.join(pkgRoot, exp));\n else if (typeof exp === \"object\" && exp !== null) {\n const dot = (exp as Record<string, unknown>)[\".\"];\n if (typeof dot === \"string\") candidates.push(path.join(pkgRoot, dot));\n }\n }\n if (pkgMain) candidates.push(path.join(pkgRoot, pkgMain));\n\n if (projJson.sourceRoot) {\n const repoRootGuess = path.resolve(pkgRoot, \"../..\");\n const srcRoot = path.resolve(repoRootGuess, projJson.sourceRoot);\n candidates.push(path.join(srcRoot, \"index.ts\"), path.join(srcRoot, \"index.tsx\"));\n }\n\n for (const c of [\"src/index.ts\", \"src/index.tsx\", \"index.ts\", \"index.tsx\"]) {\n candidates.push(path.join(pkgRoot, c));\n }\n\n const existing = candidates.filter(isFile);\n return existing.length > 0 ? existing.slice(0, 1) : candidates.slice(0, 1);\n}\n","/** Monorepo detector for pnpm workspaces (pnpm-workspace.yaml). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects pnpm workspaces via `pnpm-workspace.yaml`.\n * Reads the `packages:` glob list and resolves each pattern to `WorkspacePackage` entries.\n */\nexport const pnpmDetector: MonorepoDetector = {\n type: \"pnpm\",\n detect(rootDir) {\n const yamlPath = path.join(rootDir, \"pnpm-workspace.yaml\");\n if (!fs.existsSync(yamlPath)) return null;\n\n let patterns: string[] = [];\n try {\n const parsed = yaml.load(fs.readFileSync(yamlPath, \"utf-8\")) as {\n packages?: string[];\n } | null;\n patterns = parsed?.packages ?? [];\n } catch {\n return null;\n }\n\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Monorepo detector for Turborepo (turbo.json), contributing the orchestrator type without enumerating packages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\n\n/**\n * @description Turborepo detector. Turborepo is an orchestration layer on top of an\n * existing package manager — it contributes its type to `MonorepoLayout.types` but\n * returns no packages itself. Packages are enumerated by the pnpm/yarn/npm detector\n * that fires alongside it.\n */\nexport const turborepoDetector: MonorepoDetector = {\n type: \"turborepo\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"turbo.json\"))) return null;\n // Signal presence without contributing packages — other detectors handle that.\n return [];\n },\n};\n","/** Monorepo detector for Yarn Classic/Berry workspaces (yarn.lock + workspaces field). */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { MonorepoDetector } from \"../registry\";\nimport { resolveGlobPatterns } from \"../shared\";\n\n/**\n * @description Detects Yarn Classic / Berry workspaces.\n * Requires both `yarn.lock` and a `package.json` `\"workspaces\"` field to fire.\n */\nexport const yarnDetector: MonorepoDetector = {\n type: \"yarn\",\n detect(rootDir) {\n if (!fs.existsSync(path.join(rootDir, \"yarn.lock\"))) return null;\n\n const pkgPath = path.join(rootDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n let workspaces: string[] | { packages?: string[] } | undefined;\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: string[] | { packages?: string[] };\n };\n workspaces = pkg.workspaces;\n } catch {\n return null;\n }\n\n if (!workspaces) return null;\n\n const patterns: string[] = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);\n\n if (patterns.length === 0) return null;\n return resolveGlobPatterns(rootDir, patterns);\n },\n};\n","/** Registry for MonorepoDetector plugins, allowing custom detectors to be added alongside the built-in ones. */\nimport type { WorkspacePackage } from \"./types\";\n\n/**\n * @description Contract for a tool-specific monorepo detector.\n * Each detector knows how to recognise one package manager or build orchestrator\n * and enumerate the packages it manages.\n */\nexport interface MonorepoDetector {\n /**\n * Identifier for this tool (e.g. `\"pnpm\"`, `\"nx\"`).\n * Included in `MonorepoLayout.types` when this detector fires.\n */\n readonly type: string;\n /**\n * @description Inspects `rootDir` and returns the workspace packages it manages.\n * Return `null` to signal \"this tool is not present here\" (detector does not fire).\n * Return an empty array to signal \"tool is present but manages no packages\" (detector fires, contributes its type).\n * @param {string} rootDir - Absolute path to the repository root to inspect.\n * @returns {WorkspacePackage[] | null} Discovered packages, an empty array if the tool is present but empty, or `null` if the tool is absent.\n */\n detect(rootDir: string): WorkspacePackage[] | null;\n}\n\nconst registry: MonorepoDetector[] = [];\n\n/**\n * @description Registers a monorepo detector. Detectors are run in registration order;\n * register higher-priority tools first (e.g. Turborepo before pnpm).\n * @param {MonorepoDetector} detector - The detector implementation to add to the registry.\n */\nexport function registerMonorepoDetector(detector: MonorepoDetector): void {\n registry.push(detector);\n}\n\n/**\n * @description Returns all registered detectors in registration order.\n * @returns {readonly MonorepoDetector[]} Detectors in the order they were registered.\n */\nexport function getMonorepoDetectors(): readonly MonorepoDetector[] {\n return registry;\n}\n","/** Parser registry: maps FileType values to parser functions and provides lookup by file type. */\nimport type { FileType } from \"../types/parse\";\nimport type { ParseResult } from \"./types\";\n\nexport type ParserFunction = (\n filePath: string,\n content: string,\n) => ParseResult | Promise<ParseResult>;\n\nconst parserRegistry = new Map<FileType, ParserFunction>();\n\n/**\n * @description Registers a parser function for a given file type, overwriting any\n * previously registered parser for that type.\n * @param type - The `FileType` key this parser should handle.\n * @param parser - The parsing function that extracts imports and tags from file content.\n */\nexport function registerParser(type: FileType, parser: ParserFunction) {\n parserRegistry.set(type, parser);\n}\n\n/**\n * @description Looks up the registered parser for the given file type.\n * @param type - The `FileType` to look up.\n * @returns The registered `ParserFunction`, or `undefined` if none has been registered for this type.\n */\nexport function getParserForType(type: FileType): ParserFunction | undefined {\n return parserRegistry.get(type);\n}\n","/** Per-field predicates used by matchNode() to test a FileNode against a single NodeQuery criterion. */\nimport type { FileNode } from \"../types/node\";\nimport type { NodeQuery } from \"./types\";\n\n/**\n * @description Exact-match comparison with an optional `!` prefix for negation.\n * @param {string} nodeValue - The node's field value.\n * @param {string} queryValue - The query's criterion value, optionally prefixed with `!`.\n * @returns {boolean} `true` if `nodeValue` satisfies `queryValue`.\n */\nfunction matchesStr(nodeValue: string, queryValue: string): boolean {\n if (queryValue.startsWith(\"!\")) return nodeValue !== queryValue.slice(1);\n return nodeValue === queryValue;\n}\n\n/**\n * @description Substring comparison with an optional `!` prefix for negation.\n * @param {string} nodePath - The node's path.\n * @param {string} queryPath - The query's path substring, optionally prefixed with `!`.\n * @returns {boolean} `true` if `nodePath` satisfies `queryPath`.\n */\nfunction matchesPath(nodePath: string, queryPath: string): boolean {\n if (queryPath.startsWith(\"!\")) return !nodePath.includes(queryPath.slice(1));\n return nodePath.includes(queryPath);\n}\n\n/**\n * @description A single filter criterion evaluated against a node. Returns `true` when the\n * node passes this criterion (including when the corresponding `query` field is unset, i.e.\n * a wildcard). Each matcher owns exactly one `NodeQuery` field, so adding a new filter key\n * means adding a new matcher to `NODE_MATCHERS` rather than editing `matchNode` itself.\n * @param {FileNode} node - The graph node to evaluate.\n * @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.\n * @param {Map<string, string[]>} reverseIndex - Optional reverse importer lookup, used by the `importedBy` matcher.\n * @returns {boolean} `true` if the node satisfies this single criterion.\n */\nexport type NodeMatcher = (\n node: FileNode,\n query: NodeQuery,\n reverseIndex: Map<string, string[]> | undefined,\n) => boolean;\n\n/** @description Matches `NodeQuery.category` against `FileNode.category`. */\nexport const matchCategory: NodeMatcher = (node, query) =>\n !query.category || matchesStr(node.category, query.category);\n\n/** @description Matches `NodeQuery.type` against `FileNode.type`. */\nexport const matchType: NodeMatcher = (node, query) =>\n !query.type || matchesStr(node.type, query.type);\n\n/** @description Matches `NodeQuery.path` as a substring of `FileNode.path`. */\nexport const matchPath: NodeMatcher = (node, query) =>\n !query.path || matchesPath(node.path, query.path);\n\n/** @description Matches `NodeQuery.isExternal` against whether the node has any external import. */\nexport const matchIsExternal: NodeMatcher = (node, query) => {\n if (query.isExternal === undefined) return true;\n const hasExternalImport = node.imports.some((importEdge) => importEdge.isExternal);\n return hasExternalImport === query.isExternal;\n};\n\n/**\n * @description Matches `NodeQuery.tags` using OR logic across positive entries; entries\n * prefixed with `!` act as mandatory exclusions evaluated independently of the positive set.\n */\nexport const matchTags: NodeMatcher = (node, query) => {\n if (!query.tags || query.tags.length === 0) return true;\n const positiveTags = query.tags.filter((tag) => !tag.startsWith(\"!\"));\n const negativeTags = query.tags.filter((tag) => tag.startsWith(\"!\")).map((tag) => tag.slice(1));\n if (\n positiveTags.length > 0 &&\n !positiveTags.some((tag) => node.tags.some((structuredTag) => structuredTag.name === tag))\n )\n return false;\n if (negativeTags.some((tag) => node.tags.some((structuredTag) => structuredTag.name === tag)))\n return false;\n return true;\n};\n\n/** @description Matches `NodeQuery.allTags` using AND logic — every entry must be present. */\nexport const matchAllTags: NodeMatcher = (node, query) =>\n !query.allTags?.length ||\n query.allTags.every((tag) => node.tags.some((structuredTag) => structuredTag.name === tag));\n\n/** @description Matches `NodeQuery.importsFile` as a substring of any import's `toPath`. */\nexport const matchImportsFile: NodeMatcher = (node, query) =>\n !query.importsFile ||\n node.imports.some((importEdge) => importEdge.toPath?.includes(query.importsFile as string));\n\n/** @description Matches `NodeQuery.importedBy` as a substring of any importer path in `reverseIndex`. */\nexport const matchImportedBy: NodeMatcher = (node, query, reverseIndex) => {\n if (query.importedBy === undefined) return true;\n const importerPaths = reverseIndex?.get(node.path) ?? [];\n return importerPaths.some((importerPath) => importerPath.includes(query.importedBy as string));\n};\n\n/** @description Matches `NodeQuery.minImports` — node's import count must be at least this value. */\nexport const matchMinImports: NodeMatcher = (node, query) =>\n query.minImports === undefined || node.imports.length >= query.minImports;\n\n/** @description Matches `NodeQuery.maxImports` — node's import count must be at most this value. */\nexport const matchMaxImports: NodeMatcher = (node, query) =>\n query.maxImports === undefined || node.imports.length <= query.maxImports;\n\n/** @description Matches `NodeQuery.minSize` — node's file size must be at least this value. */\nexport const matchMinSize: NodeMatcher = (node, query) =>\n query.minSize === undefined || node.size >= query.minSize;\n\n/** @description Matches `NodeQuery.maxSize` — node's file size must be at most this value. */\nexport const matchMaxSize: NodeMatcher = (node, query) =>\n query.maxSize === undefined || node.size <= query.maxSize;\n\n/** @description Matches `NodeQuery.hasDocstring` against whether `FileNode.description` is set. */\nexport const matchHasDocstring: NodeMatcher = (node, query) =>\n query.hasDocstring === undefined || !!node.description === query.hasDocstring;\n\n/**\n * @description Matches `NodeQuery.minCoverage`. Nodes with no coverage data are excluded\n * (treated as 101%, i.e. always above any real threshold) — matching the\n * \"uncovered by default\" convention.\n */\nexport const matchMinCoverage: NodeMatcher = (node, query) =>\n query.minCoverage === undefined || (node.coveragePct ?? 101) >= query.minCoverage;\n\n/**\n * @description Matches `NodeQuery.maxCoverage`. Nodes with no coverage data are included\n * (treated as 0%) — matching the \"uncovered by default\" convention.\n */\nexport const matchMaxCoverage: NodeMatcher = (node, query) =>\n query.maxCoverage === undefined || (node.coveragePct ?? 0) <= query.maxCoverage;\n\n/** @description Matches `NodeQuery.minExportUsage`. Nodes with no coupling data are excluded. */\nexport const matchMinExportUsage: NodeMatcher = (node, query) =>\n query.minExportUsage === undefined || (node.avgExportUsage ?? -1) >= query.minExportUsage;\n\n/** @description Matches `NodeQuery.maxExportUsage`. Nodes with no coupling data are included (treated as 0). */\nexport const matchMaxExportUsage: NodeMatcher = (node, query) =>\n query.maxExportUsage === undefined || (node.avgExportUsage ?? 0) <= query.maxExportUsage;\n\n/** @description All matchers, applied in order by `matchNode`. Add new filter keys here. */\nexport const NODE_MATCHERS: NodeMatcher[] = [\n matchCategory,\n matchType,\n matchPath,\n matchIsExternal,\n matchTags,\n matchAllTags,\n matchImportsFile,\n matchImportedBy,\n matchMinImports,\n matchMaxImports,\n matchMinSize,\n matchMaxSize,\n matchHasDocstring,\n matchMinCoverage,\n matchMaxCoverage,\n matchMinExportUsage,\n matchMaxExportUsage,\n];\n","/** Filters a graph by applying NodeQuery predicates: category, type, tag, path, imports, coverage, and more. */\nimport type { SerializedGraph } from \"../types/graph\";\nimport type { FileNode } from \"../types/node\";\nimport { NODE_MATCHERS } from \"./matchers\";\nimport type { NodeQuery } from \"./types\";\n\n/**\n * @description Tests whether a graph node satisfies all criteria in `query` by running it\n * through every matcher in `NODE_MATCHERS`. String fields use exact match with an optional\n * `!` prefix for negation. `tags` uses OR logic across positive entries; negated tags act\n * as mandatory exclusions. Adding a new query key requires adding a new matcher to\n * `NODE_MATCHERS`, not editing this function.\n * @param {FileNode} node - The graph node to evaluate.\n * @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.\n * @param {Map<string, string[]>} reverseIndex - Optional reverse importer lookup, required when `query.importedBy` is set.\n * @returns {boolean} `true` if the node passes every active filter criterion.\n */\nexport function matchNode(\n node: FileNode,\n query: NodeQuery,\n reverseIndex?: Map<string, string[]>,\n): boolean {\n return NODE_MATCHERS.every((matcher) => matcher(node, query, reverseIndex));\n}\n\n/**\n * @description Filters a serialized graph to only nodes matching all criteria in `query`,\n * then trims each node's import list to edges whose target is also in the result set.\n * Optionally sorts the result and applies a `limit`.\n * @param {SerializedGraph} graph - The serialized graph to filter.\n * @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.\n * @returns {SerializedGraph} A new `SerializedGraph` containing only the matching subgraph.\n */\nexport function filterGraph(graph: SerializedGraph, query: NodeQuery): SerializedGraph {\n const reverseIndex = new Map<string, string[]>();\n if (query.importedBy !== undefined) {\n for (const node of graph.nodes) {\n for (const imp of node.imports) {\n if (imp.toPath) {\n const arr = reverseIndex.get(imp.toPath) ?? [];\n arr.push(node.path);\n reverseIndex.set(imp.toPath, arr);\n }\n }\n }\n }\n\n const filteredNodes = graph.nodes.filter((node) => matchNode(node, query, reverseIndex));\n const nodePaths = new Set(filteredNodes.map((node) => node.path));\n\n const resultNodes = filteredNodes.map((node) => ({\n ...node,\n imports: node.imports.filter((imp) => !imp.toPath || nodePaths.has(imp.toPath)),\n }));\n\n if (query.sort) {\n resultNodes.sort((nodeA, nodeB) => {\n if (query.sort === \"size\") return nodeB.size - nodeA.size;\n if (query.sort === \"imports\") return nodeB.imports.length - nodeA.imports.length;\n if (query.sort === \"commitCount90d\")\n return (nodeB.commitCount90d ?? 0) - (nodeA.commitCount90d ?? 0);\n if (query.sort === \"exportUsage\")\n return (nodeB.avgExportUsage ?? 0) - (nodeA.avgExportUsage ?? 0);\n return 0;\n });\n }\n if (query.limit !== undefined) resultNodes.splice(query.limit);\n\n return {\n nodes: resultNodes,\n cycles:\n graph.cycles?.filter((cycle) => cycle.every((path) => nodePaths.has(path))) ?? undefined,\n };\n}\n","/** Parses a key:value query string into a structured NodeQuery for use with filterGraph. */\nimport type { NodeQuery } from \"./types\";\n\n/**\n * @description Parses a `\"key:value,key:value\"` query string into a structured `NodeQuery`.\n * String values support `\"!\"` prefix for negation. The `tag`/`tags` key may appear multiple\n * times; values are OR-matched (negated entries act as exclusions). `tag:a+b` maps to `allTags`.\n * @param {string} queryString - Comma-separated `key:value` pairs, e.g. `\"category:logic,tag:auth\"`.\n * @returns {NodeQuery} The structured query object ready for use with `filterGraph` or `matchNode`.\n */\nexport function parseQuery(queryString: string): NodeQuery {\n const query: NodeQuery = {};\n const parts = queryString.split(\",\");\n\n for (const part of parts) {\n const colonIdx = part.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = part.slice(0, colonIdx).trim().toLowerCase();\n const value = part.slice(colonIdx + 1).trim();\n if (!key || !value) continue;\n\n switch (key) {\n case \"category\":\n query.category = value;\n break;\n case \"type\":\n query.type = value;\n break;\n case \"tag\":\n case \"tags\":\n if (value.includes(\"+\")) {\n query.allTags = [...(query.allTags ?? []), ...value.split(\"+\")];\n } else {\n query.tags = [...(query.tags ?? []), value];\n }\n break;\n case \"path\":\n query.path = value;\n break;\n case \"external\":\n query.isExternal = value.toLowerCase() === \"true\";\n break;\n case \"importsfile\":\n query.importsFile = value;\n break;\n case \"importedby\":\n query.importedBy = value;\n break;\n case \"minimports\":\n query.minImports = parseInt(value, 10);\n break;\n case \"maximports\":\n query.maxImports = parseInt(value, 10);\n break;\n case \"minsize\":\n query.minSize = parseInt(value, 10);\n break;\n case \"maxsize\":\n query.maxSize = parseInt(value, 10);\n break;\n case \"sort\":\n query.sort = value as \"size\" | \"imports\" | \"commitCount90d\" | \"exportUsage\";\n break;\n case \"limit\":\n query.limit = parseInt(value, 10);\n break;\n case \"hasdocstring\":\n query.hasDocstring = value.toLowerCase() !== \"false\";\n break;\n case \"mincoverage\":\n query.minCoverage = parseInt(value, 10);\n break;\n case \"maxcoverage\":\n query.maxCoverage = parseInt(value, 10);\n break;\n case \"minexportusage\":\n query.minExportUsage = parseFloat(value);\n break;\n case \"maxexportusage\":\n query.maxExportUsage = parseFloat(value);\n break;\n }\n }\n\n return query;\n}\n","/** Writes tag annotations into test files using a framework-specific strategy. */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { loadMokoshConfig } from \"../config\";\nimport type { Graph } from \"../graph\";\nimport { createStrategies, getStrategyForFile, type TagApplierStrategy } from \"./strategies\";\n\n// Valid tag names must be simple identifiers; colons (node:fs), slashes, and @ sigils are excluded.\nconst VALID_TAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]{1,}$/;\n\n// Only filename-derived import-kind tags qualify for writing. comment-marker tags are excluded\n// because collectStringLiteralAtTags extracts @word from all string literals, including external\n// package names in import paths (e.g. \"@modelcontextprotocol/sdk\" → tag \"modelcontextprotocol\").\nconst ALLOWED_TAG_KINDS = new Set([\"import\"]);\n\n// Generic structural names that appear in nearly every project and carry no domain signal.\nconst GENERIC_TAG_BLOCKLIST = new Set([\n \"common\",\n \"fixture\",\n \"fixtures\",\n \"helper\",\n \"helpers\",\n \"index\",\n \"main\",\n \"mock\",\n \"mocks\",\n \"setup\",\n \"shared\",\n \"spec\",\n \"test\",\n \"tests\",\n \"types\",\n \"util\",\n \"utils\",\n]);\n\n/**\n * @description Result for a single file processed by {@link applyTagsToFile}.\n */\nexport interface ApplyTagsFileResult {\n /** Project-relative path of the test file. */\n path: string;\n /** `\"updated\"` when the file was rewritten, `\"unchanged\"` when tags already matched, `\"error\"` on I/O failure. */\n status: \"updated\" | \"unchanged\" | \"error\";\n /** Present only when status is `\"error\"`. */\n error?: string;\n}\n\n/**\n * @description Aggregate result returned by {@link applyTags} after processing all test nodes.\n */\nexport interface ApplyTagsResult {\n /** Number of files that were written (or would have been written in dry-run mode). */\n updated: number;\n /** Number of files where the existing tags already matched the computed tags. */\n unchanged: number;\n /** Number of files that could not be read or written. */\n errors: number;\n /** Per-file breakdown. */\n files: ApplyTagsFileResult[];\n}\n\n/**\n * @description Reads a single test file, delegates tag injection to the appropriate strategy,\n * and writes the result back to disk (unless `dryRun` is true).\n * @param {string} absPath - Absolute path of the test file to update.\n * @param {string[]} tags - Computed tag names (filtered, sorted) to write.\n * @param {boolean} dryRun - When true, computes the change but skips the `fs.writeFile` call.\n * @param {TagApplierStrategy[]} strategies - Ordered strategy list; first matching strategy wins.\n * @returns {Promise<ApplyTagsFileResult>} Result object with path and status.\n */\nexport async function applyTagsToFile(\n absPath: string,\n tags: string[],\n dryRun: boolean,\n strategies: TagApplierStrategy[],\n): Promise<ApplyTagsFileResult> {\n let original: string;\n try {\n original = await fs.readFile(absPath, \"utf8\");\n } catch (err) {\n return { path: absPath, status: \"error\", error: String(err) };\n }\n\n const strategy = getStrategyForFile(absPath, strategies);\n if (!strategy) return { path: absPath, status: \"unchanged\" };\n\n const newContent = strategy.apply(absPath, original, tags);\n if (newContent === original) return { path: absPath, status: \"unchanged\" };\n\n if (!dryRun) await fs.writeFile(absPath, newContent, \"utf8\");\n return { path: absPath, status: \"updated\" };\n}\n\n/**\n * @description Iterates every test node in the graph, extracts `\"import\"` kind tags that pass\n * a name validity check and generic-name blocklist, then delegates writing to the strategy\n * selected by `mokosh.config.*` (`tagApplier.framework`, default `\"vitest\"`). Non-test nodes\n * are skipped.\n * @param {Graph} graph - The fully-enriched dependency graph.\n * @param {string} rootDir - Absolute path to the project root.\n * @param {{ dryRun: boolean }} options - Pass `dryRun: true` to preview changes without disk writes.\n * @returns {Promise<ApplyTagsResult>} Aggregate result with per-file status breakdown.\n */\nexport async function applyTags(\n graph: Graph,\n rootDir: string,\n options: { dryRun: boolean },\n): Promise<ApplyTagsResult> {\n const config = loadMokoshConfig(rootDir);\n const framework = config.tagApplier?.framework ?? \"vitest\";\n const frameworkOverrides = config.tagApplier?.frameworkOverrides ?? {};\n const strategies = createStrategies(framework, frameworkOverrides, rootDir);\n\n const result: ApplyTagsResult = { updated: 0, unchanged: 0, errors: 0, files: [] };\n\n for (const node of graph.nodes.values()) {\n if (node.category !== \"test\") continue;\n\n const seen = new Set<string>();\n const tagNames: string[] = [];\n for (const tag of node.tags) {\n if (!ALLOWED_TAG_KINDS.has(tag.kind)) continue;\n if (!VALID_TAG_NAME_RE.test(tag.name)) continue;\n if (GENERIC_TAG_BLOCKLIST.has(tag.name.toLowerCase())) continue;\n if (!seen.has(tag.name)) {\n seen.add(tag.name);\n tagNames.push(tag.name);\n }\n }\n tagNames.sort();\n\n const absPath = path.resolve(rootDir, node.path);\n const fileResult = await applyTagsToFile(absPath, tagNames, options.dryRun, strategies);\n fileResult.path = node.path;\n result.files.push(fileResult);\n if (fileResult.status === \"updated\") result.updated++;\n else if (fileResult.status === \"unchanged\") result.unchanged++;\n else result.errors++;\n }\n\n return result;\n}\n","/**\n * Strategy registry.\n *\n * Two categories of strategy:\n * Language strategies — auto-selected by file extension, always active regardless of config:\n * Gherkin (.feature), Pytest (.py), Go (*_test.go)\n * Framework strategies — selected per file by import-specifier detection for TS/JS files\n * where multiple frameworks are common: Vitest, Playwright, Cypress,\n * Jest. A repo mixing frameworks (e.g. Jest for unit tests, Playwright\n * for e2e) gets each file tagged in its own framework's native format.\n *\n * Lookup order: language strategies checked first (narrow extension predicates), the\n * auto-detecting framework strategy checked last for any TS/JS file the language strategies\n * don't claim.\n */\n\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport { CypressStrategy } from \"./cypress\";\nimport { GherkinStrategy } from \"./gherkin\";\nimport { matchesGlob } from \"./glob\";\nimport { GoStrategy } from \"./go\";\nimport { JestStrategy } from \"./jest\";\nimport { PlaywrightStrategy } from \"./playwright\";\nimport { PytestStrategy } from \"./pytest\";\nimport { TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy, TagFramework } from \"./types\";\nimport { VitestStrategy } from \"./vitest\";\n\nexport type { TagApplierStrategy, TagFramework };\n\nconst FRAMEWORK_STRATEGIES: Record<TagFramework, () => TagApplierStrategy> = {\n vitest: () => new VitestStrategy(),\n playwright: () => new PlaywrightStrategy(),\n cypress: () => new CypressStrategy(),\n jest: () => new JestStrategy(),\n};\n\n// Import specifiers that unambiguously identify which test framework a file uses.\nconst FRAMEWORK_IMPORT_MARKERS: Record<string, TagFramework> = {\n \"@playwright/test\": \"playwright\",\n cypress: \"cypress\",\n \"@jest/globals\": \"jest\",\n vitest: \"vitest\",\n};\n\n/**\n * @description Inspects a TS/JS file's top-level import declarations and returns the test\n * framework they identify, or null when no known framework import is present (e.g. a file\n * relying on Vitest/Jest `globals: true` with no explicit import).\n * @param {string} source - File source text.\n * @returns {TagFramework | null} The detected framework, or null if undetermined.\n */\nexport function detectFrameworkFromImports(source: string): TagFramework | null {\n const sf = ts.createSourceFile(\"detect.ts\", source, ts.ScriptTarget.Latest, true);\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;\n const framework = FRAMEWORK_IMPORT_MARKERS[stmt.moduleSpecifier.text];\n if (framework) return framework;\n }\n return null;\n}\n\n/**\n * @description Composite strategy for TS/JS files: detects the test framework from each file's\n * own imports and delegates to that framework's strategy. When detection is inconclusive\n * (e.g. `globals: true` with no explicit import), falls back to the first `frameworkOverrides`\n * glob pattern (checked in config order) that matches the file's project-relative path, then\n * to the scalar `defaultFramework`. This lets a single repo mix Jest/Vitest/Playwright/Cypress\n * test files and have each tagged in its native format.\n */\nclass AutoFrameworkStrategy implements TagApplierStrategy {\n readonly name = \"auto\";\n\n constructor(\n private readonly rootDir: string,\n private readonly defaultFramework: TagFramework,\n private readonly frameworkOverrides: [pattern: string, framework: TagFramework][],\n ) {}\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const framework =\n detectFrameworkFromImports(source) ?? this.matchOverride(absPath) ?? this.defaultFramework;\n const strategy = (FRAMEWORK_STRATEGIES[framework] ?? FRAMEWORK_STRATEGIES.vitest)();\n return strategy.apply(absPath, source, tags);\n }\n\n private matchOverride(absPath: string): TagFramework | null {\n const relPath = path.relative(this.rootDir, absPath).split(path.sep).join(\"/\");\n for (const [pattern, framework] of this.frameworkOverrides) {\n if (matchesGlob(pattern, relPath)) return framework;\n }\n return null;\n }\n}\n\n/**\n * @description Returns the ordered list of strategies to use for tag annotation.\n * Language strategies (Gherkin, Pytest, Go) are always included and checked first.\n * The auto-detecting framework strategy is appended last and handles TS/JS files.\n * @param {TagFramework} defaultFramework - Fallback TS/JS test framework used only when a file\n * has no detectable framework import and no matching `frameworkOverrides` pattern. Defaults to\n * `\"vitest\"`.\n * @param {Record<string, TagFramework>} frameworkOverrides - Path-glob pattern (project-relative)\n * to fallback framework. Checked in object key order; the first pattern that matches a file's\n * path wins. Only consulted when the file's own imports don't reveal a framework.\n * @param {string} rootDir - Absolute project root, used to compute each file's project-relative\n * path for matching against `frameworkOverrides` patterns.\n * @returns {TagApplierStrategy[]} Strategies in priority order.\n */\nexport function createStrategies(\n defaultFramework: TagFramework = \"vitest\",\n frameworkOverrides: Record<string, TagFramework> = {},\n rootDir: string = process.cwd(),\n): TagApplierStrategy[] {\n return [\n new GherkinStrategy(), // .feature\n new PytestStrategy(), // .py\n new GoStrategy(), // *_test.go\n new AutoFrameworkStrategy(rootDir, defaultFramework, Object.entries(frameworkOverrides)), // TS/JS, framework detected per file\n ];\n}\n\n/**\n * @description Finds the first strategy in the list that declares it can handle the file.\n * @param {string} absPath - Absolute file path.\n * @param {TagApplierStrategy[]} strategies - Ordered candidate strategies.\n * @returns {TagApplierStrategy | null} The matched strategy, or null if none applies.\n */\nexport function getStrategyForFile(\n absPath: string,\n strategies: TagApplierStrategy[],\n): TagApplierStrategy | null {\n return strategies.find((strategy) => strategy.canHandle(absPath)) ?? null;\n}\n","/**\n * Tag applier strategy for Cypress with @cypress/grep: injects { tags: ['@tag'] } into\n * describe/it/context calls.\n *\n * Requires: `npm install --save-dev @cypress/grep`\n * Setup: add `require('@cypress/grep/src/support')()` in cypress/support/e2e.ts\n * Filter at CI time with: `cypress run --env grepTags=@tagname`\n *\n * @see https://github.com/cypress-io/cypress/tree/develop/npm/grep\n */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nfunction toCypressLiteral(tags: string[]): string {\n // @cypress/grep convention: prefix each tag with '@'\n return toArrayLiteral(tags.map((tag) => `@${tag}`));\n}\n\nfunction normaliseExisting(raw: string[]): string[] {\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\nexport class CypressStrategy implements TagApplierStrategy {\n readonly name = \"cypress\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return source;\n\n const rawExisting = readArrayProp(calls[0]!, \"tags\", sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(normaliseExisting(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tags\", sf)\n : buildInjectReplacement(call, \"tags\", toCypressLiteral(sortedTags), sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\n }\n}\n","/** Shared TypeScript AST helpers for framework-specific tag injection strategies. */\nimport ts from \"typescript\";\n\nexport interface Replacement {\n start: number;\n end: number;\n text: string;\n}\n\nconst ANNOTATABLE_NAMES = new Set([\"describe\", \"test\", \"it\"]);\n\n/** Returns top-level describe/test/it call expressions from a parsed source file. */\nexport function findTopLevelCalls(sourceFile: ts.SourceFile): ts.CallExpression[] {\n const calls: ts.CallExpression[] = [];\n for (const stmt of sourceFile.statements) {\n if (!ts.isExpressionStatement(stmt)) continue;\n const expr = stmt.expression;\n if (!ts.isCallExpression(expr)) continue;\n const callee = expr.expression;\n if (ts.isIdentifier(callee) && ANNOTATABLE_NAMES.has(callee.text)) {\n calls.push(expr);\n }\n // Also handle property-access forms: test.describe, test.skip, etc.\n if (\n ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.expression) &&\n ANNOTATABLE_NAMES.has(callee.expression.text)\n ) {\n calls.push(expr);\n }\n }\n return calls;\n}\n\n/**\n * @description Reads an options-object argument from a call expression and extracts\n * the value of a named array property (e.g. `tags` or `tag`).\n * @param {ts.CallExpression} call - The call expression to inspect.\n * @param {string} propName - Name of the property to read from the options object.\n * @param {ts.SourceFile} sf - Source file needed for position information.\n * @returns {string[] | null} The array contents, or null if the property is not found.\n */\nexport function readArrayProp(\n call: ts.CallExpression,\n propName: string,\n sf: ts.SourceFile,\n): string[] | null {\n void sf; // used by callers for getStart/getEnd, not needed here\n for (const arg of call.arguments) {\n if (!ts.isObjectLiteralExpression(arg)) continue;\n const prop = arg.properties.find(\n (candidate): candidate is ts.PropertyAssignment =>\n ts.isPropertyAssignment(candidate) &&\n ts.isIdentifier(candidate.name) &&\n candidate.name.text === propName,\n );\n if (!prop || !ts.isArrayLiteralExpression(prop.initializer)) continue;\n return prop.initializer.elements.filter(ts.isStringLiteral).map((element) => element.text);\n }\n return null;\n}\n\n/**\n * @description Builds the source replacement needed to write an array property (e.g.\n * `tags` or `tag`) into a single call expression. Handles three cases:\n * 1. No options arg yet — inserts `{ <prop>: <value> }, ` before the last argument.\n * 2. Options object exists with the property — replaces the array in-place.\n * 3. Options object exists without the property — appends it before the closing brace.\n * @param {ts.CallExpression} call - The call expression to modify.\n * @param {string} propName - Name of the options property to inject (e.g. `\"tags\"` or `\"tag\"`).\n * @param {string} tagsLiteral - The serialised array literal to write (e.g. `'[\"a\", \"b\"]'`).\n * @param {ts.SourceFile} sf - Source file for position resolution.\n * @returns {Replacement | null} The replacement descriptor, or null when the call has no arguments.\n */\nexport function buildInjectReplacement(\n call: ts.CallExpression,\n propName: string,\n tagsLiteral: string,\n sf: ts.SourceFile,\n): Replacement | null {\n if (call.arguments.length === 0) return null;\n\n for (let i = 1; i < call.arguments.length; i++) {\n const arg = call.arguments[i]!;\n if (!ts.isObjectLiteralExpression(arg)) continue;\n\n const existingProp = arg.properties.find(\n (prop): prop is ts.PropertyAssignment =>\n ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName,\n );\n\n if (existingProp) {\n return {\n start: existingProp.initializer.getStart(sf),\n end: existingProp.initializer.getEnd(),\n text: tagsLiteral,\n };\n }\n\n const closeBrace = arg.getEnd() - 1;\n return {\n start: closeBrace,\n end: closeBrace,\n text: `${arg.properties.length > 0 ? \", \" : \"\"}${propName}: ${tagsLiteral}`,\n };\n }\n\n // No options object — insert before the callback (last argument)\n const callback = call.arguments[call.arguments.length - 1]!;\n return {\n start: callback.getStart(sf),\n end: callback.getStart(sf),\n text: `{ ${propName}: ${tagsLiteral} }, `,\n };\n}\n\n/**\n * @description Builds the replacement to remove a previously injected options property.\n * When the options object has only the target property, the whole options arg is removed.\n * When it has other properties, only the target property is removed.\n * @param {ts.CallExpression} call - The call expression to modify.\n * @param {string} propName - Name of the property to remove.\n * @param {ts.SourceFile} sf - Source file for position resolution.\n * @returns {Replacement | null} The replacement descriptor, or null when nothing to remove.\n */\nexport function buildRemoveReplacement(\n call: ts.CallExpression,\n propName: string,\n sf: ts.SourceFile,\n): Replacement | null {\n const args = call.arguments;\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (!ts.isObjectLiteralExpression(arg)) continue;\n\n const idx = arg.properties.findIndex(\n (prop): prop is ts.PropertyAssignment =>\n ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName,\n );\n if (idx < 0) continue;\n\n if (arg.properties.length === 1) {\n // Remove the entire options argument including the preceding `, `\n return { start: args[i - 1]!.getEnd(), end: arg.getEnd(), text: \"\" };\n }\n\n const prop = arg.properties[idx]!;\n if (idx === arg.properties.length - 1) {\n // Last property — also remove the preceding comma\n return { start: arg.properties[idx - 1]!.getEnd(), end: prop.getEnd(), text: \"\" };\n }\n // Not last — remove the property and the following separator\n return { start: prop.getStart(sf), end: arg.properties[idx + 1]!.getStart(sf), text: \"\" };\n }\n return null;\n}\n\n/** Applies a list of replacements to a source string in reverse-position order. */\nexport function applyReplacements(source: string, replacements: Replacement[]): string {\n const sorted = [...replacements].sort((left, right) => right.start - left.start);\n let result = source;\n for (const replacement of sorted) {\n result = result.slice(0, replacement.start) + replacement.text + result.slice(replacement.end);\n }\n return result;\n}\n\n/** Serialises a list of string tag names to an inline array literal: `[\"a\", \"b\"]`. */\nexport function toArrayLiteral(tags: string[]): string {\n return `[${tags.map((tag) => JSON.stringify(tag)).join(\", \")}]`;\n}\n\nexport const TS_EXTENSIONS = new Set([\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n]);\n","/**\n * Tag applier strategy for Gherkin .feature files: writes a `# <mokosh-tags>` comment block\n * with native `@tagname` lines before the Feature: declaration.\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst BLOCK_REGEX = /# <mokosh-tags>[\\s\\S]*?# <\\/mokosh-tags>\\n*/;\nconst EXISTING_TAG_REGEX = /^@([a-zA-Z0-9_-]+)/gm;\n\nfunction buildBlock(tags: string[]): string {\n return (\n [\"# <mokosh-tags>\", ...tags.map((tag) => `@${tag}`), \"# </mokosh-tags>\"].join(\"\\n\") + \"\\n\\n\"\n );\n}\n\nfunction readManualTags(content: string): Set<string> {\n const found = new Set<string>();\n EXISTING_TAG_REGEX.lastIndex = 0;\n let match = EXISTING_TAG_REGEX.exec(content);\n while (match !== null) {\n if (match[1]) found.add(match[1]);\n match = EXISTING_TAG_REGEX.exec(content);\n }\n return found;\n}\n\nexport class GherkinStrategy implements TagApplierStrategy {\n readonly name = \"gherkin\";\n\n canHandle(absPath: string): boolean {\n return path.extname(absPath).toLowerCase() === \".feature\";\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const manualContent = source.replace(BLOCK_REGEX, \"\");\n const manualTags = readManualTags(manualContent);\n const netNewTags = tags.filter((tag) => !manualTags.has(tag));\n\n const newBlock = netNewTags.length > 0 ? buildBlock(netNewTags) : \"\";\n\n if (BLOCK_REGEX.test(source)) {\n return source.replace(BLOCK_REGEX, newBlock);\n }\n if (newBlock) {\n return source.replace(/^(Feature:)/m, `${newBlock}$1`);\n }\n return source;\n }\n}\n","/** Dependency-free glob matcher used to select a fallback framework by file path. */\n\n/**\n * @description Tests whether a project-relative path matches a glob pattern. Supports `**`\n * (any characters, including `/`), `*` (any characters except `/`), and `?` (a single\n * non-`/` character). Both `pattern` and `relPath` are normalized to `/`-separated form\n * before matching, so callers on Windows don't need to pre-normalize.\n * @param {string} pattern - Glob pattern, e.g. `\"tests/e2e/**\"`.\n * @param {string} relPath - Project-relative file path to test against the pattern.\n * @returns {boolean} True when `relPath` matches `pattern`.\n */\nexport function matchesGlob(pattern: string, relPath: string): boolean {\n const normalizedPattern = pattern.replace(/\\\\/g, \"/\");\n const normalizedPath = relPath.replace(/\\\\/g, \"/\");\n\n let regexSource = \"\";\n for (let i = 0; i < normalizedPattern.length; i++) {\n const char = normalizedPattern[i];\n if (char === \"*\") {\n if (normalizedPattern[i + 1] === \"*\") {\n regexSource += \".*\";\n i++;\n } else {\n regexSource += \"[^/]*\";\n }\n } else if (char === \"?\") {\n regexSource += \"[^/]\";\n } else if (char !== undefined) {\n regexSource += char.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n }\n\n return new RegExp(`^${regexSource}$`).test(normalizedPath);\n}\n","/**\n * Tag applier strategy for Go test files (*_test.go): writes a `//go:build` constraint\n * using a `mokosh_<tag>` prefix so the tags remain opt-in and don't affect normal builds.\n *\n * Example output (inserted before the package declaration):\n * //go:build mokosh_auth || mokosh_parseArgs\n *\n * The `||` (OR) semantics mean: include this file when ANY of the listed tags is active.\n * Filter at CI time with: `go test -tags mokosh_auth ./...`\n *\n * If the file already has a non-mokosh `//go:build` line (e.g. `//go:build integration`),\n * the strategy leaves it untouched and writes a separate mokosh build tag line.\n *\n * Note: Go has no runtime test-tag system comparable to pytest marks or Vitest tags.\n * Build tags are the closest standard mechanism. Teams preferring a non-build-constraint\n * approach may skip this strategy by not using mokosh with Go test files.\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst MOKOSH_BUILD_TAG_RE = /^\\/\\/go:build mokosh_[^\\n]+\\n/m;\nconst PACKAGE_LINE_RE = /^package\\s+\\S+/m;\n\nfunction buildBuildTag(tags: string[]): string {\n const constraints = tags.map((tag) => `mokosh_${tag}`).join(\" || \");\n return `//go:build ${constraints}\\n`;\n}\n\nfunction readExistingTags(source: string): string[] | null {\n const match = MOKOSH_BUILD_TAG_RE.exec(source);\n if (!match) return null;\n const line = match[0]!;\n const re = /mokosh_([a-zA-Z0-9_-]+)/g;\n const tags: string[] = [];\n let tagMatch = re.exec(line);\n while (tagMatch !== null) {\n if (tagMatch[1]) tags.push(tagMatch[1]);\n tagMatch = re.exec(line);\n }\n return tags;\n}\n\nexport class GoStrategy implements TagApplierStrategy {\n readonly name = \"go\";\n\n canHandle(absPath: string): boolean {\n const base = path.basename(absPath);\n return base.endsWith(\"_test.go\");\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const existing = readExistingTags(source);\n const sortedTags = [...tags].sort();\n\n // Idempotency check\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n if (tags.length === 0) {\n return source.replace(MOKOSH_BUILD_TAG_RE, \"\");\n }\n\n const buildTag = buildBuildTag(sortedTags);\n\n if (existing !== null) {\n return source.replace(MOKOSH_BUILD_TAG_RE, buildTag);\n }\n\n // Insert before the package declaration\n const packageMatch = PACKAGE_LINE_RE.exec(source);\n if (!packageMatch) return source;\n\n const insertAt = packageMatch.index!;\n return source.slice(0, insertAt) + buildTag + \"\\n\" + source.slice(insertAt);\n }\n}\n","/**\n * Tag applier strategy for Jest: writes a `@group` docblock pragma at the top of the file.\n * Jest has no built-in tag/grep mechanism; `jest-runner-groups` is the de-facto standard for\n * file-level tag filtering, reading a `/** @group tagname *\\/` docblock above the imports.\n *\n * Example output:\n * /**\n * * @group auth\n * * @group parseArgs\n * *\\/\n * import { describe, test } from \"@jest/globals\";\n *\n * Filter at CI time with: `jest --group=auth`\n * Requires: `npm install --save-dev jest-runner-groups` and `runner: \"jest-runner-groups\"` in\n * the Jest config.\n * @see https://github.com/facebook-atom/jest-runner-groups\n */\nimport path from \"node:path\";\nimport { TS_EXTENSIONS } from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nconst GROUP_BLOCK_RE = /^\\/\\*\\*\\n(?: \\* @group .+\\n)+ \\*\\/\\n+/;\nconst GROUP_LINE_RE = /^ \\* @group (.+)$/gm;\n\nfunction buildBlock(tags: string[]): string {\n return [\"/**\", ...tags.map((tag) => ` * @group ${tag}`), \" */\"].join(\"\\n\") + \"\\n\\n\";\n}\n\nfunction readExistingGroups(block: string): string[] {\n const found: string[] = [];\n GROUP_LINE_RE.lastIndex = 0;\n let match = GROUP_LINE_RE.exec(block);\n while (match !== null) {\n if (match[1]) found.push(match[1]);\n match = GROUP_LINE_RE.exec(block);\n }\n return found;\n}\n\nexport class JestStrategy implements TagApplierStrategy {\n readonly name = \"jest\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const match = GROUP_BLOCK_RE.exec(source);\n const existing = match ? readExistingGroups(match[0]) : null;\n const sortedTags = [...tags].sort();\n\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n const stripped = match ? source.slice(match[0].length) : source;\n\n if (tags.length === 0) return stripped;\n\n return buildBlock(sortedTags) + stripped;\n }\n}\n","/**\n * Tag applier strategy for Playwright: injects { tag: [...] } with @ prefix into\n * test.describe/test calls. Playwright uses the singular `tag` option (not `tags`) and\n * conventionally prefixes tag names with `@` (e.g. `@auth`, `@parseArgs`).\n * Filter at CI time with: `playwright test --grep @tagname`\n */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\nfunction toPlaywrightLiteral(tags: string[]): string {\n // Playwright tag convention: prefix each name with '@'\n return toArrayLiteral(tags.map((tag) => `@${tag}`));\n}\n\nfunction normaliseExisting(raw: string[]): string[] {\n // Strip @ prefix so we can compare against unprefixed computed tags\n return raw.map((tag) => (tag.startsWith(\"@\") ? tag.slice(1) : tag));\n}\n\nexport class PlaywrightStrategy implements TagApplierStrategy {\n readonly name = \"playwright\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const sf = ts.createSourceFile(path.basename(absPath), source, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return source;\n\n // Idempotency: compare normalised existing tags with computed tags\n const rawExisting = readArrayProp(calls[0]!, \"tag\", sf);\n const sortedTags = [...tags].sort();\n if (\n rawExisting !== null &&\n JSON.stringify(normaliseExisting(rawExisting).sort()) === JSON.stringify(sortedTags)\n ) {\n return source;\n }\n\n const replacements = calls.flatMap((call) => {\n const replacement =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tag\", sf)\n : buildInjectReplacement(call, \"tag\", toPlaywrightLiteral(sortedTags), sf);\n return replacement ? [replacement] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(source, replacements) : source;\n }\n}\n","/**\n * Tag applier strategy for Python/pytest: writes a module-level `pytestmark` variable.\n * `pytestmark` applies marks to every test in the file without touching individual functions.\n *\n * Example output:\n * import pytest\n * pytestmark = [pytest.mark.auth, pytest.mark.parseArgs]\n *\n * Filter at CI time with: `pytest -m \"auth and parseArgs\"` or `pytest -m auth`\n * @see https://docs.pytest.org/en/stable/how-to/mark.html#marking-whole-classes-or-modules\n */\nimport path from \"node:path\";\nimport type { TagApplierStrategy } from \"./types\";\n\n// Matches: pytestmark = [pytest.mark.foo, pytest.mark.bar]\n// Also handles the single-mark form: pytestmark = pytest.mark.foo\nconst PYTESTMARK_RE = /^pytestmark\\s*=\\s*.+$/m;\n\n// Matches the mokosh-managed import line\nconst PYTEST_IMPORT_RE = /^import pytest\\s*$/m;\n\nfunction buildPytestmark(tags: string[]): string {\n const marks = tags.map((tag) => `pytest.mark.${tag}`).join(\", \");\n return tags.length === 1 ? `pytestmark = pytest.mark.${tags[0]}` : `pytestmark = [${marks}]`;\n}\n\nfunction readExistingMarks(source: string): string[] | null {\n const match = PYTESTMARK_RE.exec(source);\n if (!match) return null;\n const line = match[0];\n // Extract names from pytest.mark.<name>\n const marks: string[] = [];\n const re = /pytest\\.mark\\.([a-zA-Z0-9_-]+)/g;\n let markMatch = re.exec(line);\n while (markMatch !== null) {\n if (markMatch[1]) marks.push(markMatch[1]);\n markMatch = re.exec(line);\n }\n return marks;\n}\n\nexport class PytestStrategy implements TagApplierStrategy {\n readonly name = \"pytest\";\n\n canHandle(absPath: string): boolean {\n return path.extname(absPath).toLowerCase() === \".py\";\n }\n\n apply(_absPath: string, source: string, tags: string[]): string {\n const existing = readExistingMarks(source);\n const sortedTags = [...tags].sort();\n\n // Idempotency check\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return source;\n }\n\n if (tags.length === 0) {\n // Remove pytestmark line (and the pytest import if we added it and it's now unused)\n return source.replace(PYTESTMARK_RE, \"\").replace(/\\n{3,}/g, \"\\n\\n\");\n }\n\n const pytestmarkLine = buildPytestmark(sortedTags);\n\n if (existing !== null) {\n // Replace in-place\n return source.replace(PYTESTMARK_RE, pytestmarkLine);\n }\n\n // Insert after the last import block (or at the top if no imports)\n const hasImport = PYTEST_IMPORT_RE.test(source);\n\n // Find insertion point: after the last top-level import line\n const importBlockEnd = findImportBlockEnd(source);\n\n const before = source.slice(0, importBlockEnd);\n const after = source.slice(importBlockEnd);\n\n const importLine = hasImport ? \"\" : \"import pytest\\n\";\n const separator = before.endsWith(\"\\n\\n\") ? \"\" : \"\\n\";\n\n return before + separator + importLine + pytestmarkLine + \"\\n\" + after;\n }\n}\n\n/** Returns the index just after the last top-level import/from-import line. */\nfunction findImportBlockEnd(source: string): number {\n const lines = source.split(\"\\n\");\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!.trimStart();\n if (line.startsWith(\"import \") || line.startsWith(\"from \")) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine < 0) return 0;\n\n // Compute character offset of the end of that line\n let offset = 0;\n for (let i = 0; i <= lastImportLine; i++) {\n offset += lines[i]!.length + 1; // +1 for \\n\n }\n return offset;\n}\n","/** Tag applier strategy for Vitest: injects { tags: [...] } into describe/test/it calls. */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport {\n applyReplacements,\n buildInjectReplacement,\n buildRemoveReplacement,\n findTopLevelCalls,\n readArrayProp,\n TS_EXTENSIONS,\n toArrayLiteral,\n} from \"./ts-ast-utils\";\nimport type { TagApplierStrategy } from \"./types\";\n\n// Strips legacy comment blocks written by older versions of mokosh.\nconst LEGACY_BLOCK_REGEX = /\\/\\/ <mokosh-tags>[\\s\\S]*?\\/\\/ <\\/mokosh-tags>\\n*/;\n\nexport class VitestStrategy implements TagApplierStrategy {\n readonly name = \"vitest\";\n\n canHandle(absPath: string): boolean {\n return TS_EXTENSIONS.has(path.extname(absPath).toLowerCase());\n }\n\n apply(absPath: string, source: string, tags: string[]): string {\n const stripped = source.replace(LEGACY_BLOCK_REGEX, \"\");\n const sf = ts.createSourceFile(path.basename(absPath), stripped, ts.ScriptTarget.Latest, true);\n const calls = findTopLevelCalls(sf);\n\n if (calls.length === 0) return stripped;\n\n // Idempotency check — if first call already has the exact sorted tags, nothing to do\n const existing = readArrayProp(calls[0]!, \"tags\", sf);\n const sortedTags = [...tags].sort();\n if (existing !== null && JSON.stringify([...existing].sort()) === JSON.stringify(sortedTags)) {\n return stripped;\n }\n\n const replacements = calls.flatMap((call) => {\n const r =\n tags.length === 0\n ? buildRemoveReplacement(call, \"tags\", sf)\n : buildInjectReplacement(call, \"tags\", toArrayLiteral(sortedTags), sf);\n return r ? [r] : [];\n });\n\n return replacements.length > 0 ? applyReplacements(stripped, replacements) : stripped;\n }\n}\n","/** Strategy interface and default implementation for identifying test nodes in the graph. */\nimport type { StructuredTag } from \"../types/node\";\n\n/**\n * @description Strategy interface for determining whether a graph node represents a test file.\n */\nexport interface TestNodeIdentifier {\n /**\n * @description Returns whether the given node should be treated as a test node.\n * @param {{ category: string; tags: StructuredTag[] }} node - A minimal node descriptor containing its category and structured tags.\n * @returns {boolean} `true` if the node is a test file.\n */\n isTestNode(node: { category: string; tags: StructuredTag[] }): boolean;\n}\n\n/**\n * @description Default implementation that identifies test nodes by `category === \"test\"`\n * or the presence of a structured tag named `\"test\"`.\n */\nexport class DefaultTestNodeIdentifier implements TestNodeIdentifier {\n /**\n * @description Checks the node's category and tag list to determine if it is a test node.\n * @param {{ category: string; tags: StructuredTag[] }} node - A minimal node descriptor containing its category and structured tags.\n * @returns {boolean} `true` if the category is `\"test\"` or any tag is named `\"test\"`.\n */\n public isTestNode(node: { category: string; tags: StructuredTag[] }): boolean {\n return node.category === \"test\" || node.tags.some((tag) => tag.name === \"test\");\n }\n}\n","/** GraphBuilder walks the file system from entry points, parses each reachable file, and assembles the dependency graph. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGitFileStats } from \"../git.js\";\nimport { getTestPatterns } from \"../parser/classify.js\";\nimport { type LockFileData, loadLockFile } from \"../parser/lockfile.js\";\nimport { getFileType, parseFile } from \"../parser.js\";\nimport type { DependencyGraph } from \"../types/graph\";\nimport type { CallEdge, FileNode, ImportEdge } from \"../types/node\";\nimport {\n enrichCoverage,\n enrichExportUsage,\n enrichLibraryTags,\n enrichTestedBy,\n enrichTestNodeTags,\n} from \"./enrichment.js\";\nimport { Graph } from \"./model.js\";\nimport { DefaultResolver, type PathResolver } from \"./resolver.js\";\n\n/** Conventional top-level test-directory names probed as siblings between the entry-derived scan root and `rootDir`. */\nconst CONVENTIONAL_TEST_DIR_NAMES = [\"tests\", \"test\", \"__tests__\", \"specs\", \"spec\"];\n\n/**\n * @description Finds the deepest directory that contains every path in `absPaths`, clamped\n * so the result is never outside `rootDir`. Used to scope the test-file discovery walk to\n * the subtree actually reachable from the given entry points, instead of always walking the\n * full project root (which, for a nested sub-project under a much larger `rootDir`, would\n * sweep in unrelated files).\n * @param absPaths - Absolute file paths (typically resolved entry points).\n * @param rootDir - Absolute project root; acts as an upper bound for the result.\n * @returns The common ancestor directory, or `rootDir` if `absPaths` is empty or resolves outside it.\n */\nfunction commonAncestorDir(absPaths: string[], rootDir: string): string {\n if (absPaths.length === 0) return rootDir;\n\n const segmentLists = absPaths.map((p) => path.dirname(p).split(path.sep));\n let common = segmentLists[0]!;\n for (const segments of segmentLists.slice(1)) {\n let i = 0;\n while (i < common.length && i < segments.length && common[i] === segments[i]) i++;\n common = common.slice(0, i);\n }\n const candidate = common.join(path.sep) || path.sep;\n\n const rel = path.relative(rootDir, candidate);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) return rootDir;\n return candidate;\n}\n\n/**\n * @description Builds a dependency graph by recursively walking the file system from a set of entry points.\n *\n * Responsibilities:\n * - Parsing each reachable source file via {@link parseFile}\n * - Resolving raw import specifiers to actual file paths via a {@link PathResolver}\n * - Reusing unchanged nodes from a previous graph (incremental build)\n * - Annotating external imports with lock-file versions\n * - Applying post-build enrichment (test-node tags)\n *\n * **SRP note:** `resolveImports` intentionally doubles as the recursion trigger —\n * it calls `processFile` on each local dependency as it resolves it. This keeps the\n * traversal depth-first and avoids a separate queue, at the cost of two concerns\n * living in one method.\n *\n * **DIP note:** Only `PathResolver` is abstracted. `fs`, parsers, and enrichment\n * functions are concrete imports — sufficient for a build-time tool where the call\n * sites are stable and swapping them out has no real use case.\n */\nexport class GraphBuilder {\n private graph: DependencyGraph = { nodes: new Map() };\n private visited = new Set<string>();\n private readonly previousGraph: Graph | null = null;\n private readonly resolver: PathResolver;\n private lockFile: LockFileData | null = null;\n private progressCallback?: (count: number) => void;\n\n /**\n * @param rootDir - Absolute path to the project root; all node paths in the graph are relative to this.\n * @param previousGraph - Optional graph from a prior run. Nodes whose `mtime` and `size` match are reused as-is, making incremental builds significantly faster.\n * @param resolver - Strategy for turning raw import specifiers into absolute file paths. Defaults to {@link DefaultResolver}, which handles relative paths, tsconfig aliases, and node_modules.\n * @param progressCallback - Called every 100 files processed; useful for rendering a progress indicator in long-running CLI builds.\n * @param gitStats - When true, fetches `commitCount90d` and `lastAuthor` for each cache-missed file via git log.\n * @param coverageMap - Pre-loaded coverage map (relative path → line %). When non-empty, populates `coveragePct` on each node after the graph is built.\n */\n constructor(\n private rootDir: string,\n previousGraph: Graph | null = null,\n resolver?: PathResolver,\n progressCallback?: (count: number) => void,\n private readonly enableGitStats = false,\n private readonly coverageMap: Map<string, number> = new Map(),\n ) {\n this.previousGraph = previousGraph;\n this.resolver = resolver || new DefaultResolver(rootDir);\n this.lockFile = loadLockFile(rootDir);\n if (progressCallback) {\n this.progressCallback = progressCallback;\n }\n }\n\n /**\n * @description Starts the graph build from the given entry points and returns the completed graph.\n *\n * Each entry point triggers a depth-first traversal: imports are resolved, unvisited\n * local files are parsed, and the process continues until the full reachable subgraph\n * is covered. Test-node tags are applied as a final post-processing step because they\n * depend on the fully connected graph (e.g. a file is \"test\" if something imports it\n * with a `.test.` path, which can only be known after all edges are resolved).\n * @param entryPoints - File paths to start from. Relative paths are resolved against `rootDir`.\n * @returns The completed, enriched dependency graph.\n */\n public async build(entryPoints: string[]): Promise<Graph> {\n const entryPaths = entryPoints.map((entry) =>\n path.isAbsolute(entry) ? entry : path.resolve(this.rootDir, entry),\n );\n for (const entryPath of entryPaths) {\n await this.processFile(entryPath);\n }\n\n // Test files are never reachable from library entry points (imports flow source→test,\n // not the other way around). Scan for them explicitly so enrichTestedBy has data.\n // Scoped to the entry points' common ancestor (plus conventional sibling test dirs) rather\n // than the full rootDir, so a nested sub-project scanned from a much larger rootDir doesn't\n // pull in unrelated files elsewhere in the tree.\n await this.processTestFiles(commonAncestorDir(entryPaths, this.rootDir));\n\n if (this.progressCallback && this.visited.size >= 100) {\n process.stderr.write(`\\nDone. Total processed: ${this.visited.size} nodes.\\n`);\n }\n\n enrichTestNodeTags(this.graph.nodes);\n enrichTestedBy(this.graph.nodes);\n enrichExportUsage(this.graph.nodes);\n if (this.coverageMap.size > 0) enrichCoverage(this.graph.nodes, this.coverageMap);\n return new Graph(this.graph.nodes);\n }\n\n /**\n * @description Scans the file system for test files and processes them into the graph.\n * Test files are never reachable from library entry points, so they must be discovered\n * separately; without this pass `enrichTestedBy` would have no data to work with.\n * @param scanRoot - Directory to walk for test files; the entry points' common ancestor,\n * clamped to `rootDir`. Conventional sibling test directories (`tests/`, `__tests__/`, …)\n * found between `scanRoot` and `rootDir` are also walked, so a top-level test directory\n * alongside a `src/` entry point is still discovered even though it falls outside `scanRoot`.\n */\n private async processTestFiles(scanRoot: string): Promise<void> {\n const patterns = getTestPatterns();\n const ignoreDirs = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \"mokosh-cache\",\n \"coverage\",\n ]);\n const walk = async (dir: string): Promise<void> => {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!ignoreDirs.has(entry.name)) await walk(fullPath);\n } else if (entry.isFile() && patterns.some((pattern) => entry.name.includes(pattern))) {\n await this.processFile(fullPath);\n }\n }\n };\n await walk(scanRoot);\n\n let dir = scanRoot;\n while (dir !== this.rootDir) {\n const parent = path.dirname(dir);\n if (parent === dir) break;\n for (const name of CONVENTIONAL_TEST_DIR_NAMES) {\n const candidate = path.join(parent, name);\n try {\n if (fs.statSync(candidate).isDirectory()) await walk(candidate);\n } catch {\n // not present\n }\n }\n dir = parent;\n }\n }\n\n /**\n * @description Parses a single file and registers it in the graph, then recurses into its imports.\n *\n * The `visited` guard prevents re-processing files encountered via multiple import paths\n * (diamond dependencies). It is set before any async work so concurrent calls on the same\n * path — if this ever runs with parallelism — cannot race.\n * @param filePath - Absolute path of the file to process.\n */\n private async processFile(filePath: string) {\n if (this.visited.has(filePath)) return;\n this.visited.add(filePath);\n\n this.showProgress();\n\n const stats = fs.statSync(filePath, { throwIfNoEntry: false });\n if (!stats?.isFile()) return;\n\n const relativePath = path.relative(this.rootDir, filePath);\n const node = await this.getNode(filePath, relativePath, stats);\n\n node.imports = await this.resolveImports(filePath, node.imports);\n\n this.graph.nodes.set(node.path, node);\n }\n\n /**\n * @description Returns the `FileNode` for a file, either from the incremental cache or by\n * parsing it fresh. Cache hit requires both `mtime` and `size` to match — size guards\n * against tools that restore a previous file version with an identical timestamp.\n * @param filePath - Absolute path of the file.\n * @param relativePath - Path relative to `rootDir`, used as the node key.\n * @param stats - File system stats for cache validation and node metadata.\n * @returns The parsed or cached `FileNode`.\n */\n private async getNode(\n filePath: string,\n relativePath: string,\n stats: fs.Stats,\n ): Promise<FileNode> {\n const cachedNode = this.previousGraph?.nodes.get(relativePath);\n if (cachedNode && cachedNode.mtime === stats.mtimeMs && cachedNode.size === stats.size) {\n return { ...cachedNode };\n }\n\n const parsed = await this.tryParse(filePath, relativePath);\n if (!parsed) return this.makeStubNode(filePath, relativePath, stats);\n\n enrichLibraryTags(parsed.imports, parsed.tags);\n const callEdges = this.resolveCallEdges(filePath, parsed.rawCallEdges);\n const node = this.buildNode(filePath, relativePath, stats, parsed, callEdges);\n this.attachGitStats(node, relativePath);\n return node;\n }\n\n /**\n * @description Reads and parses a file, returning `null` on failure and emitting a warning\n * to stderr so the surrounding graph build can continue with a stub.\n * @param filePath - Absolute path of the file to parse.\n * @param relativePath - Relative path used only in the warning message.\n * @returns The parse result, or `null` if parsing threw.\n */\n private async tryParse(\n filePath: string,\n relativePath: string,\n ): Promise<Awaited<ReturnType<typeof parseFile>> | null> {\n const content = fs.readFileSync(filePath, \"utf-8\");\n try {\n return await parseFile(filePath, content);\n } catch (err) {\n process.stderr.write(`\\nWarning: failed to parse ${relativePath}: ${err}\\n`);\n return null;\n }\n }\n\n /**\n * @description Builds a minimal stub `FileNode` for a file that could not be parsed,\n * keeping the graph structurally intact while surfacing the failure via category `\"other\"`.\n * @param filePath - Absolute path, used to determine the file type.\n * @param relativePath - Used as the node's path key.\n * @param stats - Provides `mtime` and `size` for future cache comparisons.\n * @returns A `FileNode` with empty imports, exports, and tags.\n */\n private makeStubNode(filePath: string, relativePath: string, stats: fs.Stats): FileNode {\n return {\n path: relativePath,\n type: getFileType(filePath),\n category: \"other\",\n imports: [],\n exports: [],\n tags: [],\n mtime: stats.mtimeMs,\n size: stats.size,\n };\n }\n\n /**\n * @description Resolves raw call-edge specifiers to project-relative file paths,\n * silently dropping any specifier the resolver cannot map.\n * @param filePath - Absolute path of the file that owns the call edges.\n * @param rawCallEdges - Unresolved call edges from the parser output.\n * @returns Resolved `CallEdge` array containing only internal (non-external) edges.\n */\n private resolveCallEdges(\n filePath: string,\n rawCallEdges: Awaited<ReturnType<typeof parseFile>>[\"rawCallEdges\"],\n ): CallEdge[] {\n const callEdges: CallEdge[] = [];\n for (const rce of rawCallEdges ?? []) {\n try {\n const resolved = this.resolver.resolve(filePath, rce.toSpecifier);\n if (resolved && !resolved.isExternal) {\n callEdges.push({\n from: rce.from,\n to: rce.to,\n toFile: path.relative(this.rootDir, resolved.path),\n });\n }\n } catch {\n // unresolvable specifier — silent\n }\n }\n return callEdges;\n }\n\n /**\n * @description Assembles the final `FileNode` from parsed data and resolved call edges.\n * @param filePath - Absolute path, used to determine the file type.\n * @param relativePath - Used as the node's path key.\n * @param stats - Provides `mtime` and `size`.\n * @param parsed - Structured output from the parser.\n * @param callEdges - Already-resolved call edges to attach when non-empty.\n * @returns A fully populated `FileNode` ready to be inserted into the graph.\n */\n private buildNode(\n filePath: string,\n relativePath: string,\n stats: fs.Stats,\n parsed: Awaited<ReturnType<typeof parseFile>>,\n callEdges: CallEdge[],\n ): FileNode {\n const {\n imports,\n exports,\n tags,\n category,\n description,\n complexity,\n cognitiveComplexity,\n functions,\n } = parsed;\n return {\n path: relativePath,\n type: getFileType(filePath),\n category,\n imports,\n exports,\n tags,\n mtime: stats.mtimeMs,\n size: stats.size,\n ...(description !== undefined ? { description } : {}),\n ...(callEdges.length > 0 ? { callEdges } : {}),\n ...(complexity !== undefined ? { complexity } : {}),\n ...(cognitiveComplexity !== undefined ? { cognitiveComplexity } : {}),\n ...(functions !== undefined ? { functions } : {}),\n };\n }\n\n /**\n * @description Enriches a node with git activity metadata when `enableGitStats` is on,\n * silently skipping files not tracked by git or when git is unavailable.\n * @param node - The node to mutate in place.\n * @param relativePath - Project-relative path passed to the git helper.\n */\n private attachGitStats(node: FileNode, relativePath: string): void {\n if (!this.enableGitStats) return;\n try {\n const git = getGitFileStats(this.rootDir, relativePath);\n node.commitCount90d = git.commitCount90d;\n if (git.lastAuthor !== undefined) node.lastAuthor = git.lastAuthor;\n } catch {\n // git not available or file not tracked — silent\n }\n }\n\n /**\n * @description Resolves each import's raw specifier to a concrete path and, for local imports,\n * triggers recursive processing of the target file.\n *\n * Combining resolution with recursion (rather than two separate passes) keeps the\n * traversal depth-first, which improves cache locality during parsing. The trade-off\n * is that this method now owns two concerns: path resolution and graph traversal.\n *\n * For external imports (node_modules), the package name is extracted from the specifier\n * and matched against the lock file to attach a resolved version string. Scoped packages\n * (`@scope/pkg/deep`) are normalised to the two-segment package name before lookup.\n *\n * Imports that the resolver cannot map to any path are silently dropped — this covers\n * dynamic specifiers, virtual modules, and unsupported module systems.\n * @param filePath - Absolute path of the file that owns these imports.\n * @param imports - Raw import edges as produced by the parser, with unresolved `toPath` values.\n * @returns The same edges with `toPath`, `isExternal`, and optionally `version` filled in.\n */\n private async resolveImports(filePath: string, imports: ImportEdge[]): Promise<ImportEdge[]> {\n const resolvedImports: ImportEdge[] = [];\n\n for (const imp of imports) {\n const results = this.resolver.resolveAll(filePath, imp.rawSpecifier);\n if (results.length === 0) continue;\n\n for (const resolved of results) {\n const edge: ImportEdge = {\n ...imp,\n toPath: resolved.isExternal ? resolved.path : path.relative(this.rootDir, resolved.path),\n isExternal: resolved.isExternal,\n };\n\n if (resolved.isWorkspace) {\n edge.isWorkspace = true;\n edge.workspacePackage = resolved.workspacePackage;\n }\n\n if (resolved.isExternal) {\n this.attachLockfileVersion(edge);\n } else {\n await this.processFile(resolved.path);\n }\n\n resolvedImports.push(edge);\n }\n }\n\n return resolvedImports;\n }\n\n /**\n * @description Looks up the package version from the lock file and attaches it to the import edge.\n * Scoped packages (`@scope/pkg/deep/path`) are normalised to their two-segment name before lookup.\n * @param imp - The external import edge to annotate; mutated in place.\n */\n private attachLockfileVersion(imp: ImportEdge): void {\n if (!this.lockFile) return;\n const libName = imp.rawSpecifier.startsWith(\"@\")\n ? imp.rawSpecifier.split(\"/\").slice(0, 2).join(\"/\")\n : (imp.rawSpecifier.split(\"/\")[0] as string);\n const dep = libName ? this.lockFile.dependencies[libName] : undefined;\n if (dep) imp.version = dep.version;\n }\n\n /**\n * @description Fires the progress callback every 100 files to avoid flooding the caller with updates.\n */\n private showProgress() {\n if (this.progressCallback && this.visited.size % 100 === 0) {\n this.progressCallback(this.visited.size);\n }\n }\n}\n","/** Git integration: changed-file detection via GitProvider and per-file commit activity stats via getGitFileStats. */\nimport { execSync } from \"node:child_process\";\n\n/**\n * @description Contract for querying changed files from a version-control backend.\n * Abstracted so the CLI and MCP server can be tested without a live git repository.\n */\nexport interface GitProvider {\n getChangedFiles(): string[];\n}\n\n/**\n * @description Default `GitProvider` that shells out to the git CLI to discover\n * modified, staged, and untracked files in the current repository.\n */\nexport class DefaultGitProvider implements GitProvider {\n /**\n * @description Returns a deduplicated list of all modified, staged, and untracked files by running three git commands.\n * Returns an empty array if not inside a git repository or if git is unavailable.\n * @returns Relative file paths as reported by git, deduplicated across all three query types.\n */\n public getChangedFiles(): string[] {\n try {\n const commands = [\n \"git diff --name-only\",\n \"git diff --cached --name-only\",\n \"git ls-files --others --exclude-standard\",\n ];\n\n const allFiles = commands.flatMap((cmd) => {\n try {\n const output = execSync(cmd, { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n return output\n .split(\"\\n\")\n .map((filePath) => filePath.trim())\n .filter((filePath) => filePath !== \"\");\n } catch {\n return [];\n }\n });\n\n return Array.from(new Set(allFiles));\n } catch (error) {\n console.error(\"Error getting git diff:\", error);\n return [];\n }\n }\n}\n\n/**\n * @description Commit activity metadata for a single file, used to surface churn and ownership signals.\n */\nexport interface GitFileStats {\n commitCount90d: number;\n lastAuthor: string | undefined;\n}\n\n/**\n * @description Queries git log to compute commit frequency and last author for a file over the past 90 days.\n * @param rootDir - Absolute path to the repository root, passed to `git -C` so the command works from any cwd.\n * @param relativePath - Path to the file relative to `rootDir`.\n * @returns Commit count and the email of the most recent author, or `undefined` if the file has no history.\n */\nexport function getGitFileStats(rootDir: string, relativePath: string): GitFileStats {\n const output = execSync(\n `git -C \"${rootDir}\" log --follow --format=\"%ae\" --since=\"90 days ago\" -- \"${relativePath}\"`,\n { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n );\n const lines = output.split(\"\\n\").filter(Boolean);\n return { commitCount90d: lines.length, lastAuthor: lines[0] };\n}\n","/** Parses npm, Yarn, and pnpm lock files to extract installed package versions for import-edge annotation. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\n\n/**\n * Represents the parsed data from a lock file.\n */\nexport interface LockFileData {\n /**\n * Map of package names to their version and nested dependencies.\n */\n dependencies: Record<string, { version: string; dependencies?: Record<string, string> }>;\n}\n\ninterface PkgData {\n version: string;\n dependencies?: Record<string, string>;\n}\n\ninterface PackageLock {\n packages?: Record<string, PkgData>;\n dependencies?: Record<string, PkgData>;\n}\n\n/**\n * @description Strips the `@version` suffix from a package descriptor string,\n * correctly handling scoped packages like `@scope/pkg@1.0.0` where the leading `@`\n * must not be treated as the version separator.\n * @param descriptor - The raw package descriptor string, e.g. `react@^18.0` or `@scope/pkg@1.0.0`.\n * @returns The package name without the version suffix, or the original string if no separator was found.\n */\nfunction stripVersionSuffix(descriptor: string): string {\n const lastAt = descriptor.lastIndexOf(\"@\");\n return lastAt > 0 ? descriptor.substring(0, lastAt) : descriptor;\n}\n\n/**\n * @description Parses the header line of a yarn classic block into deduplicated package names.\n * Strips trailing colons, surrounding quotes, and version descriptors from comma-separated\n * entries like `\"react@^17.0\", \"react@^18.0\":`.\n * @param line - A raw yarn classic header line, e.g. `\"react@^17.0\", \"react@^18.0\":`.\n * @returns Array of package names extracted from the descriptors.\n */\nfunction parseYarnDescriptors(line: string): string[] {\n return line\n .replace(/:$/, \"\")\n .split(\",\")\n .map((part) => {\n let trimmed = part.trim();\n if (trimmed.startsWith('\"')) trimmed = trimmed.slice(1);\n if (trimmed.endsWith('\"')) trimmed = trimmed.slice(0, -1);\n return stripVersionSuffix(trimmed);\n })\n .filter(Boolean);\n}\n\n/**\n * @description Extracts a name and version from a pnpm package ID.\n * pnpm IDs use formats like `/pkg@version`, `/@scope/pkg@version`, or `pkg@version`;\n * when the ID encodes a version it is used as a fallback when `pkgVersion` is absent.\n * @param id - The pnpm package ID, e.g. `/lodash@4.17.21` or `/@scope/pkg@1.0.0`.\n * @param pkgVersion - The explicit version from the lockfile entry; takes priority over the version embedded in `id`.\n * @returns An object with the extracted `name` and resolved `version`.\n */\nfunction parsePnpmId(id: string, pkgVersion: string): { name: string; version: string } {\n const raw = id.startsWith(\"/\") ? id.slice(1) : id;\n const lastAt = raw.lastIndexOf(\"@\");\n if (lastAt > 0) {\n return { name: raw.substring(0, lastAt), version: pkgVersion || raw.substring(lastAt + 1) };\n }\n return { name: raw, version: pkgVersion };\n}\n\n/**\n * @description Attempts to parse a Yarn Berry (v2+) YAML lockfile. Returns `null` when\n * YAML parsing fails so the caller can fall back to the classic text-format parser.\n * @param content - Raw text content of the `yarn.lock` file.\n * @returns Parsed lock file data on success, or `null` if YAML parsing fails.\n */\nfunction tryParseYarnBerry(content: string): LockFileData | null {\n try {\n const lock = yaml.load(content) as Record<string, PkgData>;\n const result: LockFileData = { dependencies: {} };\n for (const [key, value] of Object.entries(lock)) {\n if (key === \"__metadata\" || !value?.version) continue;\n for (const part of key.split(\", \")) {\n const name = stripVersionSuffix(part);\n if (name) {\n result.dependencies[name] = {\n version: value.version,\n ...(value.dependencies !== undefined && { dependencies: value.dependencies }),\n };\n }\n }\n }\n return result;\n } catch (_e) {\n return null;\n }\n}\n\n/**\n * @description Parses a Yarn v1 classic lockfile by splitting the content into blank-line-separated\n * blocks. Each block's first non-comment, non-indented line carries the package descriptors;\n * a `version \"...\"` line within the same block provides the resolved version.\n * @param content - Raw text content of the `yarn.lock` file.\n * @returns Parsed lock file data with all discovered package versions.\n */\nfunction parseYarnClassic(content: string): LockFileData {\n const result: LockFileData = { dependencies: {} };\n\n for (const block of content.split(/\\n\\n+/)) {\n const lines = block\n .split(\"\\n\")\n .filter((line) => line.trim().length > 0 && !line.trim().startsWith(\"#\"));\n if (lines.length < 2) continue;\n\n const header = lines[0];\n if (!header || header.startsWith(\" \")) continue;\n\n const names = parseYarnDescriptors(header);\n if (names.length === 0) continue;\n\n const versionLine = lines.find((line) => line.trim().startsWith('version \"'));\n const version = versionLine?.match(/version \"(.*?)\"/)?.[1] ?? \"\";\n\n for (const name of names) {\n result.dependencies[name] = { version };\n }\n }\n\n return result;\n}\n\n/**\n * @description Parses a `package-lock.json` file. Supports v1/v2 (`dependencies` key) and\n * v3 (`packages` key with `node_modules/` prefixed paths); nested `node_modules` entries\n * (e.g. `node_modules/a/node_modules/b`) are skipped.\n * @param filePath - Absolute path to the `package-lock.json` file.\n * @returns Parsed lock file data with all top-level dependencies and their versions.\n */\nexport function parsePackageLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const lock = JSON.parse(content) as PackageLock;\n const result: LockFileData = { dependencies: {} };\n\n if (lock.packages) {\n for (const [pkgPath, pkgData] of Object.entries(lock.packages)) {\n if (!pkgPath.startsWith(\"node_modules/\")) continue;\n const name = pkgPath.replace(\"node_modules/\", \"\");\n if (name.includes(\"node_modules/\")) continue;\n result.dependencies[name] = {\n version: pkgData.version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n } else if (lock.dependencies) {\n for (const [name, pkgData] of Object.entries(lock.dependencies)) {\n result.dependencies[name] = {\n version: pkgData.version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n }\n\n return result;\n}\n\n/**\n * @description Parses a `yarn.lock` file. Detects Yarn Berry (v2+) by the presence of\n * `__metadata:` and attempts YAML parsing first; falls back to the v1 classic text parser\n * when YAML parsing fails.\n * @param filePath - Absolute path to the `yarn.lock` file.\n * @returns Parsed lock file data with all discovered package versions.\n */\nexport function parseYarnLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n if (content.includes(\"__metadata:\")) {\n const berryResult = tryParseYarnBerry(content);\n if (berryResult !== null) return berryResult;\n }\n\n return parseYarnClassic(content);\n}\n\n/**\n * @description Parses a `pnpm-lock.yaml` file. Handles the `packages` section (v6+) where\n * package IDs encode the name and version, and the root-level `dependencies` section (v5)\n * as a fallback for packages not already captured from `packages`.\n * @param filePath - Absolute path to the `pnpm-lock.yaml` file.\n * @returns Parsed lock file data, or an empty dependencies map if YAML parsing fails.\n */\nexport function parsePnpmLock(filePath: string): LockFileData {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const result: LockFileData = { dependencies: {} };\n\n try {\n const lock = yaml.load(content) as {\n packages?: Record<string, PkgData>;\n dependencies?: Record<string, string | PkgData>;\n };\n\n if (lock.packages) {\n for (const [id, pkgData] of Object.entries(lock.packages)) {\n const { name, version } = parsePnpmId(id, pkgData.version);\n if (name) {\n result.dependencies[name] = {\n version,\n ...(pkgData.dependencies !== undefined && { dependencies: pkgData.dependencies }),\n };\n }\n }\n }\n\n if (lock.dependencies) {\n for (const [name, versionData] of Object.entries(lock.dependencies)) {\n if (result.dependencies[name]) continue;\n const version = typeof versionData === \"string\" ? versionData : versionData.version;\n result.dependencies[name] = { version: version || \"\" };\n }\n }\n } catch (_e) {\n // Ignore YAML errors\n }\n\n return result;\n}\n\n/**\n * @description Detects and loads the first supported lock file found in `rootDir`.\n * Checks for `package-lock.json`, `yarn.lock`, and `pnpm-lock.yaml` in that order.\n * @param rootDir - The project root directory to search for lock files.\n * @returns Parsed lock file data from the first detected lock file, or `null` if none is found.\n */\nexport function loadLockFile(rootDir: string): LockFileData | null {\n const candidates: [string, (lockFilePath: string) => LockFileData][] = [\n [\"package-lock.json\", parsePackageLock],\n [\"yarn.lock\", parseYarnLock],\n [\"pnpm-lock.yaml\", parsePnpmLock],\n ];\n\n for (const [filename, parser] of candidates) {\n const filePath = path.join(rootDir, filename);\n if (fs.existsSync(filePath)) return parser(filePath);\n }\n\n return null;\n}\n","/** Maps file extensions to FileType enum values for use by the parser registry and graph builder. */\nimport path from \"node:path\";\nimport type { FileType } from \"../types/parse\";\n\n/**\n * @description Maps a file path's extension to its canonical `FileType` identifier,\n * returning `\"unknown\"` for unrecognised or unsupported extensions.\n * @param filePath - Absolute or relative path to the source file.\n * @returns The `FileType` string corresponding to the file's language.\n */\nexport function getFileType(filePath: string): FileType {\n const ext = path.extname(filePath).toLowerCase();\n switch (ext) {\n case \".js\":\n case \".jsx\":\n case \".mjs\":\n case \".cjs\":\n return \"javascript\";\n case \".ts\":\n case \".tsx\":\n return \"typescript\";\n case \".css\":\n return \"css\";\n case \".scss\":\n case \".sass\":\n return \"scss\";\n case \".less\":\n return \"less\";\n case \".styl\":\n return \"stylus\";\n case \".coffee\":\n return \"coffeescript\";\n case \".ls\":\n return \"livescript\";\n case \".lua\":\n return \"lua\";\n case \".py\":\n return \"python\";\n case \".go\":\n return \"go\";\n case \".java\":\n case \".cpp\":\n case \".cc\":\n case \".cxx\":\n case \".c\":\n return \"unknown\";\n case \".feature\":\n return \"gherkin\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * @description Checks whether an import specifier refers to a stylesheet by examining\n * its extension, covering CSS, SCSS/Sass, Less, and Stylus.\n * @param specifier - The raw import specifier string from source code.\n * @returns `true` if the specifier's extension is a known stylesheet format.\n */\nexport function isStyleFile(specifier: string): boolean {\n const ext = path.extname(specifier).toLowerCase();\n return [\".css\", \".scss\", \".sass\", \".less\", \".styl\"].includes(ext);\n}\n","/** Parses CoffeeScript files to extract import edges and tag annotations. */\nimport coffee from \"coffeescript\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\n\ninterface CoffeeNode {\n constructor: { name: string };\n source?: { value: string };\n variable?: { base?: { value: string } };\n args?: Array<{ base?: { value: string } }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Scans raw source text for `@tag <name>` comment annotations and collects\n * the tag names. Runs before category resolution so `@tag test` can influence classification.\n * @param content - Raw source text to scan.\n * @returns Set of tag name strings found in `@tag` annotations.\n */\nfunction extractTags(content: string): Set<string> {\n const tags = new Set<string>();\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let match = tagRegex.exec(content);\n while (match !== null) {\n if (match[1]) tags.add(match[1]);\n match = tagRegex.exec(content);\n }\n return tags;\n}\n\n/**\n * @description Determines whether a file is a test or production-logic file by checking\n * path naming conventions (`.test.`, `.spec.`) and explicit `@tag test` annotations.\n * @param filePath - Path to the file being classified.\n * @param tags - Tag names extracted from the file's content.\n * @returns `\"test\"` if the file is a test file, `\"logic\"` otherwise.\n */\nfunction resolveCategory(filePath: string, tags: Set<string>): \"test\" | \"logic\" {\n const lower = filePath.toLowerCase();\n if (lower.includes(\".test.\") || lower.includes(\".spec.\") || tags.has(\"test\")) {\n return \"test\";\n }\n return \"logic\";\n}\n\n/**\n * @description Builds an `ImportEdge` from a CoffeeScript static `import` declaration AST node.\n * @param filePath - Source file path stamped onto the edge.\n * @param node - CoffeeScript AST node representing an `ImportDeclaration`.\n * @returns An `ImportEdge` for the import, or `null` if the node carries no source value.\n */\nfunction edgeFromImportDeclaration(filePath: string, node: CoffeeNode): ImportEdge | null {\n const specifier = node.source?.value;\n if (!specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"static\",\n };\n}\n\n/**\n * @description Builds an `ImportEdge` from a CoffeeScript `require()` call AST node.\n * @param filePath - Source file path stamped onto the edge.\n * @param node - CoffeeScript AST node representing a `Call`.\n * @returns An `ImportEdge` for the require call, or `null` if the node is not a `require` call or has no specifier.\n */\nfunction edgeFromRequireCall(filePath: string, node: CoffeeNode): ImportEdge | null {\n const isRequire = node.variable?.base?.value === \"require\";\n const specifier = node.args?.[0]?.base?.value;\n if (!isRequire || !specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n };\n}\n\n/**\n * @description Inspects a single CoffeeScript AST node and appends any discovered import edge\n * to the accumulator array. Handles both `ImportDeclaration` and `Call` (require) node types.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to inspect.\n * @param out - Accumulator array that receives any discovered edge.\n */\nfunction visitNode(filePath: string, node: CoffeeNode, out: ImportEdge[]): void {\n const className = node.constructor?.name;\n if (className === \"ImportDeclaration\") {\n const edge = edgeFromImportDeclaration(filePath, node);\n if (edge) out.push(edge);\n } else if (className === \"Call\") {\n const edge = edgeFromRequireCall(filePath, node);\n if (edge) out.push(edge);\n }\n}\n\n/**\n * @description Recursively walks the CoffeeScript AST and collects all import edges into `out`.\n * Skips `locationData` keys to prevent infinite cycles on circular metadata references.\n * @param filePath - Source file path forwarded to each created edge.\n * @param node - The AST node to walk.\n * @param out - Accumulator array that receives all discovered edges.\n */\nfunction traverse(filePath: string, node: CoffeeNode, out: ImportEdge[]): void {\n if (!node || typeof node !== \"object\") return;\n visitNode(filePath, node, out);\n for (const key in node) {\n if (key === \"locationData\") continue;\n const child = node[key];\n if (!child || typeof child !== \"object\") continue;\n if (Array.isArray(child)) {\n for (const c of child) traverse(filePath, c as CoffeeNode, out);\n } else {\n traverse(filePath, child as CoffeeNode, out);\n }\n }\n}\n\n/**\n * @description Parses a CoffeeScript source file and extracts its import edges, comment-marker\n * tags, and file category. Uses the CoffeeScript compiler's `nodes()` API for full AST\n * traversal, capturing both ES `import` declarations and CommonJS `require()` calls.\n * Falls back to an empty import list if the file fails to parse.\n * @param filePath - Absolute or project-relative path to the `.coffee` file.\n * @param content - Raw source text of the file.\n * @returns Parsed imports, empty exports list, extracted tags, and resolved category.\n */\nexport function parseCoffeeScript(filePath: string, content: string): ParseResult {\n const tags = extractTags(content);\n const category = resolveCategory(filePath, tags);\n const imports: ImportEdge[] = [];\n\n try {\n traverse(filePath, coffee.nodes(content) as unknown as CoffeeNode, imports);\n } catch (_e) {\n // coffeescript compiler throws on invalid syntax; return what we have\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Gherkin .feature files to extract scenario tag annotations using the official Cucumber parser. */\nimport { AstBuilder, GherkinClassicTokenMatcher, Parser } from \"@cucumber/gherkin\";\nimport { IdGenerator } from \"@cucumber/messages\";\nimport { registerParser } from \"../registry\";\nimport type { ParseResult } from \"../types\";\n\nconst uuidFn = IdGenerator.uuid();\n\n/**\n * @description Parses a Gherkin `.feature` file using the official Cucumber AST builder.\n * Walks the feature, scenario, example, and rule hierarchy to collect all `@tag` annotations.\n * Gherkin files are always categorized as `\"test\"`.\n * @param _filePath - Path to the feature file; used only in error messages.\n * @param content - Raw Gherkin source text.\n * @returns A `ParseResult` with no imports, no exports, all collected tags, and category `\"test\"`.\n */\nexport function parseGherkin(_filePath: string, content: string): ParseResult {\n const rawTags = new Set<string>();\n\n try {\n const builder = new AstBuilder(uuidFn);\n const matcher = new GherkinClassicTokenMatcher();\n const parser = new Parser(builder, matcher);\n\n const gherkinDocument = parser.parse(content);\n\n if (gherkinDocument.feature) {\n // Feature tags\n gherkinDocument.feature.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n\n // Child tags (Scenarios, Rules, etc.)\n gherkinDocument.feature.children.forEach((child) => {\n if (child.scenario) {\n child.scenario.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n\n // Example tags\n child.scenario.examples.forEach((example) => {\n example.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n });\n }\n\n if (child.rule) {\n child.rule.children.forEach((ruleChild) => {\n if (ruleChild.scenario) {\n ruleChild.scenario.tags.forEach((tag) => {\n rawTags.add(tag.name.startsWith(\"@\") ? tag.name.slice(1) : tag.name);\n });\n }\n });\n }\n });\n }\n } catch (error) {\n console.warn(`[GherkinParser] Failed to parse ${_filePath}:`, error);\n }\n\n return {\n imports: [],\n exports: [],\n tags: Array.from(rawTags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category: \"test\",\n };\n}\n\nregisterParser(\"gherkin\", parseGherkin);\n","/** Parses Go source files using the Lezer parser to extract import paths and tag annotations. */\nimport path from \"node:path\";\nimport { parser } from \"@lezer/go\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\nconst TAG_RE = /\\/\\/\\s*@tag\\s+([a-zA-Z0-9_-]+)/;\nconst BUILD_NEW_RE = /^\\/\\/go:build\\s+(.+)$/;\nconst BUILD_OLD_RE = /^\\/\\/\\s*\\+build\\s+(.+)$/;\n\n/**\n * @description Parses a Go source file using the Lezer Go grammar to extract import edges,\n * exported symbols, `// @tag` comment markers, and file category. All imports are marked\n * external — local package resolution requires `go.mod` context not available at parse time.\n * @param {string} filePath - Path to the `.go` file; used for test-file classification by basename convention.\n * @param {string} content - Raw Go source text.\n * @returns {ParseResult} Parsed imports, top-level exports, comment-marker tags, and resolved category.\n */\nexport function parseGo(filePath: string, content: string): ParseResult {\n const imports: ImportEdge[] = [];\n const exportMap = new Map<string, ExportedSymbol>();\n const tags = new Set<string>();\n const buildTags = new Set<string>();\n\n const tree = parser.parse(content);\n const cursor = tree.cursor();\n\n do {\n switch (cursor.name) {\n case \"LineComment\": {\n const text = content.slice(cursor.from, cursor.to);\n const tagM = text.match(TAG_RE);\n if (tagM?.[1]) tags.add(tagM[1]);\n\n const newBuild = text.match(BUILD_NEW_RE);\n if (newBuild) extractBuildTokens(newBuild[1] as string, buildTags);\n\n const oldBuild = text.match(BUILD_OLD_RE);\n if (oldBuild) extractBuildTokens(oldBuild[1] as string, buildTags);\n break;\n }\n\n case \"ImportSpec\": {\n // ImportSpec: DefName? String\n // The String child always holds the quoted import path.\n const stringNode = cursor.node.getChild(\"String\");\n if (stringNode) {\n const raw = content.slice(stringNode.from, stringNode.to);\n // Strip surrounding double-quotes\n const specifier = raw.slice(1, -1);\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isExternal: true,\n isStyle: false,\n type: \"static\",\n });\n }\n break;\n }\n\n case \"FunctionDecl\":\n case \"TypeDecl\":\n case \"VarDecl\":\n case \"ConstDecl\": {\n // For FunctionDecl the DefName is a direct child.\n // For TypeDecl the DefName lives inside TypeSpec.\n // For VarDecl/ConstDecl the DefName lives inside VarSpec/ConstSpec.\n const nameNode =\n cursor.node.getChild(\"DefName\") ??\n cursor.node.getChild(\"TypeSpec\")?.getChild(\"DefName\") ??\n cursor.node.getChild(\"VarSpec\")?.getChild(\"DefName\") ??\n cursor.node.getChild(\"ConstSpec\")?.getChild(\"DefName\");\n\n if (nameNode) {\n const name = content.slice(nameNode.from, nameNode.to);\n // Go export rule: identifier starts with an uppercase letter\n if (name !== \"_\" && /^[A-Z]/.test(name) && !exportMap.has(name)) {\n exportMap.set(name, { name });\n }\n }\n break;\n }\n }\n } while (cursor.next());\n\n const importsTestingPkg = imports.some((importEdge) => importEdge.rawSpecifier === \"testing\");\n const category =\n path.basename(filePath).endsWith(\"_test.go\") || tags.has(\"test\") || importsTestingPkg\n ? \"test\"\n : \"logic\";\n\n const allTagNames = new Set([...tags, ...buildTags]);\n return {\n imports,\n exports: Array.from(exportMap.values()),\n tags: Array.from(allTagNames).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n\n/**\n * @description Extracts individual identifier tokens from a Go build constraint expression.\n * Splits on operators and punctuation, strips leading `!`, discards the pseudo-tag `ignore`.\n * @param {string} expr - Raw expression text after `//go:build` or `// +build`.\n * @param {Set<string>} out - Set to populate with extracted tag names.\n */\nfunction extractBuildTokens(expr: string, out: Set<string>): void {\n for (const tok of expr.split(/[\\s,&|!()]+/)) {\n const name = tok.trim();\n if (name && name !== \"ignore\") out.add(name);\n }\n}\n","/** Parses LiveScript files to extract import edges and tag annotations. */\n// @ts-expect-error\nimport ls from \"livescript\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { stripQuotes } from \"../utils\";\n\ninterface LiveScriptNode {\n constructor: { name: string };\n type?: string;\n right?: { value: string };\n head?: { value: string };\n tails?: Array<{\n constructor: { name: string };\n type?: string;\n args?: Array<{ value: string }>;\n }>;\n [key: string]: unknown;\n}\n\n/**\n * @description Scans raw source text for `@tag` comment markers and collects the tag\n * names they carry. Runs before AST parsing so tags are available for classification.\n * @param content - Raw source text of the LiveScript file.\n * @returns A set of tag name strings found in `@tag` annotations.\n */\nfunction extractTags(content: string): Set<string> {\n const tags = new Set<string>();\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let match = tagRegex.exec(content);\n while (match !== null) {\n if (match[1]) tags.add(match[1]);\n match = tagRegex.exec(content);\n }\n return tags;\n}\n\n/**\n * @description Determines whether a file is a test or production-logic file by checking\n * path conventions (.test., .spec.) and the presence of an explicit `@tag test` annotation.\n * @param filePath - Absolute or relative path to the file being classified.\n * @param tags - Tag names extracted from the file's comments.\n * @returns \"test\" if the file is identified as a test file, \"logic\" otherwise.\n */\nfunction classifyFile(filePath: string, tags: Set<string>): \"test\" | \"logic\" {\n const lower = filePath.toLowerCase();\n return lower.includes(\".test.\") || lower.includes(\".spec.\") || tags.has(\"test\")\n ? \"test\"\n : \"logic\";\n}\n\nconst POSITIONAL_KEYS = new Set([\n \"first_line\",\n \"first_column\",\n \"last_line\",\n \"last_column\",\n \"line\",\n \"column\",\n]);\n\n/**\n * @description Inspects a single AST node and returns an ImportEdge if the node represents\n * an `import` statement or a `require()` call, or null if it is neither.\n * @param node - The AST node to inspect.\n * @param filePath - Source path to stamp onto any emitted edge.\n * @returns An ImportEdge for the detected dependency, or null if the node is not an import.\n */\nfunction extractEdge(node: LiveScriptNode, filePath: string): ImportEdge | null {\n const type = node.constructor?.name || node.type;\n\n if (type === \"Import\") {\n const raw = node.right?.value;\n if (typeof raw === \"string\") {\n const specifier = stripQuotes(raw);\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"static\",\n };\n }\n }\n\n if (type === \"Chain\" && node.head?.value === \"require\") {\n const call = node.tails?.[0];\n if (call?.constructor?.name === \"Call\" || call?.type === \"Call\") {\n const raw = call.args?.[0]?.value;\n if (typeof raw === \"string\") {\n const specifier = stripQuotes(raw);\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * @description Recursively walks a LiveScript AST and collects all import edges found\n * within the tree. Skips positional metadata keys to avoid infinite recursion.\n * @param node - The root AST node to walk.\n * @param filePath - Source path forwarded to each discovered edge.\n * @returns All ImportEdge values found in this node and its descendants.\n */\nfunction collectEdges(node: LiveScriptNode, filePath: string): ImportEdge[] {\n if (!node || typeof node !== \"object\") return [];\n\n const edges: ImportEdge[] = [];\n const edge = extractEdge(node, filePath);\n if (edge) edges.push(edge);\n\n for (const key in node) {\n if (POSITIONAL_KEYS.has(key)) continue;\n const child = node[key];\n if (!child || typeof child !== \"object\") continue;\n if (Array.isArray(child)) {\n for (const c of child) edges.push(...collectEdges(c as LiveScriptNode, filePath));\n } else {\n edges.push(...collectEdges(child as LiveScriptNode, filePath));\n }\n }\n\n return edges;\n}\n\n/**\n * @description Parses a LiveScript source file to extract its dependency edges, comment-marker\n * tags, and file category. Handles both ES-style `import` statements and CommonJS `require()`\n * calls. Falls back gracefully if the LiveScript AST cannot be produced.\n * @param filePath - Path used as the source identifier on all emitted import edges.\n * @param content - Raw LiveScript source text to parse.\n * @returns A ParseResult with collected imports, an empty exports list, extracted tags, and the file category.\n */\nexport function parseLiveScript(filePath: string, content: string): ParseResult {\n const tags = extractTags(content);\n const category = classifyFile(filePath, tags);\n let imports: ImportEdge[] = [];\n\n try {\n imports = collectEdges(ls.ast(content) as LiveScriptNode, filePath);\n } catch (_e) {\n // ignore parse errors\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/**\n * @description Removes a single surrounding quote pair (`'` or `\"`) from a string if one\n * is present. Safe to call on already-unquoted values — returns the input unchanged.\n * @param value - The string to unquote.\n * @returns The unquoted string, or the original value if it was not quoted.\n */\nexport function stripQuotes(value: string): string {\n return value.startsWith(\"'\") || value.startsWith('\"') ? value.slice(1, -1) : value;\n}\n","/** Parses Lua source files via luaparse to extract require() dependency edges and @tag annotations. */\nimport type { Chunk, Node } from \"luaparse\";\nimport luaparse from \"luaparse\";\nimport type { ImportEdge } from \"../../types/node\";\nimport { isStyleFile } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { stripQuotes } from \"../utils\";\n\n/**\n * @description Extracts `@tag <name>` comment annotations from raw Lua source text.\n * @param {string} content - Raw Lua source text.\n * @returns {Set<string>} The set of distinct tag names found in `content`.\n */\nfunction extractTagAnnotations(content: string): Set<string> {\n const tagNames = new Set<string>();\n const tagAnnotationRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n let annotationMatch = tagAnnotationRegex.exec(content);\n while (annotationMatch !== null) {\n if (annotationMatch[1]) tagNames.add(annotationMatch[1]);\n annotationMatch = tagAnnotationRegex.exec(content);\n }\n return tagNames;\n}\n\n/**\n * @description Classifies a Lua file as `\"test\"` or `\"logic\"` based on its filename\n * (`.test.` / `.spec.` substrings) or the presence of an explicit `@tag test` annotation.\n * @param {string} filePath - Path to the Lua file.\n * @param {Set<string>} tagNames - Tag names already extracted from the file's comments.\n * @returns {\"test\" | \"logic\"} The resolved file category.\n */\nfunction classifyCategory(filePath: string, tagNames: Set<string>): \"test\" | \"logic\" {\n const lowerCasePath = filePath.toLowerCase();\n const isTest =\n lowerCasePath.includes(\".test.\") || lowerCasePath.includes(\".spec.\") || tagNames.has(\"test\");\n return isTest ? \"test\" : \"logic\";\n}\n\n/**\n * @description Recursively walks a luaparse AST and returns a `require()` dependency edge for\n * every matching call expression found. Skips `loc` keys to avoid processing location\n * metadata objects.\n * @param {Chunk} ast - The parsed luaparse AST root.\n * @param {string} filePath - Path to the Lua file; used as `fromPath` on emitted edges.\n * @returns {ImportEdge[]} One edge per `require()` call found with a string-literal argument.\n */\nfunction collectRequireEdges(ast: Chunk, filePath: string): ImportEdge[] {\n const importEdges: ImportEdge[] = [];\n\n function visitNode(node: Node) {\n if (!node || typeof node !== \"object\") return;\n\n if (\n (node.type === \"CallExpression\" || node.type === \"StringCallExpression\") &&\n node.base?.type === \"Identifier\" &&\n node.base?.name === \"require\"\n ) {\n let specifier: string | undefined;\n if (node.type === \"CallExpression\") {\n const requireArgument = node.arguments?.[0];\n if (requireArgument?.type === \"StringLiteral\") {\n // raw is like \"'module'\" or '\"module\"'\n specifier = stripQuotes(requireArgument.raw);\n }\n } else if (node.type === \"StringCallExpression\") {\n const requireArgument = node.argument;\n if (requireArgument?.type === \"StringLiteral\") {\n specifier = stripQuotes(requireArgument.raw);\n }\n }\n\n if (specifier) {\n importEdges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"require\",\n });\n }\n }\n\n for (const key in node) {\n if (key === \"loc\") continue;\n const childValue = (node as unknown as Record<string, unknown>)[key];\n if (childValue && typeof childValue === \"object\") {\n if (Array.isArray(childValue)) {\n for (const childNode of childValue) visitNode(childNode as Node);\n } else {\n visitNode(childValue as Node);\n }\n }\n }\n }\n\n visitNode(ast);\n return importEdges;\n}\n\n/**\n * @description Parses a Lua source file using luaparse to extract `require()` dependency edges\n * and `@tag` comment annotations. Falls back to an empty import list if the file contains\n * syntax errors.\n * @param filePath - Path to the Lua file; used as the source on emitted edges and for test-file classification.\n * @param content - Raw Lua source text.\n * @returns Parsed imports, empty exports list, extracted tags, and resolved category.\n */\nexport function parseLua(filePath: string, content: string): ParseResult {\n const tagNames = extractTagAnnotations(content);\n const category = classifyCategory(filePath, tagNames);\n\n let imports: ImportEdge[] = [];\n try {\n const ast: Chunk = luaparse.parse(content);\n imports = collectRequireEdges(ast, filePath);\n } catch (_parseError) {\n // Ignore parse errors\n }\n\n return {\n imports,\n exports: [],\n tags: Array.from(tagNames).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n","/** Parses Python source files using the Lezer parser to extract import edges, exports, and tag annotations. */\nimport path from \"node:path\";\nimport type { SyntaxNode } from \"@lezer/common\";\nimport { parser } from \"@lezer/python\";\nimport type { ExportedSymbol, ImportEdge } from \"../../types/node\";\nimport type { ParseResult } from \"../types\";\n\nconst TEST_LIBS = new Set([\"pytest\", \"unittest\", \"nose\", \"hypothesis\"]);\n\n/**\n * @description Parses a Python source file using the Lezer parser to extract import edges,\n * top-level definitions as exports, `# @tag` comment markers, and file category.\n * @param {string} filePath - Path to the `.py` file; used for test-file classification by basename convention.\n * @param {string} content - Raw Python source text.\n * @returns {ParseResult} Parsed imports, top-level exports, comment-marker tags, and resolved category.\n */\nexport function parsePython(filePath: string, content: string): ParseResult {\n const imports: ImportEdge[] = [];\n const exports: ExportedSymbol[] = [];\n const tags = new Set<string>();\n const baseName = path.basename(filePath).toLowerCase();\n\n const tree = parser.parse(content);\n const cursor = tree.cursor();\n\n do {\n switch (cursor.name) {\n case \"Comment\": {\n const tagMatch = content.slice(cursor.from, cursor.to).match(/#\\s*@tag\\s+([a-zA-Z0-9_-]+)/);\n if (tagMatch?.[1]) tags.add(tagMatch[1]);\n break;\n }\n case \"ImportStatement\": {\n for (const edge of extractImportEdges(cursor.node, content, filePath)) {\n imports.push(edge);\n }\n break;\n }\n case \"FunctionDefinition\":\n case \"ClassDefinition\": {\n // Only top-level — parent must be Script or a DecoratedStatement directly under Script\n const parentNode = cursor.node.parent;\n const isTopLevel =\n parentNode?.name === \"Script\" ||\n (parentNode?.name === \"DecoratedStatement\" && parentNode.parent?.name === \"Script\");\n if (isTopLevel) {\n const nameNode = cursor.node.getChild(\"VariableName\");\n if (nameNode) exports.push({ name: content.slice(nameNode.from, nameNode.to) });\n }\n break;\n }\n\n case \"AssignStatement\": {\n // Only top-level simple assignments: `MY_VAR = value`\n if (cursor.node.parent?.name === \"Script\") {\n const target = cursor.node.firstChild;\n if (target?.name === \"VariableName\") {\n exports.push({ name: content.slice(target.from, target.to) });\n }\n }\n break;\n }\n }\n } while (cursor.next());\n\n const category = resolveCategory(baseName, imports, tags);\n if (category === \"test\") tags.add(\"test\");\n\n return {\n imports,\n exports,\n tags: Array.from(tags).map((name) => ({ name, kind: \"comment-marker\" as const })),\n category,\n };\n}\n\n// ─── import edge extraction ───────────────────────────────────────────────────\n\n/**\n * @description Dispatches a single Lezer `ImportStatement` node to the appropriate extractor\n * based on whether it begins with `from` (from-import form) or not (bare import form).\n * @param {SyntaxNode} node - The `ImportStatement` AST node to process.\n * @param {string} src - Full source text, used to slice node ranges into strings.\n * @param {string} filePath - Source file path stamped onto each emitted edge.\n * @returns {ImportEdge[]} One or more import edges extracted from the statement.\n */\nfunction extractImportEdges(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const first = node.firstChild;\n if (!first) return [];\n return first.name === \"from\"\n ? extractFromImport(node, src, filePath)\n : extractBareImport(node, src, filePath);\n}\n\n/**\n * Handles `from <module> import <names>` in all forms:\n * absolute, relative (. / .. / ...), dotted module paths, star, aliases.\n */\nfunction extractFromImport(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const fromKw = node.firstChild;\n if (!fromKw) return [];\n\n // Find the `import` keyword that splits module from names\n let importKw: SyntaxNode | null = fromKw.nextSibling;\n while (importKw && importKw.name !== \"import\") importKw = importKw.nextSibling;\n if (!importKw) return [];\n\n // Raw module text: everything between `from` end and `import` start.\n // e.g. \" .models\", \" os.path\", \" .. \", \" ...core.utils\"\n const rawModule = src.slice(fromKw.to, importKw.from).trim();\n const importedNames = collectImportedNames(importKw.nextSibling, src);\n if (!importedNames.length) return [];\n\n // Split leading dots from the rest of the module path\n let dotCount = 0;\n while (dotCount < rawModule.length && rawModule[dotCount] === \".\") dotCount++;\n const modulePart = rawModule.slice(dotCount); // e.g. \"models\", \"core.utils\", \"\"\n\n if (dotCount === 0) {\n // Absolute import: `from pathlib import Path`\n // Keep dotted module name as-is; resolver converts dots → path separators.\n return [makeEdge(filePath, rawModule, importedNames, true)];\n }\n\n // n=1 → \"./\" (current package)\n // n=2 → \"../\" (parent package)\n // n=3 → \"../../\" (grandparent)\n const prefix = dotCount === 1 ? \"./\" : \"../\".repeat(dotCount - 1);\n\n if (!modulePart) {\n // `from . import utils, models` — each name is its own sub-module.\n // `from . import *` — edge to the package init.\n if (importedNames[0] === \"*\") {\n return [makeEdge(filePath, prefix.slice(0, -1), [\"*\"], false)];\n }\n return importedNames.map((name) => makeEdge(filePath, prefix + name, [name], false));\n }\n\n // `from .models import User` → \"./models\"\n // `from .models.user import X` → \"./models/user\"\n return [makeEdge(filePath, prefix + modulePart.replace(/\\./g, \"/\"), importedNames, false)];\n}\n\n/**\n * Handles `import <module>` statements, including dotted paths and aliases.\n * `import os, sys` produces two edges; `import os.path as p` uses the original module name.\n */\nfunction extractBareImport(node: SyntaxNode, src: string, filePath: string): ImportEdge[] {\n const edges: ImportEdge[] = [];\n let childNode: SyntaxNode | null = node.firstChild?.nextSibling ?? null; // skip \"import\" keyword\n\n while (childNode) {\n if (childNode.name === \"VariableName\") {\n // Collect possibly dotted module name: os + . + path → \"os.path\"\n let modName = src.slice(childNode.from, childNode.to);\n while (\n childNode.nextSibling?.name === \".\" &&\n childNode.nextSibling.nextSibling?.name === \"VariableName\"\n ) {\n childNode = childNode.nextSibling.nextSibling as SyntaxNode;\n modName += `.${src.slice(childNode.from, childNode.to)}`;\n }\n // Skip optional `as alias`\n if (childNode.nextSibling?.name === \"as\") {\n childNode = childNode.nextSibling.nextSibling ?? childNode.nextSibling;\n }\n edges.push(makeEdge(filePath, modName, [\"*\"], true));\n }\n childNode = childNode.nextSibling;\n }\n\n return edges;\n}\n\n/**\n * Walks the sibling chain after `import`, collecting symbol names and skipping `as` aliases.\n */\nfunction collectImportedNames(start: SyntaxNode | null, src: string): string[] {\n const names: string[] = [];\n let childNode: SyntaxNode | null = start;\n while (childNode) {\n if (childNode.name === \"*\") {\n names.push(\"*\");\n } else if (childNode.name === \"VariableName\") {\n names.push(src.slice(childNode.from, childNode.to));\n // Skip `as alias` if present\n if (childNode.nextSibling?.name === \"as\") {\n childNode = childNode.nextSibling.nextSibling ?? childNode.nextSibling;\n }\n }\n childNode = childNode.nextSibling;\n }\n return names;\n}\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeEdge(\n filePath: string,\n rawSpecifier: string,\n symbols: string[],\n isExternal: boolean,\n): ImportEdge {\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier,\n isStyle: false,\n isExternal,\n type: \"static\",\n symbols: symbols.length > 0 ? symbols : undefined,\n };\n}\n\n/**\n * @description Classifies a Python file as `\"test\"`, `\"config\"`, or `\"logic\"` based on\n * its basename convention, imports from known test libraries, and explicit `@tag test` markers.\n * @param {string} baseName - Lowercase basename of the file, e.g. `\"test_auth.py\"`.\n * @param {ImportEdge[]} imports - Resolved import edges used to detect test-library usage.\n * @param {Set<string>} tags - Tag names extracted from comments.\n * @returns {\"test\" | \"config\" | \"logic\"} The resolved category for this file.\n */\nfunction resolveCategory(\n baseName: string,\n imports: ImportEdge[],\n tags: Set<string>,\n): \"test\" | \"config\" | \"logic\" {\n if (baseName.startsWith(\"test_\") || baseName.endsWith(\"_test.py\")) return \"test\";\n if (baseName === \"conftest.py\" || baseName === \"setup.py\") return \"config\";\n if (tags.has(\"test\")) return \"test\";\n if (imports.some((imp) => TEST_LIBS.has(imp.rawSpecifier))) return \"test\";\n return \"logic\";\n}\n","/** Parses JavaScript and TypeScript source files using the TypeScript Compiler API to extract imports, exports, tags, category, and complexity. */\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport type {\n ExportedSymbol,\n FunctionComplexity,\n ImportEdge,\n StructuredTag,\n} from \"../../types/node\";\nimport type { FileType, ImportType, NodeCategory } from \"../../types/parse\";\nimport { getBarrelThreshold, getTestLibraries, getTestPatterns, isConfigFile } from \"../classify\";\nimport { computeComplexity } from \"../complexity\";\nimport { isStyleFile } from \"../file-type\";\nimport { handleTagging } from \"../tagging\";\nimport type { ParseContext, ParseResult, RawCallEdge } from \"../types\";\n\n/**\n * @description Parses a JavaScript or TypeScript file using the TypeScript Compiler API.\n *\n * Creates a source file AST, walks every node to collect imports, exports, tags, and\n * category hints, then classifies the file and returns a structured result.\n * @param filePath - Absolute path of the file; used as the node identifier in the graph.\n * @param content - Raw source content of the file.\n * @param fileType - Determines the TS script kind (`TSX` for TypeScript, `JSX` for JavaScript).\n * @returns Parsed result containing imports, exports, tags, and category.\n */\nexport function parseCodeFile(filePath: string, content: string, fileType: FileType): ParseResult {\n const imports: ImportEdge[] = [];\n const exports: Map<string, ExportedSymbol> = new Map();\n const tags: Set<StructuredTag> = new Set();\n\n const sourceFile = ts.createSourceFile(\n filePath,\n content,\n ts.ScriptTarget.Latest,\n true,\n fileType === \"typescript\" ? ts.ScriptKind.TSX : ts.ScriptKind.JSX,\n );\n\n const context: ParseContext = {\n filePath,\n imports,\n exports,\n tags,\n rawCallEdges: [],\n sourceFile,\n hasUI: false,\n hasTypesOnly: true,\n totalStatements: 0,\n exportStatements: 0,\n };\n\n const visit = (node: ts.Node) => {\n analyzeNode(node, context);\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n const category = determineCategory(filePath, context);\n if (category === \"test\" || category === \"barrel\") {\n tags.add({ name: category, kind: \"comment-marker\" });\n }\n\n if (category !== \"test\") {\n collectRawCallEdges(context, sourceFile);\n }\n\n const firstStatement = sourceFile.statements[0];\n const description = firstStatement ? extractJsDoc(firstStatement) : undefined;\n const { complexity, cognitiveComplexity } = computeComplexity(sourceFile);\n const functions = collectFunctionComplexity(sourceFile);\n\n return {\n imports,\n exports: Array.from(exports.values()),\n tags: Array.from(tags),\n category,\n rawCallEdges: context.rawCallEdges ?? [],\n complexity,\n cognitiveComplexity,\n ...(functions.length > 0 ? { functions } : {}),\n ...(description !== undefined ? { description } : {}),\n };\n}\n\n/**\n * @description Walks the entire source file and records per-function complexity for every named\n * function-like declaration: function declarations, const-assigned arrow/function expressions,\n * and class methods/constructors/accessors (named `ClassName.member`). Anonymous inline\n * callbacks are skipped since they have no stable name to key results on.\n * @param sourceFile - The TypeScript source file AST to walk.\n * @returns Per-function complexity entries, in traversal order.\n */\nfunction collectFunctionComplexity(sourceFile: ts.SourceFile): FunctionComplexity[] {\n const results: FunctionComplexity[] = [];\n\n const record = (name: string, node: ts.Node): void => {\n const { complexity, cognitiveComplexity } = computeComplexity(node);\n const line = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;\n results.push({ name, line, complexity, cognitiveComplexity });\n };\n\n const visit = (node: ts.Node, className: string | undefined): void => {\n if (ts.isFunctionDeclaration(node) && node.name && node.body) {\n record(node.name.text, node);\n } else if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))\n ) {\n record(node.name.text, node.initializer);\n } else if (\n className &&\n ts.isMethodDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.${node.name.text}`, node);\n } else if (className && ts.isConstructorDeclaration(node) && node.body) {\n record(`${className}.constructor`, node);\n } else if (\n className &&\n ts.isGetAccessorDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.get ${node.name.text}`, node);\n } else if (\n className &&\n ts.isSetAccessorDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.body\n ) {\n record(`${className}.set ${node.name.text}`, node);\n }\n\n if (ts.isClassDeclaration(node) && node.name) {\n const nextClassName = node.name.text;\n ts.forEachChild(node, (child) => visit(child, nextClassName));\n return;\n }\n ts.forEachChild(node, (child) => visit(child, className));\n };\n\n visit(sourceFile, undefined);\n return results;\n}\n\n/**\n * @description Constructs an `ExportedSymbol` from an AST declaration node, attaching JSDoc, flags, and type signature where available.\n * @param name - The exported symbol name.\n * @param declNode - The specific declaration node (e.g. function or variable declarator) used for flags and signature extraction.\n * @param stmtNode - The parent statement node used for JSDoc extraction.\n * @param sourceFile - The source file, required by the TS printer for signature serialisation.\n * @returns The fully populated `ExportedSymbol`.\n */\nfunction makeExportedSymbol(\n name: string,\n declNode: ts.Node,\n stmtNode: ts.Node,\n sourceFile: ts.SourceFile,\n): ExportedSymbol {\n const sym: ExportedSymbol = { name };\n const doc = extractJsDoc(stmtNode);\n if (doc !== undefined) sym.doc = doc;\n const flags = extractJsDocFlags(declNode);\n if (flags !== undefined) sym.flags = flags;\n const sig = extractSignature(declNode, sourceFile);\n if (sig !== undefined) sym.signature = sig;\n return sym;\n}\n\n/**\n * @description Extracts the text of the first JSDoc comment block attached to a node.\n * @param node - The AST node to inspect.\n * @returns The comment text, or `undefined` if no JSDoc is present.\n */\nfunction extractJsDoc(node: ts.Node): string | undefined {\n const cmts = ts.getJSDocCommentsAndTags(node);\n for (const cmtNode of cmts) {\n if (ts.isJSDoc(cmtNode) && cmtNode.comment) {\n return ts.getTextOfJSDocComment(cmtNode.comment) || undefined;\n }\n }\n return undefined;\n}\n\n/**\n * @description Extracts known JSDoc tag names from a node.\n *\n * Only a fixed set of tags is recognised: `deprecated`, `internal`, `public`, `alpha`, `beta`.\n * Unknown tags are ignored so that project-specific markers don't pollute the symbol metadata.\n * @param node - The AST node to inspect.\n * @returns Array of matched tag names, or `undefined` if none are present.\n */\nfunction extractJsDocFlags(node: ts.Node): string[] | undefined {\n const KNOWN = new Set([\"deprecated\", \"internal\", \"public\", \"alpha\", \"beta\"]);\n const flags = ts\n .getJSDocTags(node)\n .map((jsDocTag) => jsDocTag.tagName.text)\n .filter((name) => KNOWN.has(name));\n return flags.length > 0 ? flags : undefined;\n}\n\n/**\n * @description Serialises the type signature of a declaration node into a human-readable string.\n *\n * Covers functions, methods, variable declarations (including arrow functions), classes,\n * interfaces, type aliases, and enums. Returns `undefined` for node kinds with no\n * meaningful signature (e.g. plain object literals).\n * @param node - The declaration node to serialise.\n * @param sourceFile - Required by the TS printer to resolve node text.\n * @returns The signature string, or `undefined` if the node kind is not supported.\n */\nfunction extractSignature(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {\n const printer = ts.createPrinter({ removeComments: true });\n const print = (tsNode: ts.Node) => printer.printNode(ts.EmitHint.Unspecified, tsNode, sourceFile);\n\n if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n const params = node.parameters.map(print).join(\", \");\n const ret = node.type ? print(node.type) : \"void\";\n const tps = node.typeParameters\n ? `<${node.typeParameters.map((tp) => tp.name.text).join(\", \")}>`\n : \"\";\n return `${tps}(${params}) => ${ret}`;\n }\n if (ts.isVariableDeclaration(node)) {\n if (node.type) return print(node.type);\n if (\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))\n ) {\n const fn = node.initializer;\n const params = fn.parameters.map(print).join(\", \");\n const ret = fn.type ? print(fn.type) : \"unknown\";\n return `(${params}) => ${ret}`;\n }\n return undefined;\n }\n if (ts.isClassDeclaration(node) && node.name) return `class ${node.name.text}`;\n if (ts.isInterfaceDeclaration(node)) return `interface ${node.name.text}`;\n if (ts.isTypeAliasDeclaration(node)) return print(node.type);\n if (ts.isEnumDeclaration(node)) return `enum ${node.name.text}`;\n return undefined;\n}\n\n/**\n * @description Dispatches a single AST node to all analysis handlers that update the parse context.\n * @param node - The current AST node being visited.\n * @param ctx - The shared parse context accumulating imports, exports, tags, and category hints.\n */\nfunction analyzeNode(node: ts.Node, ctx: ParseContext) {\n updateStatementCounts(node, ctx);\n updateCategoryHints(node, ctx);\n handleImports(node, ctx);\n handleExports(node, ctx);\n handleCalls(node, ctx);\n handleTagging(node, ctx);\n}\n\n/**\n * @description Counts total and export statements in a source file and writes the totals to the parse context.\n *\n * Only runs for `SourceFile` nodes — all other node kinds are ignored.\n * The counts are later used by `determineCategory` to detect barrel files.\n * @param node - The current AST node; only `SourceFile` nodes are processed.\n * @param ctx - The parse context to update.\n */\nfunction updateStatementCounts(node: ts.Node, ctx: ParseContext) {\n if (!ts.isSourceFile(node)) return;\n const statements = node.statements.filter((statement) => !ts.isEmptyStatement(statement));\n ctx.totalStatements = statements.length;\n ctx.exportStatements = statements.filter(\n (statement) =>\n ts.isExportDeclaration(statement) ||\n ts.isExportAssignment(statement) ||\n hasExportModifier(statement),\n ).length;\n}\n\n/**\n * @description Updates `hasUI` and `hasTypesOnly` flags on the context based on the current node kind.\n *\n * JSX nodes set `hasUI`; function/class/variable/enum nodes clear `hasTypesOnly`.\n * Non-type-only `ExportDeclaration` nodes (re-exports of values) also clear `hasTypesOnly`.\n * Both flags feed into `determineCategory` after the full AST walk.\n * @param node - The current AST node.\n * @param ctx - The parse context whose flags are mutated.\n */\nfunction updateCategoryHints(node: ts.Node, ctx: ParseContext) {\n if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) {\n ctx.hasUI = true;\n ctx.hasTypesOnly = false;\n return;\n }\n if (\n ts.isFunctionDeclaration(node) ||\n ts.isMethodDeclaration(node) ||\n ts.isArrowFunction(node) ||\n ts.isClassDeclaration(node) ||\n ts.isVariableStatement(node) ||\n ts.isEnumDeclaration(node)\n ) {\n ctx.hasTypesOnly = false;\n return;\n }\n if (ts.isExportDeclaration(node) && !isTypeOnlyExportDecl(node)) {\n ctx.hasTypesOnly = false;\n }\n}\n\n/**\n * @description Returns true if an export declaration exports only type-level bindings.\n *\n * Covers `export type { ... }` (declaration-level) and `export { type Foo, type Bar }`\n * (element-level, TypeScript 4.5+). Star re-exports without `type` are treated as value\n * exports because their symbol kind is not statically knowable.\n */\nfunction isTypeOnlyExportDecl(node: ts.ExportDeclaration): boolean {\n if (node.isTypeOnly) return true;\n if (!node.exportClause || !ts.isNamedExports(node.exportClause)) return false;\n return node.exportClause.elements.every((el) => el.isTypeOnly);\n}\n\n/**\n * @description Handles a static `import` declaration and pushes an `ImportEdge` onto the context.\n *\n * Symbol extraction distinguishes default imports, named imports, and namespace imports (`* as ns`).\n * A declaration with no import clause (side-effect import) produces an edge with no symbols.\n * @param node - The current AST node; only `ImportDeclaration` nodes are processed.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleImports(node: ts.Node, ctx: ParseContext) {\n if (!ts.isImportDeclaration(node)) return;\n if (!node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) return;\n\n const symbols: string[] = [];\n if (node.importClause) {\n if (node.importClause.name) symbols.push(\"default\");\n if (node.importClause.namedBindings) {\n if (ts.isNamedImports(node.importClause.namedBindings)) {\n for (const element of node.importClause.namedBindings.elements) {\n symbols.push(element.name.text);\n }\n } else if (ts.isNamespaceImport(node.importClause.namedBindings)) {\n symbols.push(\"*\");\n }\n }\n }\n\n const type: ImportType = symbols.length > 0 ? \"static\" : \"side-effect\";\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: node.moduleSpecifier.text,\n isStyle: isStyleFile(node.moduleSpecifier.text),\n type,\n symbols: symbols.length > 0 ? symbols : undefined,\n });\n}\n\n/**\n * @description Visits an AST node and records any exports it declares into `ctx`.\n *\n * Handles three syntactic forms:\n *\n * 1. **Re-export with source** (`export { A, B } from './mod'` / `export * from './mod'`):\n * Adds an `ImportEdge` of type `\"re-export\"` so the graph captures the cross-file\n * relationship. Symbols are the named exports, or `[\"*\"]` for a star re-export.\n *\n * 2. **Local re-export** (`export { localName }`):\n * Registers the symbol in `ctx.exports`; no import edge is created because no\n * external module is referenced.\n *\n * 3. **Inline export modifier** (`export function foo`, `export const bar`, `export default`):\n * Registers the exported name (or `\"default\"` for `export default`) in `ctx.exports`.\n * @param node - The current AST node to inspect.\n * @param ctx - The parse context whose `exports` and `imports` are updated.\n */\nfunction handleExports(node: ts.Node, ctx: ParseContext) {\n if (ts.isExportDeclaration(node)) {\n handleExportDeclaration(node, ctx);\n } else if (ts.isExportAssignment(node)) {\n ctx.exports.set(\"default\", { name: \"default\" });\n } else if (hasExportModifier(node)) {\n handleInlineExport(node, ctx);\n }\n}\n\n/**\n * @description Handles `export { ... }` and `export { ... } from '...'` / `export * from '...'`.\n *\n * When a module specifier is present this is a re-export edge; otherwise it is a\n * local symbol registration.\n * @param node - The export declaration node.\n * @param ctx - The parse context to update.\n */\nfunction handleExportDeclaration(node: ts.ExportDeclaration, ctx: ParseContext) {\n if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {\n handleReExport(node, node.moduleSpecifier.text, ctx);\n } else if (node.exportClause && ts.isNamedExports(node.exportClause)) {\n for (const element of node.exportClause.elements) {\n const name = element.name.text;\n ctx.exports.set(name, { name });\n }\n }\n}\n\n/**\n * @description Records a cross-module re-export as an `ImportEdge`.\n *\n * Extracts named symbols from `export { A } from '...'`, or uses `\"*\"` for\n * `export * from '...'` (no export clause).\n * @param node - The export declaration node.\n * @param specifier - The raw module specifier string from the source.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleReExport(node: ts.ExportDeclaration, specifier: string, ctx: ParseContext) {\n const symbols = extractReExportSymbols(node);\n const edge: ImportEdge = {\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: isStyleFile(specifier),\n type: \"re-export\",\n };\n if (symbols.length > 0) edge.symbols = symbols;\n ctx.imports.push(edge);\n}\n\n/**\n * @description Returns the exported symbol names from a re-export declaration.\n *\n * Returns `[\"*\"]` when the export clause is absent (star re-export), or an empty\n * array for namespace re-exports (`export * as ns from '...'`) which are not yet tracked.\n * @param node - The export declaration node to inspect.\n * @returns Array of symbol names, or `[\"*\"]` for a star re-export.\n */\nfunction extractReExportSymbols(node: ts.ExportDeclaration): string[] {\n if (!node.exportClause) return [\"*\"];\n if (ts.isNamedExports(node.exportClause)) {\n return node.exportClause.elements.map((el) => el.name.text);\n }\n return [];\n}\n\n/**\n * @description Handles declarations that carry an `export` modifier, e.g.:\n * `export function foo`, `export class Bar`, `export const baz`, `export type T`.\n *\n * Registers each exported name into `ctx.exports` with its signature and JSDoc metadata.\n * @param node - The exported declaration node.\n * @param ctx - The parse context whose `exports` map is updated.\n */\nfunction handleInlineExport(node: ts.Node, ctx: ParseContext) {\n const isNamedDeclaration =\n ts.isFunctionDeclaration(node) ||\n ts.isClassDeclaration(node) ||\n ts.isInterfaceDeclaration(node) ||\n ts.isTypeAliasDeclaration(node) ||\n ts.isEnumDeclaration(node);\n\n if (isNamedDeclaration && node.name) {\n const name = node.name.text;\n ctx.exports.set(name, makeExportedSymbol(name, node, node, ctx.sourceFile));\n return;\n }\n\n if (ts.isVariableStatement(node)) {\n for (const decl of node.declarationList.declarations) {\n if (ts.isIdentifier(decl.name)) {\n const name = decl.name.text;\n ctx.exports.set(name, makeExportedSymbol(name, decl, node, ctx.sourceFile));\n }\n }\n }\n}\n\n/**\n * @description Handles dynamic `import()` calls and `require()` calls, pushing import edges onto the context.\n *\n * The string argument is hoisted before the call-type check so both branches share the\n * same guard, removing a level of nesting. Non-string (computed) specifiers are silently ignored.\n * @param node - The current AST node; only `CallExpression` nodes are processed.\n * @param ctx - The parse context whose `imports` array is updated.\n */\nfunction handleCalls(node: ts.Node, ctx: ParseContext) {\n if (!ts.isCallExpression(node)) return;\n\n const arg = node.arguments[0];\n if (!arg || !ts.isStringLiteral(arg)) return;\n\n if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: arg.text,\n isStyle: isStyleFile(arg.text),\n type: \"dynamic\",\n });\n } else if (ts.isIdentifier(node.expression) && node.expression.text === \"require\") {\n ctx.imports.push({\n fromPath: ctx.filePath,\n toPath: \"\",\n rawSpecifier: arg.text,\n isStyle: isStyleFile(arg.text),\n type: \"require\",\n });\n }\n}\n\n/**\n * @description Classifies a parsed file into a `NodeCategory` based on file name patterns, imports, and AST shape.\n *\n * Checks are ordered from most to least specific: explicit test files, config files,\n * testing-library imports, JSX/UI presence, barrel ratio, type-only content, and finally\n * the default `\"logic\"` bucket.\n * @param filePath - The file path, checked against test and config name patterns.\n * @param ctx - The parse context with accumulated category hints from the AST walk.\n * @returns The most specific matching `NodeCategory`.\n */\nfunction determineCategory(filePath: string, ctx: ParseContext): NodeCategory {\n const baseName = path.basename(filePath).toLowerCase();\n const ext = path.extname(filePath).toLowerCase();\n\n // 1. Explicit test files\n if (getTestPatterns().some((pattern) => baseName.includes(pattern))) {\n return \"test\";\n }\n\n // 2. Configuration files (built-in list + user-registered matchers)\n if (isConfigFile(baseName)) {\n return \"config\";\n }\n\n // 3. UI detection (JSX/TSX or explicit UI elements or testing library imports)\n const importsTestingLib = ctx.imports.some((imp) =>\n getTestLibraries().some((lib) => imp.rawSpecifier.includes(lib)),\n );\n\n if (importsTestingLib) return \"test\";\n\n if (ext === \".tsx\" || ext === \".jsx\" || ctx.hasUI) return \"ui\";\n\n // 4. Type-only files (interfaces, types, type-only re-exports)\n if (ctx.hasTypesOnly && ctx.totalStatements > 0) return \"type-only\";\n\n // 5. Barrel files (mostly value exports/re-exports)\n if (ctx.totalStatements > 0 && ctx.exportStatements / ctx.totalStatements > getBarrelThreshold())\n return \"barrel\";\n\n return \"logic\";\n}\n\n/**\n * @description Checks whether a node has an `export` keyword modifier.\n * @param node - The AST node to inspect.\n * @returns `true` if the node carries an `export` modifier.\n */\nfunction hasExportModifier(node: ts.Node): boolean {\n return (\n ts.canHaveModifiers(node) &&\n ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ===\n true\n );\n}\n\n/**\n * @description Builds a map of imported symbol names to their module specifiers, then walks\n * every top-level exported function body and every class method body to collect\n * caller→callee→specifier triples. Class method edges use `ClassName.methodName` as\n * the `from` field. Populates `ctx.rawCallEdges` in place; skipped entirely for test files.\n * @param {ParseContext} ctx - The parse context whose `rawCallEdges` array is populated.\n * @param {ts.SourceFile} sourceFile - The TypeScript source file AST used to enumerate statements.\n */\nfunction collectRawCallEdges(ctx: ParseContext, sourceFile: ts.SourceFile): void {\n const edges: RawCallEdge[] = ctx.rawCallEdges ?? [];\n ctx.rawCallEdges = edges;\n\n const importSymbolMap = new Map<string, string>();\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;\n const specifier = stmt.moduleSpecifier.text;\n const clause = stmt.importClause;\n if (!clause) continue;\n if (clause.name) importSymbolMap.set(clause.name.text, specifier);\n if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {\n for (const el of clause.namedBindings.elements) {\n importSymbolMap.set(el.name.text, specifier);\n }\n }\n }\n if (importSymbolMap.size === 0) return;\n\n for (const stmt of sourceFile.statements) {\n const fnName = getTopLevelExportedFunctionName(stmt);\n if (fnName) {\n const body = getFunctionBody(stmt);\n if (body) walkCallExpressions(body, fnName, importSymbolMap, edges);\n continue;\n }\n if (ts.isClassDeclaration(stmt) && stmt.name) {\n collectClassMethodCallEdges(stmt, importSymbolMap, edges);\n }\n }\n}\n\n/**\n * @description Walks every method and constructor in a class declaration and records\n * call edges for any imported symbol invocations found in their bodies.\n * Edge `from` fields are formatted as `ClassName.methodName` (or `ClassName.constructor`).\n * @param {ts.ClassDeclaration} classDecl - The class declaration to walk.\n * @param {Map<string, string>} importSymbolMap - Maps local import names to their module specifiers.\n * @param {RawCallEdge[]} edges - Accumulator array that receives discovered edges.\n */\nfunction collectClassMethodCallEdges(\n classDecl: ts.ClassDeclaration,\n importSymbolMap: Map<string, string>,\n edges: RawCallEdge[],\n): void {\n const className = classDecl.name!.text;\n for (const member of classDecl.members) {\n if (ts.isMethodDeclaration(member) && member.body && ts.isIdentifier(member.name)) {\n walkCallExpressions(member.body, `${className}.${member.name.text}`, importSymbolMap, edges);\n } else if (ts.isConstructorDeclaration(member) && member.body) {\n walkCallExpressions(member.body, `${className}.constructor`, importSymbolMap, edges);\n }\n }\n}\n\n/**\n * @description Extracts the name of a top-level exported function from a statement.\n * Recognises both `export function foo` and `export const foo = () => ...` forms.\n * @param stmt - The top-level statement to inspect.\n * @returns The function name, or `undefined` if the statement is not an exported function.\n */\nfunction getTopLevelExportedFunctionName(stmt: ts.Statement): string | undefined {\n if (!hasExportModifier(stmt)) return undefined;\n if (ts.isFunctionDeclaration(stmt) && stmt.name) return stmt.name.text;\n if (ts.isVariableStatement(stmt)) {\n for (const decl of stmt.declarationList.declarations) {\n if (\n ts.isIdentifier(decl.name) &&\n decl.initializer &&\n (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))\n ) {\n return decl.name.text;\n }\n }\n }\n return undefined;\n}\n\n/**\n * @description Extracts the body node from a top-level function declaration or a variable-declared\n * arrow/function expression. Used to scope the call-expression walk to a single function.\n * @param stmt - The top-level statement to inspect.\n * @returns The body node, or `undefined` if the statement is neither a function declaration\n * nor a variable-declared function expression.\n */\nfunction getFunctionBody(stmt: ts.Statement): ts.Node | undefined {\n if (ts.isFunctionDeclaration(stmt)) return stmt.body;\n if (ts.isVariableStatement(stmt)) {\n for (const decl of stmt.declarationList.declarations) {\n if (\n decl.initializer &&\n (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))\n ) {\n return decl.initializer;\n }\n }\n }\n return undefined;\n}\n\n/**\n * @description Recursively walks an AST subtree and records every direct call to an imported\n * symbol as a `RawCallEdge`. Deduplicates so the same (from, to, specifier) triple is only\n * pushed once.\n * @param node - The AST node to walk.\n * @param fnName - The name of the enclosing exported function, used as the `from` field on edges.\n * @param importSymbolMap - Maps local import names to their module specifiers.\n * @param result - Accumulator array that receives discovered edges.\n */\nfunction walkCallExpressions(\n node: ts.Node,\n fnName: string,\n importSymbolMap: Map<string, string>,\n result: RawCallEdge[],\n): void {\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {\n const callee = node.expression.text;\n const specifier = importSymbolMap.get(callee);\n if (\n specifier &&\n !result.some(\n (callEdgeEntry) =>\n callEdgeEntry.from === fnName &&\n callEdgeEntry.to === callee &&\n callEdgeEntry.toSpecifier === specifier,\n )\n ) {\n result.push({ from: fnName, to: callee, toSpecifier: specifier });\n }\n }\n ts.forEachChild(node, (child) => walkCallExpressions(child, fnName, importSymbolMap, result));\n}\n","/** Computes McCabe cyclomatic complexity and cognitive complexity for TypeScript/JavaScript source files. */\nimport ts from \"typescript\";\n\n/**\n * @description Computes McCabe cyclomatic complexity for an AST node: every independent\n * decision point counts (base 1) — `if`, ternary, `for`, `while`, `do`, `switch case`,\n * `catch`, and each `&&` / `||` / `??` operator.\n * @param {ts.Node} rootNode - The AST root node to analyse — a whole `ts.SourceFile` for\n * file-level totals, or any function-like node to score it in isolation.\n * @returns {number} The cyclomatic complexity score, minimum 1.\n */\nexport function computeCyclomaticComplexity(rootNode: ts.Node): number {\n let complexity = 1;\n\n function walkCyclomatic(node: ts.Node): void {\n switch (node.kind) {\n case ts.SyntaxKind.IfStatement:\n case ts.SyntaxKind.ConditionalExpression:\n case ts.SyntaxKind.ForStatement:\n case ts.SyntaxKind.ForInStatement:\n case ts.SyntaxKind.ForOfStatement:\n case ts.SyntaxKind.WhileStatement:\n case ts.SyntaxKind.DoStatement:\n case ts.SyntaxKind.CatchClause:\n case ts.SyntaxKind.CaseClause:\n complexity++;\n break;\n case ts.SyntaxKind.BinaryExpression: {\n const operatorKind = (node as ts.BinaryExpression).operatorToken.kind;\n if (\n operatorKind === ts.SyntaxKind.AmpersandAmpersandToken ||\n operatorKind === ts.SyntaxKind.BarBarToken ||\n operatorKind === ts.SyntaxKind.QuestionQuestionToken\n ) {\n complexity++;\n }\n break;\n }\n }\n ts.forEachChild(node, walkCyclomatic);\n }\n\n walkCyclomatic(rootNode);\n return complexity;\n}\n\n/**\n * @description Computes a simplified SonarSource-style cognitive complexity score for an AST\n * node, tracking how hard the code is to read by adding a nesting penalty. Structural nodes\n * (`if`, loops, `switch`, `catch`) increment by `1 + current nesting depth` and increase the\n * depth for their children. Chained `else if` gets +1 (no nesting bonus). A bare `else` gets\n * +1. Logical operators and ternaries each add +1 without nesting. Nested functions (lambdas,\n * inner functions) add `1 + depth` and increase nesting.\n * @param {ts.Node} rootNode - The AST root node to analyse — a whole `ts.SourceFile` for\n * file-level totals, or any function-like node to score it in isolation (nesting depth\n * resets to 0 at `rootNode`).\n * @returns {number} The cognitive complexity score, minimum 0.\n */\nexport function computeCognitiveComplexity(rootNode: ts.Node): number {\n let cognitiveComplexity = 0;\n\n function walkCognitive(node: ts.Node, depth: number, isElseIf: boolean): void {\n if (ts.isIfStatement(node)) {\n // else-if chains: flat +1; fresh if: +1 + nesting\n cognitiveComplexity += isElseIf ? 1 : 1 + depth;\n const bodyDepth = isElseIf ? depth : depth + 1;\n walkCognitive(node.expression, bodyDepth, false);\n walkCognitive(node.thenStatement, bodyDepth, false);\n if (node.elseStatement) {\n if (ts.isIfStatement(node.elseStatement)) {\n walkCognitive(node.elseStatement, depth, true);\n } else {\n cognitiveComplexity += 1; // bare else\n walkCognitive(node.elseStatement, depth + 1, false);\n }\n }\n return;\n }\n\n if (\n ts.isForStatement(node) ||\n ts.isForInStatement(node) ||\n ts.isForOfStatement(node) ||\n ts.isWhileStatement(node) ||\n ts.isDoStatement(node) ||\n ts.isSwitchStatement(node)\n ) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth + 1, false));\n return;\n }\n\n if (ts.isCatchClause(node)) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth, false));\n return;\n }\n\n if (ts.isConditionalExpression(node)) {\n cognitiveComplexity += 1;\n }\n\n if (ts.isBinaryExpression(node)) {\n const operatorKind = node.operatorToken.kind;\n if (\n operatorKind === ts.SyntaxKind.AmpersandAmpersandToken ||\n operatorKind === ts.SyntaxKind.BarBarToken ||\n operatorKind === ts.SyntaxKind.QuestionQuestionToken\n ) {\n cognitiveComplexity += 1;\n }\n }\n\n // Nested functions and lambdas increase nesting for their body\n const isNestedFunction =\n depth > 0 &&\n (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node));\n if (isNestedFunction) {\n cognitiveComplexity += 1 + depth;\n ts.forEachChild(node, (child) => walkCognitive(child, depth + 1, false));\n return;\n }\n\n ts.forEachChild(node, (child) => walkCognitive(child, depth, false));\n }\n\n walkCognitive(rootNode, 0, false);\n return cognitiveComplexity;\n}\n\n/**\n * @description Computes both McCabe cyclomatic complexity and a simplified SonarSource-style\n * cognitive complexity for a TypeScript/JavaScript AST node, by composing\n * `computeCyclomaticComplexity` and `computeCognitiveComplexity`.\n * @param {ts.Node} node - The AST root node to analyse — a whole `ts.SourceFile` for file-level\n * totals, or any function-like node to score it in isolation.\n * @returns {{ complexity: number; cognitiveComplexity: number }} Both scores, minimum 1 / 0 respectively.\n */\nexport function computeComplexity(node: ts.Node): {\n complexity: number;\n cognitiveComplexity: number;\n} {\n return {\n complexity: computeCyclomaticComplexity(node),\n cognitiveComplexity: computeCognitiveComplexity(node),\n };\n}\n","/** Collects structured tags from a TypeScript/JavaScript AST node using declaration names, @marker strings, comment annotations, and Vitest/Playwright option bags. */\nimport ts from \"typescript\";\nimport type { TagKind } from \"../../types/parse\";\nimport type { ParseContext } from \"../types\";\n\nconst TEST_CALL_NAMES = new Set([\"test\", \"describe\", \"it\"]);\n\n/**\n * @description Collects tags from a single AST node into `ctx.tags` using four strategies:\n * declaration names, string-literal `@` markers, comment `@tag` annotations, and\n * Vitest/Playwright option-bag arrays. Each strategy applies its own type guard so only\n * relevant nodes produce output.\n * @param node - The AST node currently being visited.\n * @param ctx - Mutable parse context accumulating tags for the current source file.\n */\nexport function handleTagging(node: ts.Node, ctx: ParseContext): void {\n collectDeclarationNameTags(node, ctx);\n collectStringLiteralAtTags(node, ctx);\n collectCommentAnnotationTags(node, ctx);\n collectVitestOptionBagTags(node, ctx);\n}\n\n/**\n * @description Adds the name of any top-level function or variable declaration to `ctx.tags`,\n * tagging it as `\"function\"` or `\"variable\"` based on its initializer.\n * Declarations nested inside callbacks or test blocks are skipped to avoid noise.\n * @param node - The AST node being visited.\n * @param ctx - Mutable parse context that receives the new tag.\n */\nfunction collectDeclarationNameTags(node: ts.Node, ctx: ParseContext): void {\n if (\n (ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node)) &&\n node.name &&\n ts.isIdentifier(node.name) &&\n isTopLevel(node)\n ) {\n let kind: TagKind;\n if (ts.isFunctionDeclaration(node)) {\n kind = \"function\";\n } else {\n const init = node.initializer;\n kind =\n init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))\n ? \"function\"\n : \"variable\";\n }\n ctx.tags.add({ name: node.name.text, kind });\n }\n}\n\n/**\n * @description Determines whether a function or variable declaration sits directly under the\n * source file root, distinguishing top-level exports from declarations nested in callbacks or blocks.\n * @param node - A function or variable declaration node to test.\n * @returns True if the node is a direct child of the `SourceFile`.\n */\nfunction isTopLevel(node: ts.FunctionDeclaration | ts.VariableDeclaration): boolean {\n if (ts.isFunctionDeclaration(node)) return ts.isSourceFile(node.parent);\n const stmt = node.parent?.parent; // VariableDeclarationList → VariableStatement\n return !!stmt && ts.isSourceFile(stmt.parent);\n}\n\n/**\n * @description Scans a string literal for `@word` patterns and records each matched word\n * as a `comment-marker` tag, enabling tag extraction from test-title strings like `'login @smoke'`.\n * @param node - The AST node to inspect; only string literals produce output.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectStringLiteralAtTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isStringLiteral(node)) return;\n const matches = node.text.match(/@[\\w-]+/g);\n if (matches) {\n for (const tag of matches) ctx.tags.add({ name: tag.substring(1), kind: \"comment-marker\" });\n }\n}\n\n/**\n * @description Scans the full source text for `@tag <name>` annotations and records each `<name>`\n * as a `comment-marker` tag. Only runs when `node` is the `SourceFile` so the text is scanned exactly once per file.\n * @param node - The current AST node; processing is skipped unless it is a `SourceFile`.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectCommentAnnotationTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isSourceFile(node)) return;\n const tagRegex = /@tag\\s+([a-zA-Z0-9_-]+)/g;\n const fullText = node.getFullText();\n let match = tagRegex.exec(fullText);\n while (match !== null) {\n if (match[1]) ctx.tags.add({ name: match[1], kind: \"comment-marker\" });\n match = tagRegex.exec(fullText);\n }\n}\n\n/**\n * @description Inspects call expressions that match test-framework functions and extracts tags\n * from any object-literal argument. Handles both direct calls (`test(...)`) and chained forms\n * like `it.each(...)` or `describe.skip(...)`.\n * @param node - The AST node to inspect; only call expressions are processed.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectVitestOptionBagTags(node: ts.Node, ctx: ParseContext): void {\n if (!ts.isCallExpression(node)) return;\n\n if (!isTestCallExpression(node.expression)) return;\n\n for (const arg of node.arguments) {\n if (!ts.isObjectLiteralExpression(arg)) continue;\n collectTagsFromObjectLiteral(arg, ctx);\n }\n}\n\n/**\n * @description Returns true if the callee expression resolves to a test-framework function\n * (`test`, `describe`, or `it`), recognising both bare identifiers and property-access\n * forms such as `it.skip` or `describe.concurrent`.\n * @param callee - The callee expression of a call node to classify.\n * @returns True when the expression refers to a known test-framework entry point.\n */\nfunction isTestCallExpression(callee: ts.Expression): boolean {\n if (ts.isIdentifier(callee)) return TEST_CALL_NAMES.has(callee.text);\n if (ts.isPropertyAccessExpression(callee)) {\n // `something.test(...)` / `something.describe(...)`\n if (TEST_CALL_NAMES.has(callee.name.text)) return true;\n // `it.skip(...)` / `test.concurrent(...)` — base is the test function\n if (ts.isIdentifier(callee.expression) && TEST_CALL_NAMES.has(callee.expression.text))\n return true;\n }\n return false;\n}\n\n/**\n * @description Reads the `tags` (Vitest array) or `tag` (Playwright string or array) property\n * from an object literal and records each value as a `comment-marker` tag, stripping any\n * leading `@` so both frameworks produce the same normalised tag name.\n * @param obj - The object literal expression from a test call's option argument.\n * @param ctx - Mutable parse context that receives extracted tags.\n */\nfunction collectTagsFromObjectLiteral(obj: ts.ObjectLiteralExpression, ctx: ParseContext): void {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text !== \"tags\" && prop.name.text !== \"tag\") continue;\n\n const { initializer } = prop;\n const values: ts.StringLiteral[] = ts.isArrayLiteralExpression(initializer)\n ? initializer.elements.filter(ts.isStringLiteral)\n : prop.name.text === \"tag\" && ts.isStringLiteral(initializer)\n ? [initializer]\n : [];\n\n for (const el of values) {\n ctx.tags.add({ name: el.text.replace(/^@/, \"\"), kind: \"comment-marker\" });\n }\n }\n}\n","/** Classifies CSS/Less files as barrels (import-only) or UI files based on PostCSS AST analysis. */\nimport type postcss from \"postcss\";\nimport type { ImportEdge } from \"../../types/node\";\nimport type { NodeCategory } from \"../../types/parse\";\n\n// TODO(SOLID-I): only `imports.length` is read; parameter could be narrowed to `{ length: number }`\n/**\n * @description Classifies a CSS or Less file as a barrel (imports only) or a UI file (contains CSS rules).\n * @param {postcss.Root} root - The PostCSS AST of the parsed file; walked to detect any `rule` nodes\n * @param {ImportEdge[]} imports - The edges already extracted from the file; only the count is used to short-circuit empty files\n * @returns {NodeCategory} `\"barrel\"` when the file has imports but no CSS rules, `\"ui\"` otherwise\n */\nexport function detectCssBarrel(root: postcss.Root, imports: ImportEdge[]): NodeCategory {\n if (imports.length === 0) return \"ui\";\n let hasRule = false;\n root.walk((node) => {\n if (node.type === \"rule\") {\n hasRule = true;\n return false;\n }\n });\n return hasRule ? \"ui\" : \"barrel\";\n}\n","/** Parses CSS and Less files using PostCSS to extract @import edges. */\nimport postcss from \"postcss\";\nimport type { ImportEdge } from \"../../types/node\";\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst lessParser = require(\"postcss-less\") as {\n parse: postcss.Parser<postcss.Root>;\n stringify: postcss.Stringifier;\n};\n\nconst SIDE_EFFECT_KEYWORDS = new Set([\"reference\", \"inline\"]);\n\n/**\n * @description Returns true when a CSS import specifier points to an external resource rather than a local file.\n * @param {string} specifier - The raw import path as written in the source (e.g. `~bootstrap`, `https://…`)\n * @returns {boolean} `true` for tilde-prefixed node_modules, absolute URLs, protocol-relative URLs, and data URIs\n */\nfunction isExternalCss(specifier: string): boolean {\n return (\n specifier.startsWith(\"~\") ||\n specifier.startsWith(\"http://\") ||\n specifier.startsWith(\"https://\") ||\n specifier.startsWith(\"//\") ||\n specifier.startsWith(\"data:\")\n );\n}\n\n/**\n * @description Returns true when a `url()` value refers to a file on disk rather than an external or fragment URL.\n * @param {string} specifier - The raw value extracted from a `url()` expression, before any trimming\n * @returns {boolean} `true` for relative or absolute local paths; `false` for HTTP URLs, protocol-relative URLs, data URIs, and hash fragments\n */\nfunction isLocalUrl(specifier: string): boolean {\n const trimmed = specifier.trim();\n return (\n trimmed.length > 0 &&\n !trimmed.startsWith(\"http://\") &&\n !trimmed.startsWith(\"https://\") &&\n !trimmed.startsWith(\"//\") &&\n !trimmed.startsWith(\"data:\") &&\n !trimmed.startsWith(\"#\")\n );\n}\n\n/**\n * @description Parses the params string of a PostCSS `@import` at-rule into a single import edge.\n * Handles three syntaxes: Less modifier form `(keyword) \"path\"`, `url(\"path\")`, and bare `\"path\"`.\n * @param {string} params - The raw text after `@import`, exactly as PostCSS exposes it (no leading `@import`)\n * @param {string} filePath - Absolute path of the file being parsed, used as the `fromPath` of the edge\n * @returns {ImportEdge | null} An `ImportEdge` when the params contain a recognisable import path, or `null` for empty or malformed params\n */\nfunction extractAtImportEdge(params: string, filePath: string): ImportEdge | null {\n // Less modifier: (keyword) \"path\" or (keyword) 'path'\n const lessMatch = params.match(/^\\(([^)]+)\\)\\s+['\"]([^'\"]+)['\"]/);\n if (lessMatch) {\n const keyword = lessMatch[1]?.trim() ?? \"\";\n const specifier = lessMatch[2] ?? \"\";\n if (!specifier) return null;\n const type = SIDE_EFFECT_KEYWORDS.has(keyword) ? \"side-effect\" : \"static\";\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type,\n ...(isExternalCss(specifier) ? { isExternal: true } : {}),\n };\n }\n // url() form: url(\"path\") or url('path') or url(path)\n const urlMatch = params.match(/^url\\(['\"]?([^'\")]+)['\"]?\\)/);\n const specifier = urlMatch\n ? (urlMatch[1]?.trim() ?? \"\")\n : (params.match(/^['\"]([^'\"]+)['\"]/)?.[1] ?? \"\");\n if (!specifier) return null;\n return {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n ...(isExternalCss(specifier) ? { isExternal: true } : {}),\n };\n}\n\n/**\n * @description Extracts all local `url()` references from a single CSS declaration value as import edges.\n * @param {string} value - The raw CSS property value string (e.g. `url(\"./bg.png\") center`)\n * @param {string} filePath - Absolute path of the file being parsed, used as `fromPath` on each edge\n * @returns {ImportEdge[]} One edge per local `url()` found; external URLs and data URIs are skipped\n */\nfunction extractUrlDeclarationEdges(value: string, filePath: string): ImportEdge[] {\n const edges: ImportEdge[] = [];\n const urlPattern = /url\\(['\"]?([^'\")]+)['\"]?\\)/g;\n let match = urlPattern.exec(value);\n while (match !== null) {\n const specifier = match[1]?.trim() ?? \"\";\n if (isLocalUrl(specifier)) {\n edges.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n });\n }\n match = urlPattern.exec(value);\n }\n return edges;\n}\n\n/**\n * @description Walks a parsed PostCSS tree and collects every import edge — both `@import` at-rules and `url()` references in declarations.\n * @param {postcss.Root} root - The PostCSS root node produced by parsing a CSS or Less file\n * @param {string} filePath - Absolute path of the source file; forwarded to edge constructors as `fromPath`\n * @returns {ImportEdge[]} All import edges found in the tree, in document order\n */\nfunction collectEdgesFromRoot(root: postcss.Root, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n root.walk((node) => {\n if (node.type === \"atrule\" && node.name === \"import\") {\n const edge = extractAtImportEdge(node.params, filePath);\n if (edge) imports.push(edge);\n }\n if (node.type === \"decl\") {\n imports.push(...extractUrlDeclarationEdges(node.value, filePath));\n }\n });\n return imports;\n}\n\n/**\n * @description Removes `//` line comments from CSS source so PostCSS can parse files that use non-standard comment syntax.\n * @param {string} content - Raw CSS file contents, potentially containing `//` comments\n * @returns {string} The content with `//`-to-end-of-line sequences removed, leaving `://` (URLs) intact\n */\nfunction stripLineComments(content: string): string {\n // `//` is not valid CSS but is widely used; strip before passing to PostCSS.\n // Negative lookbehind on `:` avoids stripping `//` inside `https://` or `http://` URLs.\n return content.replace(/(?<!:)\\/\\/.*/g, \"\");\n}\n\n/**\n * @description Extracts `@import` edges from raw CSS/Less source using a regex when the PostCSS parser fails.\n * Only captures `@import` at-rules; `url()` references in declarations are not extracted here.\n * @param {string} content - Raw file contents that could not be parsed by PostCSS\n * @param {string} filePath - Absolute path of the file being parsed, used as `fromPath` on each edge\n * @returns {ImportEdge[]} All `@import` edges found by pattern matching, with no barrel/side-effect detection for url() forms\n */\nfunction regexFallbackImports(content: string, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n const atImportPattern = /@import\\s+(?:\\(([^)]+)\\)\\s+)?['\"]([^'\"]+)['\"]/g;\n let match = atImportPattern.exec(content);\n while (match !== null) {\n const keyword = match[1]?.trim() ?? \"\";\n const specifier = match[2] ?? \"\";\n if (specifier) {\n const type = SIDE_EFFECT_KEYWORDS.has(keyword) ? \"side-effect\" : \"static\";\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type,\n });\n }\n match = atImportPattern.exec(content);\n }\n return imports;\n}\n\n/**\n * @description Parses a CSS file and returns its import edges alongside the PostCSS AST.\n * Strips non-standard `//` line comments before parsing so that common CSS-in-JS and preprocessor conventions do not cause a parse error.\n * @param {string} content - Raw CSS file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root, which callers use for barrel detection\n */\nexport function parseCssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n // Strip // line comments — not valid CSS but common; PostCSS throws on them\n const root = postcss.parse(stripLineComments(content));\n return { imports: collectEdgesFromRoot(root, filePath), root };\n}\n\n/**\n * @description Parses a Less file and returns its import edges alongside the PostCSS AST.\n * Falls back to regex-only extraction when `postcss-less` throws, so mixed or malformed Less files still yield at least the `@import` edges.\n * @param {string} content - Raw Less file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root (may be empty on parse failure)\n */\nexport function parseLessContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n try {\n const root = lessParser.parse(content);\n return { imports: collectEdgesFromRoot(root, filePath), root };\n } catch {\n // Fallback when content mixes non-Less syntax (e.g., bare `import` without @).\n // Use regex to extract @import edges only; barrel detection gets an empty root.\n return { imports: regexFallbackImports(content, filePath), root: postcss.parse(\"\") };\n }\n}\n","/** Parses SCSS/Sass files using postcss-scss to extract @use, @forward, and @import edges. */\nimport type postcss from \"postcss\";\nimport { parse as scssParse } from \"postcss-scss\";\nimport type { ImportEdge } from \"../../types/node\";\n\n/**\n * @description Returns true when a SCSS/Sass import specifier resolves outside the local file tree.\n * @param {string} specifier - The raw import path as written in source (e.g. `sass:color`, `~bootstrap`, `./tokens`)\n * @returns {boolean} `true` for built-in Sass namespaces, tilde node_modules shortcuts, HTTP/protocol-relative URLs, and bare package names\n */\nfunction isScssExternal(specifier: string): boolean {\n // Built-in Sass namespaces (sass:color, sass:math, etc.)\n if (specifier.startsWith(\"sass:\")) return true;\n // Webpack/Less tilde convention for node_modules\n if (specifier.startsWith(\"~\")) return true;\n // HTTP/protocol-relative URLs\n if (\n specifier.startsWith(\"http://\") ||\n specifier.startsWith(\"https://\") ||\n specifier.startsWith(\"//\")\n )\n return true;\n // Bare package name: no leading `.`, `/`, or `_` (Sass partial convention)\n if (!specifier.startsWith(\".\") && !specifier.startsWith(\"/\") && !specifier.startsWith(\"_\"))\n return true;\n return false;\n}\n\n/**\n * @description Extracts the import path and optional namespace alias from a SCSS `@use` or `@forward` params string.\n * @param {string} params - The raw text after the at-rule keyword (e.g. `\"./tokens\" as t`)\n * @returns {{ specifier: string; alias?: string }} The resolved specifier and, when an `as` clause is present, the alias name\n */\nfunction parseScssParams(params: string): { specifier: string; alias?: string } {\n const specMatch = params.match(/^['\"]([^'\"]+)['\"]/);\n if (!specMatch?.[1]) return { specifier: \"\" };\n const specifier = specMatch[1];\n const asMatch = params.match(/\\bas\\s+(\\S+)/);\n const alias = asMatch?.[1];\n return alias !== undefined ? { specifier, alias } : { specifier };\n}\n\n/**\n * @description Parses a SCSS file and returns its import edges alongside the PostCSS AST.\n * Recognises `@import`, `@use`, and `@forward` at-rules; marks `@forward` edges as `re-export` and attaches namespace aliases when an `as` clause is present.\n * @param {string} content - Raw SCSS file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {{ imports: ImportEdge[]; root: postcss.Root }} The collected import edges and the PostCSS root, which callers use for barrel detection\n */\nexport function parseScssContent(\n content: string,\n filePath: string,\n): { imports: ImportEdge[]; root: postcss.Root } {\n const root = scssParse(content) as postcss.Root;\n const imports: ImportEdge[] = [];\n\n root.walk((node) => {\n if (node.type !== \"atrule\") return;\n const { name, params } = node;\n if (name !== \"import\" && name !== \"use\" && name !== \"forward\") return;\n\n const { specifier, alias } = parseScssParams(params);\n if (!specifier) return;\n\n const edge: ImportEdge = {\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: name === \"forward\" ? \"re-export\" : \"static\",\n ...(isScssExternal(specifier) ? { isExternal: true } : {}),\n };\n if (alias) edge.symbols = [alias];\n imports.push(edge);\n });\n\n return { imports, root };\n}\n","/** Parses Stylus files to extract @require and bare import/require dependency edges. */\nimport type { ImportEdge } from \"../../types/node\";\n\n/**\n * @description Extracts all import edges from a Stylus file, covering both `@require` and bare `import`/`require` forms.\n * @param {string} content - Raw Stylus file contents\n * @param {string} filePath - Absolute path of the file; used as `fromPath` on each returned edge\n * @returns {ImportEdge[]} All import edges found, with `@require` entries typed as `\"require\"` and bare forms as `\"static\"`\n */\nexport function parseStylusImports(content: string, filePath: string): ImportEdge[] {\n const imports: ImportEdge[] = [];\n\n const atRequirePattern = /@require\\s+['\"]([^'\"]+)['\"]/g;\n let match = atRequirePattern.exec(content);\n while (match !== null) {\n const specifier = match[1];\n if (specifier) {\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"require\",\n });\n }\n match = atRequirePattern.exec(content);\n }\n\n // Negative lookbehind on @ avoids re-matching @require entries above\n const bareImportPattern = /(?<!@)(?:import|require)\\s*\\(?\\s*['\"]([^'\"]+)['\"]/g;\n match = bareImportPattern.exec(content);\n while (match !== null) {\n const specifier = match[1];\n if (specifier) {\n imports.push({\n fromPath: filePath,\n toPath: \"\",\n rawSpecifier: specifier,\n isStyle: true,\n type: \"static\",\n });\n }\n match = bareImportPattern.exec(content);\n }\n\n return imports;\n}\n\n// TODO(SOLID-I): only `imports.length` is read; parameter could be narrowed to `{ length: number }`\n/**\n * @description Classifies a Stylus file as a barrel (re-exports only) or a UI file (contains rules or styles).\n * Attempts AST analysis via the optional `stylus` library; falls back to regex stripping when unavailable.\n * @param {string} content - Raw Stylus file contents, used for both AST parsing and the regex fallback\n * @param {ImportEdge[]} imports - The edges already extracted from the file; only the count is used to short-circuit empty files\n * @returns {\"ui\" | \"barrel\"} `\"barrel\"` when the file contains only imports, `\"ui\"` when it also defines rules or styles\n */\nexport function detectStylusCategory(content: string, imports: ImportEdge[]): \"ui\" | \"barrel\" {\n if (imports.length === 0) return \"ui\";\n\n // Try Stylus AST for files using @require/@import (common form).\n // The Stylus Parser AST correctly identifies Import vs rule Group nodes.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const stylusLib = require(\"stylus\") as {\n Parser: new (src: string) => { parse(): { nodes: Array<{ constructor: { name: string } }> } };\n };\n const ast = new stylusLib.Parser(content).parse();\n const hasNonImport = ast.nodes.some((astNode) => astNode.constructor.name !== \"Import\");\n return hasNonImport ? \"ui\" : \"barrel\";\n } catch {\n // Fallback: strip all import/require lines and check if any content remains.\n // Handles bare `import 'path'`, `require('path')`, and `@require 'path'` forms.\n const withoutImports = content.replace(/^\\s*@?(?:require|import)\\b.*/gm, \"\").trim();\n return withoutImports.length > 0 ? \"ui\" : \"barrel\";\n }\n}\n","/** Dispatches style file parsing to the appropriate dialect handler (CSS, Less, SCSS, Sass, Stylus). */\nimport { getFileType } from \"../file-type\";\nimport type { ParseResult } from \"../types\";\nimport { detectCssBarrel } from \"./barrel\";\nimport { parseCssContent, parseLessContent } from \"./css\";\nimport { parseScssContent } from \"./scss\";\nimport { detectStylusCategory, parseStylusImports } from \"./stylus\";\n\n// TODO(SOLID-O): adding a new style dialect (e.g. Sass indented) requires editing this function; consider a parser registry keyed by file type\n/**\n * @description Parses a style file of any supported dialect and returns a normalised `ParseResult`.\n * Delegates to the dialect-specific parser based on the file extension, then wraps the result in the standard shape with empty `exports` and `tags`.\n * @param {string} filePath - Absolute path to the style file; determines which parser is selected\n * @param {string} content - Raw file contents to parse\n * @returns {ParseResult} Import edges, empty exports/tags, and a category classification for the file\n */\nexport function parseStyleFile(filePath: string, content: string): ParseResult {\n const fileType = getFileType(filePath);\n\n if (fileType === \"stylus\") {\n const imports = parseStylusImports(content, filePath);\n return {\n imports,\n exports: [],\n tags: [],\n category: detectStylusCategory(content, imports),\n };\n }\n\n if (fileType === \"scss\") {\n const { imports, root } = parseScssContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n }\n\n if (fileType === \"less\") {\n const { imports, root } = parseLessContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n }\n\n // css (and any unknown style type)\n const { imports, root } = parseCssContent(content, filePath);\n return { imports, exports: [], tags: [], category: detectCssBarrel(root, imports) };\n}\n","/** Aggregates all language parsers and exposes parseFile and parseImports as the unified entry points. */\nimport { getFileType } from \"./parser/file-type\";\nimport { parseCoffeeScript } from \"./parser/lang/coffee\";\nimport { parseGherkin } from \"./parser/lang/gherkin\";\nimport { parseGo } from \"./parser/lang/go\";\nimport { parseLiveScript } from \"./parser/lang/ls\";\nimport { parseLua } from \"./parser/lang/lua\";\nimport { parsePython } from \"./parser/lang/python\";\nimport { parseCodeFile } from \"./parser/lang/typescript\";\nimport type { ParserFunction } from \"./parser/registry\";\nimport { getParserForType, registerParser } from \"./parser/registry\";\nimport { parseStyleFile } from \"./parser/style\";\nimport type { ParseResult } from \"./parser/types\";\nimport type { ImportEdge } from \"./types/node\";\nimport type { FileType } from \"./types/parse\";\n\nfor (const [type, parser] of [\n [\"javascript\", (path, content) => parseCodeFile(path, content, \"javascript\")],\n [\"typescript\", (path, content) => parseCodeFile(path, content, \"typescript\")],\n [\"css\", parseStyleFile],\n [\"scss\", parseStyleFile],\n [\"less\", parseStyleFile],\n [\"stylus\", parseStyleFile],\n [\"coffeescript\", parseCoffeeScript],\n [\"livescript\", parseLiveScript],\n [\"lua\", parseLua],\n [\"python\", parsePython],\n [\"go\", parseGo],\n [\"gherkin\", parseGherkin],\n] satisfies [FileType, ParserFunction][]) {\n registerParser(type, parser);\n}\n\nexport {\n getBarrelThreshold,\n getTestLibraries,\n getTestPatterns,\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n setBarrelThreshold,\n} from \"./parser/classify.js\";\nexport { getFileType } from \"./parser/file-type.js\";\nexport { registerParser } from \"./parser/registry.js\";\n\n/**\n * @description Main entry point for parsing a file. Dispatches to a registered\n * language-specific parser based on the file's extension, falling back to an\n * empty result for unknown types.\n * @param {string} filePath - Absolute or relative path to the file; determines which parser is used.\n * @param {string} content - Raw source content of the file.\n * @returns {Promise<ParseResult>} Parsed imports, exports, tags, and category for the file.\n */\nexport async function parseFile(filePath: string, content: string): Promise<ParseResult> {\n const fileType = getFileType(filePath);\n const parser = getParserForType(fileType);\n\n if (parser) {\n return parser(filePath, content);\n }\n\n return { imports: [], exports: [], tags: [], category: \"other\" };\n}\n\n/**\n * @description Parses a file and returns only its import edges, discarding exports, tags, and category.\n * @param {string} filePath - Path to the file being parsed; determines the parser to use.\n * @param {string} content - Raw source content of the file.\n * @returns {Promise<ImportEdge[]>} All import edges extracted from the file.\n */\nexport async function parseImports(filePath: string, content: string): Promise<ImportEdge[]> {\n const result = await parseFile(filePath, content);\n return result.imports;\n}\n","/** Post-build enrichment passes that annotate graph nodes with coverage, library tags, test links, and export-usage ratios. */\nimport path from \"node:path\";\nimport type { FileNode, ImportEdge, StructuredTag } from \"../types/node\";\n\n/**\n * @description Annotates each node with its line-coverage percentage from a pre-loaded\n * coverage map. Nodes not present in the map are left untouched (`coveragePct` remains\n * undefined). Only called when the map is non-empty.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n * @param coverageMap - Map of project-relative path → line-coverage percentage (0–100).\n */\nexport function enrichCoverage(\n nodes: Map<string, FileNode>,\n coverageMap: Map<string, number>,\n): void {\n for (const node of nodes.values()) {\n const pct = coverageMap.get(node.path);\n if (pct !== undefined) node.coveragePct = pct;\n }\n}\n\n/**\n * @description Scans a file's import edges and appends a structured `import`-kind tag for every\n * third-party library found. Scoped packages (`@scope/pkg/deep`) are normalised to their\n * two-segment name before deduplication.\n * @param imports - The resolved import edges for the file being enriched.\n * @param tags - The tag array for that same file; modified in place.\n */\nexport function enrichLibraryTags(imports: ImportEdge[], tags: StructuredTag[]): void {\n for (const imp of imports) {\n if (!imp.rawSpecifier.startsWith(\".\") && !path.isAbsolute(imp.rawSpecifier)) {\n const libName = imp.rawSpecifier.startsWith(\"@\")\n ? imp.rawSpecifier.split(\"/\").slice(0, 2).join(\"/\")\n : imp.rawSpecifier.split(\"/\")[0];\n if (libName && !tags.some((existingTag) => existingTag.name === libName)) {\n tags.push({ name: libName, kind: \"library\" });\n }\n }\n }\n}\n\n/**\n * @description Walks every test node in the graph and records it as a tester of each\n * `logic` or `barrel` node it imports. Populates `FileNode.testedBy` so that an AI\n * (or human) can ask \"what tests cover this file?\" without re-running dynamic analysis.\n * Only internal, resolved imports are considered; external imports are ignored.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichTestedBy(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.category !== \"test\") continue;\n for (const imp of node.imports) {\n if (imp.isExternal || !imp.toPath) continue;\n const target = nodes.get(imp.toPath);\n if (!target) continue;\n if (target.category !== \"logic\" && target.category !== \"barrel\") continue;\n target.testedBy ??= [];\n if (!target.testedBy.includes(node.path)) target.testedBy.push(node.path);\n }\n }\n}\n\nfunction round4(value: number): number {\n return Math.round(value * 10000) / 10000;\n}\n/**\n * @description Computes a `exportUsageRatio` for each internal import edge and aggregates\n * `avgExportUsage` and `maxExportUsage` per node. The ratio is the fraction of the target\n * file's exports consumed by this import (`importedSymbols / target.exports.length`).\n * Namespace imports (`[\"*\"]`) and unresolved re-exports are treated as full usage (1.0).\n * Side-effect imports and edges where the target has zero exports are skipped.\n * @param nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichExportUsage(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n const ratios: number[] = [];\n for (const imp of node.imports) {\n if (imp.isExternal || !imp.toPath) continue;\n const target = nodes.get(imp.toPath);\n if (!target || target.exports.length === 0) continue;\n\n let ratio: number;\n if (imp.symbols === undefined) {\n if (imp.type === \"side-effect\") continue;\n ratio = 1.0;\n } else if (imp.symbols.includes(\"*\")) {\n ratio = 1.0;\n } else {\n ratio = imp.symbols.length / target.exports.length;\n }\n\n imp.exportUsageRatio = round4(Math.min(1, ratio));\n ratios.push(imp.exportUsageRatio);\n }\n\n if (ratios.length > 0) {\n node.avgExportUsage = round4(ratios.reduce((sum, ratio) => sum + ratio, 0) / ratios.length);\n node.maxExportUsage = Math.max(...ratios);\n }\n }\n}\n\n/**\n * @description Appends `{ name, kind }` to `tags` unless an entry with the same `name` and\n * `kind` already exists. Centralises the dedup check shared by every tag-adding step in\n * `enrichTestNodeTags`.\n * @param {StructuredTag[]} tags - The tag array to append to; mutated in place.\n * @param {string} name - The tag name to add.\n * @param {StructuredTag[\"kind\"]} kind - The tag kind to add.\n */\nfunction addUniqueTag(tags: StructuredTag[], name: string, kind: StructuredTag[\"kind\"]): void {\n if (!name) return;\n if (tags.some((existingTag) => existingTag.name === name && existingTag.kind === kind)) return;\n tags.push({ name, kind });\n}\n\n/**\n * @description Adds a filename-derived `import` tag to `testNode` for the file it imports\n * (e.g. a test importing `graph/builder.ts` receives the tag `builder`). Test-suffix\n * extensions (`.test`/`.spec`) are stripped from the derived name.\n * @param {FileNode} testNode - The test node receiving the tag; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge to derive the tag from.\n */\nfunction addFilenameTag(testNode: FileNode, importEdge: ImportEdge): void {\n const toPath = importEdge.toPath as string;\n const filenameTag = path.basename(toPath, path.extname(toPath)).replace(/\\.(test|spec)$/, \"\");\n addUniqueTag(testNode.tags, filenameTag, \"import\");\n}\n\n/**\n * @description Adds an `import` tag for each named symbol imported by `importEdge`.\n * Namespace imports (`[\"*\"]`) are skipped since there is no single symbol name to tag.\n * @param {FileNode} testNode - The test node receiving the tags; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge whose `symbols` are tagged.\n */\nfunction addSymbolTags(testNode: FileNode, importEdge: ImportEdge): void {\n if (!importEdge.symbols || importEdge.symbols.includes(\"*\")) return;\n for (const symbolName of importEdge.symbols) {\n addUniqueTag(testNode.tags, symbolName, \"import\");\n }\n}\n\n/**\n * @description Propagates `comment-marker` tags (e.g. `@tag auth` in the source file) from\n * the imported node to `testNode`, so tests inherit the semantic markers of what they test.\n * Skips imports that resolve to another test node.\n * @param {FileNode} testNode - The test node receiving the tags; mutated in place.\n * @param {ImportEdge} importEdge - The resolved import edge whose target is inspected.\n * @param {Map<string, FileNode>} nodes - The full node map, used to look up the import target.\n */\nfunction propagateCommentMarkers(\n testNode: FileNode,\n importEdge: ImportEdge,\n nodes: Map<string, FileNode>,\n): void {\n const sourceNode = nodes.get(importEdge.toPath as string);\n if (!sourceNode || sourceNode.category === \"test\") return;\n for (const sourceTag of sourceNode.tags) {\n if (sourceTag.kind !== \"comment-marker\") continue;\n addUniqueTag(testNode.tags, sourceTag.name, \"comment-marker\");\n }\n}\n\n/**\n * @description Adds tags derived from each local import to the importing test node.\n * Two tag kinds are applied: a filename-derived `import` tag (e.g. a test importing\n * `graph/builder.ts` receives the tag `builder`), and any `comment-marker` tags\n * propagated from the source node (e.g. `@tag auth` in `auth/service.ts` propagates\n * to tests that import it). `function` and `variable` kind tags are intentionally skipped\n * as they are too granular for test filtering. Existing duplicate tags are skipped.\n * @param {Map<string, FileNode>} nodes - The full node map produced by the graph builder; mutated in place.\n */\nexport function enrichTestNodeTags(nodes: Map<string, FileNode>): void {\n for (const node of nodes.values()) {\n if (node.category !== \"test\") continue;\n for (const importEdge of node.imports) {\n if (!importEdge.toPath || importEdge.isExternal) continue;\n\n addFilenameTag(node, importEdge);\n addSymbolTags(node, importEdge);\n propagateCommentMarkers(node, importEdge, nodes);\n }\n }\n}\n","/** DefaultResolver turns raw import specifiers into absolute file paths, handling relative paths, tsconfig aliases, workspace packages, and node_modules. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n GoLangResolver,\n type LangResolver,\n LuaLangResolver,\n PythonLangResolver,\n type ResolvedImport,\n} from \"./lang-resolvers/index\";\n\nexport type { ResolvedImport };\n\n/**\n * @description Contract for resolving an import specifier to one or more absolute file paths\n * given the file that contains the import.\n */\nexport interface PathResolver {\n /**\n * @description Resolves an import specifier to an absolute path and whether it is\n * outside the project root. Returns the first result when multiple files are possible\n * (e.g. a Go package directory). Use `resolveAll` to get every file.\n * @param currentFile - Absolute path of the file containing the import statement.\n * @param specifier - The raw import specifier string (e.g. `\"./utils\"` or `\"lodash\"`).\n * @returns Resolved path and external flag, or `null` if resolution fails.\n */\n resolve(currentFile: string, specifier: string): ResolvedImport | null;\n\n /**\n * @description Resolves an import specifier to all matching local files. For most languages\n * this is identical to `resolve` (one file). For Go packages it returns every non-test\n * `.go` file in the target directory. Returns an empty array if resolution fails.\n * @param currentFile - Absolute path of the file containing the import statement.\n * @param specifier - The raw import specifier string.\n * @returns Array of resolved imports (may be empty).\n */\n resolveAll(currentFile: string, specifier: string): ResolvedImport[];\n}\n\n/** @description Configuration options for `DefaultResolver`, used to support monorepo and alias-aware resolution. */\nexport interface ResolverOptions {\n /**\n * Maps workspace package names to their absolute root directories.\n * When set, matching specifiers are resolved as internal workspace imports\n * rather than external npm packages.\n */\n workspaceMap?: Map<string, string>;\n /**\n * Ordered list of directories to search for `tsconfig.json` when resolving\n * path aliases. Defaults to `[rootDir]`. For monorepo builds, pass\n * `[packageRoot, monorepoRoot]` so per-package aliases take precedence.\n */\n tsconfigSearchPaths?: string[];\n /**\n * Language-specific resolvers that handle bare (non-relative) specifiers before\n * falling through to the external-module default. Defaults to Python, Lua, and Go resolvers.\n */\n langResolvers?: LangResolver[];\n}\n\n/**\n * @description Default import resolver that handles relative paths, absolute paths,\n * tsconfig path aliases, workspace packages, Lua dot-separated modules, and external node_modules.\n */\nexport class DefaultResolver implements PathResolver {\n private readonly workspaceMap: Map<string, string>;\n private readonly tsconfigSearchPaths: string[];\n private readonly langResolvers: LangResolver[];\n\n /**\n * @param rootDir - Absolute path to the project root, used as the boundary for\n * deciding whether a resolved path is internal or external.\n * @param options - Optional workspace map, tsconfig search paths, and lang resolvers.\n */\n constructor(\n private rootDir: string,\n options: ResolverOptions = {},\n ) {\n this.workspaceMap = options.workspaceMap ?? new Map();\n this.tsconfigSearchPaths = options.tsconfigSearchPaths ?? [rootDir];\n this.langResolvers = options.langResolvers ?? [\n new PythonLangResolver(),\n new LuaLangResolver(),\n new GoLangResolver(),\n ];\n }\n\n /**\n * @description Resolves a specifier to all matching files. For most languages this returns\n * a single-element array (or empty on failure). For Go packages it returns one entry per\n * non-test `.go` file in the target directory.\n * @param currentFile - Absolute path of the file containing the import.\n * @param specifier - The raw import specifier to resolve.\n * @returns Array of resolved imports; empty if resolution fails.\n */\n public resolveAll(currentFile: string, specifier: string): ResolvedImport[] {\n // 1. Path aliases → always single result\n const aliased = this.resolvePathAlias(specifier);\n if (aliased) return [aliased];\n\n // 2. Relative / absolute local paths → always single result\n if (specifier.startsWith(\".\") || specifier.startsWith(\"/\")) {\n const resolved = this.resolveLocalPath(currentFile, specifier);\n return resolved ? [resolved] : [];\n }\n\n // 3. Language-specific — may return multiple files (e.g. Go packages)\n const resolveLocal = (cf: string, spec: string) => this.resolveLocalPath(cf, spec);\n for (const lr of this.langResolvers) {\n if (lr.extensions.some((ext) => currentFile.endsWith(ext))) {\n const locals = lr.resolve(currentFile, specifier, this.rootDir, resolveLocal);\n if (locals) return locals;\n }\n }\n\n // 4. Workspace package\n const workspace = this.resolveWorkspaceImport(specifier);\n if (workspace) return [workspace];\n\n // 5. External\n return [{ path: specifier, isExternal: true }];\n }\n\n /**\n * @description Resolves a specifier by trying path aliases first, then relative/absolute\n * local paths, then Lua dot-notation, and finally treating the specifier as an external module.\n * @param currentFile - Absolute path of the file containing the import.\n * @param specifier - The raw import specifier to resolve.\n * @returns Resolved path and external flag, or `null` if no local file can be found.\n */\n public resolve(currentFile: string, specifier: string): ResolvedImport | null {\n // 1. Try Path Aliases (tsconfig.json paths)\n const aliased = this.resolvePathAlias(specifier);\n if (aliased) return aliased;\n\n // 2. Handle Relative or Absolute Local Paths\n if (specifier.startsWith(\".\") || specifier.startsWith(\"/\")) {\n return this.resolveLocalPath(currentFile, specifier);\n }\n\n // 3. Language-specific resolution (Python, Lua, Go, …)\n const resolveLocal = (cf: string, spec: string) => this.resolveLocalPath(cf, spec);\n for (const lr of this.langResolvers) {\n if (lr.extensions.some((ext) => currentFile.endsWith(ext))) {\n const locals = lr.resolve(currentFile, specifier, this.rootDir, resolveLocal);\n if (locals) return locals[0] ?? null;\n }\n }\n\n // 4. Workspace package resolution — check before falling through to external\n const workspace = this.resolveWorkspaceImport(specifier);\n if (workspace) return workspace;\n\n // 5. Non-relative, non-absolute import (likely a node_module or built-in)\n return { path: specifier, isExternal: true };\n }\n\n /**\n * @description Resolves a relative or absolute specifier to a concrete file path by\n * trying multiple extensions and index-file fallbacks, including ESM `.js`→`.ts` rewriting.\n * @param currentFile - Absolute path of the importing file, used to compute the base directory.\n * @param specifier - A relative (`./foo`) or absolute (`/foo`) import specifier.\n * @returns Resolved path and external flag, or `null` if no matching file is found within the project.\n */\n private resolveLocalPath(currentFile: string, specifier: string): ResolvedImport | null {\n const dir = path.dirname(currentFile);\n const fullPath = specifier.startsWith(\"/\") ? specifier : path.resolve(dir, specifier);\n const isExternal = !fullPath.startsWith(this.rootDir);\n\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".css\",\n \".scss\",\n \".sass\",\n \".less\",\n \".styl\",\n \".coffee\",\n \".ls\",\n \".lua\",\n \".py\",\n \".feature\",\n ];\n\n // ESM Support: If specifier ends with .js/.mjs/.cjs, try stripping it to allow .ts resolution\n const esmMatch = fullPath.match(/\\.(js|mjs|cjs)$/);\n if (esmMatch) {\n const strippedPath = fullPath.slice(0, -esmMatch[0].length);\n for (const ext of [\".ts\", \".tsx\"]) {\n const resolved = this.tryExtensions(strippedPath, ext, isExternal);\n if (resolved) return resolved;\n }\n }\n\n for (const ext of extensions) {\n const resolved = this.tryExtensions(fullPath, ext, isExternal);\n if (resolved) return resolved;\n }\n\n // Fallback for external absolute paths that couldn't be resolved with extensions\n return isExternal ? { path: fullPath, isExternal: true } : null;\n }\n\n /**\n * @description Checks whether `fullPath + ext` resolves to an existing file or an\n * `index` file inside `fullPath` as a directory.\n * @param fullPath - The candidate path without extension.\n * @param ext - Extension to append, including the dot (e.g. `\".ts\"`), or empty string to try as-is.\n * @param isExternal - Whether the path falls outside the project root.\n * @returns Resolved path and external flag, or `null` if neither variant exists.\n */\n private tryExtensions(fullPath: string, ext: string, isExternal: boolean): ResolvedImport | null {\n // Try file directly\n const candidatePath = fullPath + ext;\n if (this.isFile(candidatePath)) {\n return { path: candidatePath, isExternal };\n }\n\n // Try index file in directory (JS/TS convention: index.ts)\n const indexP = path.join(fullPath, `index${ext}`);\n if (this.isFile(indexP)) {\n return { path: indexP, isExternal };\n }\n\n // Python convention: __init__.py for packages\n if (ext === \".py\") {\n const initP = path.join(fullPath, \"__init__.py\");\n if (this.isFile(initP)) {\n return { path: initP, isExternal };\n }\n }\n\n return null;\n }\n\n /**\n * @description Safely checks whether a path refers to a regular file without throwing\n * on missing entries or permission errors.\n * @param filePath - Absolute path to test.\n * @returns `true` if the path exists and is a regular file.\n */\n private isFile(filePath: string): boolean {\n try {\n const stats = fs.statSync(filePath, { throwIfNoEntry: false });\n return stats?.isFile() === true;\n } catch {\n return false;\n }\n }\n\n /**\n * @description Reads `tsconfig.json` from the project root and attempts to match the\n * specifier against configured `compilerOptions.paths` aliases, trying each substitution\n * with multiple extensions.\n * @param specifier - The import specifier to match against path aliases.\n * @returns Resolved path and external flag if an alias matches, or `null` otherwise.\n */\n private resolvePathAlias(specifier: string): ResolvedImport | null {\n for (const searchDir of this.tsconfigSearchPaths) {\n const tsconfigPath = path.join(searchDir, \"tsconfig.json\");\n if (!fs.existsSync(tsconfigPath)) continue;\n\n try {\n const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, \"utf-8\"));\n const paths = tsconfig.compilerOptions?.paths;\n if (!paths) continue;\n\n for (const alias in paths) {\n const match = this.matchAliasPattern(alias, specifier);\n if (match) {\n const resolved = this.tryAliasSubstitutions(paths[alias], match[1] || \"\", searchDir);\n if (resolved) return resolved;\n }\n }\n } catch {\n // Ignore parse errors\n }\n }\n return null;\n }\n\n private aliasRegexCache = new Map<string, RegExp>();\n\n /**\n * @description Converts a tsconfig path alias (e.g. `\"@app/*\"`) to a regex and tests it\n * against the specifier, caching compiled regexes for repeated lookups.\n * @param alias - A tsconfig `paths` key, potentially containing a `*` wildcard.\n * @param specifier - The import specifier to test.\n * @returns The regex match array (including wildcard capture) if matched, or `null`.\n */\n private matchAliasPattern(alias: string, specifier: string): RegExpMatchArray | null {\n let regex = this.aliasRegexCache.get(alias);\n if (!regex) {\n const pattern = alias.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(\"\\\\*\", \"(.*)\");\n regex = new RegExp(`^${pattern}$`);\n this.aliasRegexCache.set(alias, regex);\n }\n return specifier.match(regex);\n }\n\n /**\n * @description Iterates over all substitution templates for a matched alias, replacing\n * the `*` placeholder with the captured wildcard segment, then probing for an existing file.\n * @param substitutions - The array of path templates from tsconfig `paths` (e.g. `[\"src/app/*\"]`).\n * @param wildcardMatch - The portion of the specifier that matched the `*` in the alias pattern.\n * @returns The first substitution that resolves to an existing file, or `null` if none match.\n */\n private tryAliasSubstitutions(\n substitutions: string[],\n wildcardMatch: string,\n baseDir: string = this.rootDir,\n ): ResolvedImport | null {\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\", \".coffee\", \".ls\", \".lua\", \".feature\"];\n\n for (const sub of substitutions) {\n const resolvedSub = sub.replace(\"*\", wildcardMatch);\n const fullPath = path.resolve(baseDir, resolvedSub);\n\n for (const ext of extensions) {\n const resolved = this.tryExtensions(fullPath, ext, false);\n if (resolved) return resolved;\n }\n }\n return null;\n }\n\n /**\n * @description Resolves a specifier against the workspace package map. Handles exact\n * package name matches and deep imports (`@myorg/shared/utils`). Resolved paths are\n * marked `isExternal: false` and `isWorkspace: true` so the builder treats them as\n * internal cross-package edges rather than npm dependencies.\n * @param {string} specifier - The raw import specifier to match against workspace package names.\n * @returns {ResolvedImport | null} Resolved path with workspace flags, or `null` if no package matches.\n */\n private resolveWorkspaceImport(specifier: string): ResolvedImport | null {\n if (this.workspaceMap.size === 0) return null;\n\n for (const [pkgName, pkgRoot] of this.workspaceMap) {\n if (specifier !== pkgName && !specifier.startsWith(`${pkgName}/`)) continue;\n\n const subPath = specifier.slice(pkgName.length); // \"\" or \"/deep/path\"\n const base: ResolvedImport = {\n path: \"\",\n isExternal: false,\n isWorkspace: true,\n workspacePackage: pkgName,\n };\n\n if (!subPath) {\n // Resolve to package entry — try common conventions\n for (const candidate of [\n \"src/index.ts\",\n \"src/index.tsx\",\n \"index.ts\",\n \"index.tsx\",\n \"index.js\",\n ]) {\n const abs = path.join(pkgRoot, candidate);\n try {\n if (fs.statSync(abs, { throwIfNoEntry: false })?.isFile()) {\n return { ...base, path: abs };\n }\n } catch {\n /* skip */\n }\n }\n // Fallback: package root itself (builder will handle gracefully)\n return { ...base, path: pkgRoot };\n }\n\n // Deep import: resolve subPath relative to pkgRoot\n const deepResolved = this.resolveLocalPath(path.join(pkgRoot, \"_dummy\"), subPath.slice(1));\n if (deepResolved) return { ...base, path: deepResolved.path };\n\n return null;\n }\n\n return null;\n }\n}\n","/** Language resolver for Go: maps module-local import paths to concrete .go files using go.mod. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\ninterface GoModData {\n /** Declared module path, e.g. `\"github.com/myorg/myrepo\"`. */\n mod: string | null;\n /**\n * `replace` directive map: module path (without version) → absolute local directory.\n * Only local-path replacements (`=> ./foo` or `=> /abs/path`) are recorded;\n * version-to-version redirects (`=> otherpkg v1.2.3`) are ignored.\n */\n replaces: Map<string, string>;\n}\n\n/**\n * @description Resolves Go module-local import paths to all concrete `.go` source files\n * in the target package directory, using `go.mod` for module name and `replace` directives.\n *\n * Two gaps addressed over the previous single-file resolver:\n * 1. Returns every non-test `.go` file in the package directory (one edge per file).\n * 2. Honours `replace` directives that redirect a module path to a local directory.\n *\n * Known remaining limitations (see ADR-007):\n * - Vendor directories are not traversed.\n * - `go.work` workspace files are not read.\n */\nexport class GoLangResolver implements LangResolver {\n extensions = [\".go\"];\n private goModCache = new Map<string, GoModData>();\n\n /**\n * @description Resolves a Go import specifier to all non-test `.go` files in the target\n * package directory. Returns `null` for stdlib, third-party, and root-module imports.\n * @param {string} _currentFile - Absolute path of the importing file (unused; resolution is module-relative).\n * @param {string} specifier - Full Go import path, e.g. `\"github.com/myorg/myrepo/internal/utils\"`.\n * @param {string} rootDir - Absolute project root where `go.mod` is located.\n * @param {Function} _resolveLocal - Generic resolver callback (unused for Go).\n * @returns {ResolvedImport[] | null} All non-test `.go` files in the package, or `null` if external/unresolvable.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n _resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const { mod, replaces } = this.readGoMod(rootDir);\n if (!mod) return null;\n\n // Check replace directives first — they can redirect any module path prefix.\n const redirected = this.applyReplace(specifier, replaces, rootDir);\n if (redirected !== undefined) {\n return goFilesInDir(redirected);\n }\n\n // Standard module-local resolution: specifier must start with the declared module name.\n if (specifier !== mod && !specifier.startsWith(`${mod}/`)) return null;\n\n const rel = specifier.slice(mod.length).replace(/^\\//, \"\");\n if (!rel) return null;\n\n return goFilesInDir(path.join(rootDir, rel));\n }\n\n /**\n * @description Reads and caches `go.mod` from the project root, extracting the module\n * name and any local `replace` directives.\n * @param {string} rootDir - Absolute directory containing `go.mod`.\n * @returns {GoModData} Parsed module data; `mod` is `null` when `go.mod` is absent or malformed.\n */\n private readGoMod(rootDir: string): GoModData {\n const cached = this.goModCache.get(rootDir);\n if (cached !== undefined) return cached;\n\n const empty: GoModData = { mod: null, replaces: new Map() };\n try {\n const content = fs.readFileSync(path.join(rootDir, \"go.mod\"), \"utf-8\");\n const data = parseGoMod(content, rootDir);\n this.goModCache.set(rootDir, data);\n return data;\n } catch {\n this.goModCache.set(rootDir, empty);\n return empty;\n }\n }\n\n /**\n * @description Checks whether the specifier matches any `replace` directive and returns\n * the absolute local directory it maps to, or `undefined` if no match.\n *\n * A replace directive `replace A => ./local` matches specifier `A/pkg/sub`\n * and maps it to `<rootDir>/local/pkg/sub`.\n * @param {string} specifier - The import path to check.\n * @param {Map<string, string>} replaces - Parsed replace map: module prefix → absolute dir.\n * @param {string} rootDir - Project root, used when replacing relative paths.\n * @returns {string | undefined} Absolute target directory, or `undefined` if no directive matches.\n */\n private applyReplace(\n specifier: string,\n replaces: Map<string, string>,\n rootDir: string,\n ): string | undefined {\n for (const [from, toDir] of replaces) {\n if (specifier === from) {\n return toDir;\n }\n if (specifier.startsWith(`${from}/`)) {\n const sub = specifier.slice(from.length + 1);\n return path.join(toDir, sub);\n }\n }\n // Unused parameter kept to avoid signature drift — rootDir is used during parsing.\n void rootDir;\n return undefined;\n }\n}\n\n/**\n * @description Parses a `go.mod` file and extracts the declared module name and all\n * local-path `replace` directives.\n *\n * Handles both block form (`replace ( ... )`) and single-line form (`replace A => B`).\n * Only records directives whose replacement target is a relative or absolute local path\n * (starts with `.` or `/`). Version-to-version redirects are skipped.\n * @param {string} content - Raw text of `go.mod`.\n * @param {string} rootDir - Project root, used to resolve relative replacement paths.\n * @returns {GoModData} Parsed module name and replace map.\n */\nfunction parseGoMod(content: string, rootDir: string): GoModData {\n const lines = content.split(\"\\n\");\n let mod: string | null = null;\n const replaces = new Map<string, string>();\n\n let inReplaceBlock = false;\n\n for (const raw of lines) {\n const line = raw.trim();\n\n if (line.startsWith(\"module \")) {\n mod = line.slice(\"module \".length).trim();\n continue;\n }\n\n // Block open: `replace (`\n if (/^replace\\s*\\(/.test(line)) {\n inReplaceBlock = true;\n continue;\n }\n\n // Block close\n if (inReplaceBlock && line === \")\") {\n inReplaceBlock = false;\n continue;\n }\n\n // Line inside a replace block, e.g. `github.com/org/repo => ../local`\n if (inReplaceBlock && line.includes(\"=>\")) {\n parseReplaceLine(line, rootDir, replaces);\n continue;\n }\n\n // Single-line replace: `replace github.com/org/repo => ../local`\n if (!inReplaceBlock && /^replace\\s+/.test(line) && line.includes(\"=>\")) {\n parseReplaceLine(line.replace(/^replace\\s+/, \"\"), rootDir, replaces);\n }\n }\n\n return { mod, replaces };\n}\n\n/**\n * @description Parses a single replace directive line (without the leading `replace` keyword)\n * and records it in `out` when the target is a local path.\n *\n * Line forms:\n * - `github.com/org/repo => ./local`\n * - `github.com/org/repo v1.0.0 => ./local`\n * - `github.com/org/repo => /absolute/path`\n *\n * Version-to-version targets (`=> other/module v1.2.3`) are ignored.\n * @param {string} line - Trimmed directive text after stripping the `replace` keyword.\n * @param {string} rootDir - Project root for resolving relative replacement paths.\n * @param {Map<string, string>} out - Map to populate with resolved replacements.\n */\nfunction parseReplaceLine(line: string, rootDir: string, out: Map<string, string>): void {\n const [lhs, rhs] = line.split(\"=>\").map((side) => side.trim());\n if (!lhs || !rhs) return;\n\n // Strip optional version from lhs: `github.com/org/repo v1.0.0` → `github.com/org/repo`\n const fromModule = lhs.split(/\\s+/)[0] as string;\n\n // Only handle local path targets (relative or absolute)\n if (!rhs.startsWith(\".\") && !rhs.startsWith(\"/\")) return;\n\n const absTarget = path.isAbsolute(rhs) ? rhs : path.resolve(rootDir, rhs);\n out.set(fromModule, absTarget);\n}\n\n/**\n * @description Returns all non-test `.go` files in a package directory as resolved imports.\n * Files ending in `_test.go` are excluded — they are discovered separately by the builder's\n * test-file scan and should not appear as dependency targets.\n * @param {string} absDir - Absolute path to the Go package directory.\n * @returns {ResolvedImport[] | null} Array of resolved imports, or `null` if the directory\n * is missing or contains no non-test Go files.\n */\nfunction goFilesInDir(absDir: string): ResolvedImport[] | null {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(absDir, { withFileTypes: true });\n } catch {\n return null;\n }\n\n const files = entries\n .filter(\n (dirent) =>\n dirent.isFile() && dirent.name.endsWith(\".go\") && !dirent.name.endsWith(\"_test.go\"),\n )\n .map((dirent): ResolvedImport => ({ path: path.join(absDir, dirent.name), isExternal: false }))\n .sort((resolvedA, resolvedB) => resolvedA.path.localeCompare(resolvedB.path));\n\n return files.length > 0 ? files : null;\n}\n","/** Language resolver for Lua: converts dot-separated module names to local .lua file paths. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\n/**\n * @description Resolves Lua dot-separated module names (e.g. `utils.string`) to local files\n * by converting dots to path separators and probing the project root and a `lib/` sub-directory.\n */\nexport class LuaLangResolver implements LangResolver {\n extensions = [\".lua\"];\n\n /**\n * @description Converts dots in the specifier to path separators and probes the project root\n * and a `lib/` subdirectory using the generic resolver's extension-probing logic.\n * @param {string} _currentFile - Absolute path of the importing file (unused; search is root-relative).\n * @param {string} specifier - Dot-separated Lua module name, e.g. `\"utils.string\"`.\n * @param {string} rootDir - Absolute project root used as the primary search base.\n * @param {Function} resolveLocal - Generic resolver callback for extension and index-file probing.\n * @returns {ResolvedImport | null} Local file path, or `null` if no match is found.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const luaSpecifier = specifier.replace(/\\./g, path.sep);\n const searchBases = [rootDir, path.join(rootDir, \"lib\")];\n\n for (const base of searchBases) {\n if (!fs.existsSync(base)) continue;\n const resolved = resolveLocal(path.join(base, \"_dummy.lua\"), luaSpecifier);\n if (resolved) return [resolved];\n }\n\n return null;\n }\n}\n","/** Language resolver for Python: maps bare module specifiers to local .py files or __init__.py packages. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { LangResolver, ResolvedImport } from \"./types\";\n\n/**\n * @description Resolves bare Python module names (e.g. `mymodule` or `mypackage.sub`)\n * to local `.py` files or `__init__.py` packages inside the project root.\n * Dots in the specifier are treated as path separators.\n */\nexport class PythonLangResolver implements LangResolver {\n extensions = [\".py\"];\n\n /**\n * @description Converts dots in the specifier to path separators and probes for a matching\n * `.py` file or `__init__.py` package relative to the project root.\n * @param {string} _currentFile - Absolute path of the importing file (unused; resolution is root-relative).\n * @param {string} specifier - Bare module name, e.g. `\"mypackage.sub\"`.\n * @param {string} rootDir - Absolute project root used as the search base.\n * @param {Function} _resolveLocal - Generic resolver callback (unused for Python).\n * @returns {ResolvedImport[] | null} Single-element array with the local file, or `null` if no match is found.\n */\n resolve(\n _currentFile: string,\n specifier: string,\n rootDir: string,\n _resolveLocal: (currentFile: string, specifier: string) => ResolvedImport | null,\n ): ResolvedImport[] | null {\n const pyPath = specifier.replace(/\\./g, path.sep);\n\n const pyFile = path.join(rootDir, `${pyPath}.py`);\n if (isFile(pyFile)) return [{ path: pyFile, isExternal: false }];\n\n const initFile = path.join(rootDir, pyPath, \"__init__.py\");\n if (isFile(initFile)) return [{ path: initFile, isExternal: false }];\n\n return null;\n }\n}\n\n/**\n * @description Safely checks whether a path refers to a regular file without throwing on missing entries.\n * @param {string} filePath - Absolute path to test.\n * @returns {boolean} `true` if the path exists and is a regular file.\n */\nfunction isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() === true;\n } catch {\n return false;\n }\n}\n","/** Proposes semantic test tags and affected test files from git diff and the dependency graph. */\n\nimport type { Graph } from \"../graph\";\nimport {\n detectFeatures,\n type FeatureDetectionOptions,\n type FeatureInfo,\n SymbolTraversalContext,\n} from \"../graph\";\nimport type { FileNode } from \"../types/node\";\nimport { DefaultTestNodeIdentifier, type TestNodeIdentifier } from \"./identifier\";\n\n/** @description Options for `proposeTags` and `proposeAffectedTests`, allowing callers to override the test-node identifier and feature-detection behaviour. */\nexport interface ProposeTagsOptions {\n identifier?: TestNodeIdentifier;\n featureDetection?: FeatureDetectionOptions | false;\n}\n\n/**\n * @description Materialises optional settings into concrete implementations.\n *\n * The feature map is computed once here so traversal can do O(1) hub lookups\n * rather than re-running detection on every visited node.\n * @param {Graph} graph - The full project dependency graph, needed to run feature detection.\n * @param {ProposeTagsOptions} [options] - Optional identifier and feature-detection overrides.\n * @returns {{ identifier: TestNodeIdentifier; featureMap: Map<string, FeatureInfo> }} Concrete identifier and pre-computed feature map ready for traversal.\n */\nfunction resolveOptions(\n graph: Graph,\n options?: ProposeTagsOptions,\n): { identifier: TestNodeIdentifier; featureMap: Map<string, FeatureInfo> } {\n return {\n identifier: options?.identifier ?? new DefaultTestNodeIdentifier(),\n featureMap:\n options?.featureDetection === false\n ? new Map()\n : detectFeatures(graph.nodes, options?.featureDetection ?? undefined),\n };\n}\n\n/**\n * @description Walks the incoming dependency graph from each changed file.\n *\n * For every reachable node that passes the symbol-propagation check:\n * - If the node is a feature hub (and not the start node), `onFeatureHub` is\n * called and that branch is pruned — preventing traversal explosions.\n * - Otherwise `onNode` is called so callers can decide what to collect.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified.\n * @param {Map<string, FeatureInfo>} featureMap - Pre-computed map of path → feature hub info.\n * @param {(feature: FeatureInfo) => void} onFeatureHub - Called when a feature hub is encountered; return signals pruning.\n * @param {(node: FileNode) => void} onNode - Called for every non-hub reachable node that passes the symbol check.\n */\nfunction traverseAffected(\n graph: Graph,\n changedFiles: string[],\n featureMap: Map<string, FeatureInfo>,\n onFeatureHub: (feature: FeatureInfo) => void,\n onNode: (node: FileNode) => void,\n): void {\n for (const changed of changedFiles) {\n const startNode = graph.nodes.get(changed);\n if (!startNode) continue;\n\n const context = new SymbolTraversalContext(changed, [\n \"*\",\n ...startNode.exports.map((exportedSym) => exportedSym.name),\n ]);\n\n graph.traverse(\n changed,\n (visitedNode, depth, childPath) => {\n if (!childPath) return true; // start node — always continue\n\n if (!context.updateAffectedSymbols(visitedNode, childPath)) return false;\n\n if (depth > 0) {\n const feature = featureMap.get(visitedNode.path);\n if (feature) {\n onFeatureHub(feature);\n return false; // prune: don't walk past this hub\n }\n }\n\n onNode(visitedNode);\n return true;\n },\n { direction: \"incoming\" },\n );\n }\n}\n\n/**\n * @description Proposes Vitest tags to run based on which files changed.\n *\n * Traverses the incoming dependency graph from each changed file. Test nodes\n * that can reach the changed file contribute their tags. Feature hubs act as\n * boundaries: the hub's tag is emitted and traversal stops there, preventing\n * combinatorial blowup in large graphs.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified (e.g. from git diff).\n * @param {ProposeTagsOptions} [options] - Optional: custom test identifier and feature-detection settings.\n * @returns {string[]} Deduplicated list of tag strings to pass to `vitest --grep`.\n */\nexport function proposeTags(\n graph: Graph,\n changedFiles: string[],\n options?: ProposeTagsOptions,\n): string[] {\n const { identifier, featureMap } = resolveOptions(graph, options);\n const proposedTags = new Set<string>();\n\n // A changed file that is itself a feature hub should immediately emit its tag\n // (it won't appear during incoming traversal since traversal starts from it).\n for (const changed of changedFiles) {\n const feature = featureMap.get(changed);\n if (feature) proposedTags.add(feature.tag);\n }\n\n traverseAffected(\n graph,\n changedFiles,\n featureMap,\n (feature) => proposedTags.add(feature.tag),\n (node) => {\n if (identifier.isTestNode(node)) {\n for (const tag of node.tags) proposedTags.add(tag.name);\n }\n },\n );\n\n return Array.from(proposedTags);\n}\n\n/**\n * @description Returns the file paths of test files affected by the changed files.\n *\n * Traverses the incoming dependency graph from each changed file and collects\n * paths of reachable test nodes. Feature hubs act as traversal boundaries —\n * tests beyond a hub are excluded because the hub's own tag already covers\n * them when using `proposeTags`.\n *\n * The output is a plain list of relative paths suitable for piping directly\n * into Vitest: `vitest $(mokosh --affected-tests)`.\n * @param {Graph} graph - The full project dependency graph.\n * @param {string[]} changedFiles - Relative paths of files that were modified (e.g. from git diff).\n * @param {ProposeTagsOptions} [options] - Optional: custom test identifier and feature-detection settings.\n * @returns {string[]} Deduplicated list of relative test file paths.\n */\nexport function proposeAffectedTests(\n graph: Graph,\n changedFiles: string[],\n options?: ProposeTagsOptions,\n): string[] {\n const { identifier, featureMap } = resolveOptions(graph, options);\n const affectedTests = new Set<string>();\n\n traverseAffected(\n graph,\n changedFiles,\n featureMap,\n () => {}, // feature hubs don't contribute test paths — their sub-graph stays pruned\n (node) => {\n if (identifier.isTestNode(node)) {\n affectedTests.add(node.path);\n }\n },\n );\n\n return Array.from(affectedTests);\n}\n","/** Public library API: createImportMap, createWorkspaceGraph, and getAllProjectFiles. */\n\n// Config\nexport { applyConfig, loadMokoshConfig, type MokoshConfig } from \"./config\";\n\n// Constants\nexport { DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS, type ScanOptions } from \"./const\";\n\n// Coverage\nexport { loadCoverageMap } from \"./coverage\";\n\n// Exporters\nexport { type GraphExporter, MermaidExporter, toMermaid } from \"./exporters\";\n// Graph analysis utilities\nexport {\n type ApiSurface,\n buildApiSurface,\n detectAllEntryPoints,\n detectEntryPoint,\n type ExportKind,\n type PublicExport,\n} from \"./graph/api-surface\";\nexport { queryCallGraph } from \"./graph/call-graph\";\nexport type {\n CalleeEntry,\n CallerEntry,\n FunctionCallInfo,\n} from \"./graph/call-graph/types\";\nexport {\n buildChangeImpactCache,\n type ChangeImpactCache,\n computeGraphHash,\n isChangeImpactCacheValid,\n loadChangeImpactCache,\n queryChangeImpact,\n saveChangeImpactCache,\n} from \"./graph/change-impact-cache\";\nexport {\n detectFeatures,\n type FeatureDetectionOptions,\n type FeatureInfo,\n} from \"./graph/features\";\nexport {\n buildFeatureGraph,\n type FeatureDomain,\n type FeatureGraph,\n type FeatureGraphOptions,\n} from \"./graph/features/feature-graph\";\n// Core graph classes\nexport { Graph } from \"./graph/model\";\nexport { buildResponsibilityGraph } from \"./graph/responsibility\";\nexport type {\n ModuleResponsibility,\n ModuleRole,\n ResponsibilityGraph,\n} from \"./graph/responsibility/types\";\nexport { SymbolTraversalContext } from \"./graph/symbol-traversal\";\nexport {\n buildTypeGraph,\n queryTypeGraph,\n type TypeEdge,\n type TypeGraph,\n type TypeKind,\n type TypeNode,\n type TypeQueryResult,\n} from \"./graph/type-graph\";\n// Monorepo detection + extension point\nexport { detectMonorepo } from \"./graph/workspace\";\nexport { type MonorepoDetector, registerMonorepoDetector } from \"./graph/workspace/registry\";\nexport type { MonorepoLayout, WorkspacePackage } from \"./graph/workspace/types\";\nexport { type SerializedWorkspaceGraph, WorkspaceGraph } from \"./graph/workspace-model\";\nexport {\n registerConfigMatcher,\n registerTestLibrary,\n registerTestPattern,\n} from \"./parser/classify\";\n// Parser extension points\nexport { registerParser } from \"./parser/registry\";\n// Query\nexport { filterGraph, type NodeQuery, parseQuery } from \"./query\";\n// Tags\nexport {\n type ApplyTagsFileResult,\n type ApplyTagsResult,\n applyTags,\n type ProposeTagsOptions,\n proposeAffectedTests,\n proposeTags,\n type TestNodeIdentifier,\n} from \"./tags\";\nexport type {\n DependencyGraph,\n SerializedGraph,\n TraversalOptions,\n TraversalVisitor,\n} from \"./types/graph\";\n// Core data types\nexport type {\n CallEdge,\n ExportedSymbol,\n FileNode,\n ImportEdge,\n StructuredTag,\n} from \"./types/node\";\nexport type { FileType, ImportType, NodeCategory, TagKind } from \"./types/parse\";\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS, type ScanOptions } from \"./const\";\nimport { DefaultResolver, detectMonorepo, type Graph, GraphBuilder, WorkspaceGraph } from \"./graph\";\n\n/**\n * @description Builds a dependency graph from the given entry points, optionally reusing a\n * previously built graph for incremental updates.\n * @param rootDir - Absolute or relative path to the project root; resolved internally.\n * @param entryPoints - File paths (relative to `rootDir`) that seed the graph walk.\n * @param previousGraph - An earlier graph to diff against for incremental builds; pass `null` for a full build.\n * @param options - `silent` suppresses progress output; `gitStats` attaches git churn data; `coverageMap` maps file paths to line-coverage percentages.\n * @returns The fully-built Graph with all reachable nodes and import edges populated.\n */\nexport async function createImportMap(\n rootDir: string,\n entryPoints: string[],\n previousGraph: Graph | null = null,\n options: { silent?: boolean; gitStats?: boolean; coverageMap?: Map<string, number> } = {},\n): Promise<Graph> {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`Processed ${count} files...\\r`);\n };\n const builder = new GraphBuilder(\n path.resolve(rootDir),\n previousGraph,\n undefined,\n progressCallback,\n options.gitStats ?? false,\n options.coverageMap ?? new Map(),\n );\n return await builder.build(entryPoints);\n}\n\n/**\n * @description Auto-detects the monorepo layout under `rootDir` and builds a per-package\n * dependency graph, stitching them together into a single WorkspaceGraph.\n * @param rootDir - Absolute path to the monorepo root.\n * @param options - `packages` filters to a named subset of packages; `silent` suppresses progress; `gitStats` attaches git churn data per file.\n * @returns A WorkspaceGraph where each package has its own Graph and cross-package edges are resolved.\n */\nexport async function createWorkspaceGraph(\n rootDir: string,\n options: { packages?: string[]; silent?: boolean; gitStats?: boolean } = {},\n): Promise<WorkspaceGraph> {\n const abs = path.resolve(rootDir);\n const layout = detectMonorepo(abs);\n\n const pkgs = options.packages\n ? layout.packages.filter(\n (pkg) =>\n options.packages?.includes(pkg.name) || options.packages?.includes(pkg.relativeRoot),\n )\n : layout.packages;\n\n const workspaceMap = new Map(layout.packages.map((pkg) => [pkg.name, pkg.root]));\n const wg = new WorkspaceGraph(abs, layout.type);\n\n for (const pkg of pkgs) {\n const progressCallback = options.silent\n ? undefined\n : (count: number) => {\n process.stderr.write(`[${pkg.name}] Processed ${count} files...\\r`);\n };\n const builder = new GraphBuilder(\n abs,\n null,\n new DefaultResolver(abs, { workspaceMap, tsconfigSearchPaths: [pkg.root, abs] }),\n progressCallback,\n options.gitStats ?? false,\n );\n const graph = await builder.build(pkg.entryPoints);\n wg.addPackage(pkg, graph);\n }\n\n return wg;\n}\n\n/**\n * @description Recursively walks `rootDir` and returns paths of every file whose extension\n * is in the allowed set, skipping ignored directories. Silently skips unreadable entries.\n * @param rootDir - Root directory to scan; returned paths are relative to this.\n * @param options - Override or extend the default ignore-dir and extension lists via ScanOptions.\n * @returns Relative file paths for all matching source files found under `rootDir`.\n */\nexport function getAllProjectFiles(rootDir: string, options: ScanOptions = {}): string[] {\n const files: string[] = [];\n const ignoreDirs = new Set([\n ...(options.ignoreDirs ?? DEFAULT_IGNORE_DIRS),\n ...(options.additionalIgnoreDirs ?? []),\n ]);\n const extensions = new Set([\n ...(options.extensions ?? DEFAULT_EXTENSIONS),\n ...(options.additionalExtensions ?? []),\n ]);\n\n /**\n * @description Recursively visits `dir`, pushing matching file paths into the outer `files` array.\n * @param dir - Absolute path of the directory to scan in this recursion step.\n */\n function walk(dir: string) {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!ignoreDirs.has(entry.name)) {\n walk(fullPath);\n }\n } else if (entry.isFile()) {\n if (extensions.has(path.extname(entry.name).toLowerCase())) {\n files.push(path.relative(rootDir, fullPath));\n }\n }\n }\n } catch (_e) {\n // Permission issues or broken symlinks\n }\n }\n\n walk(rootDir);\n return files;\n}\n","/** Parses command-line arguments into a structured ParsedArgs object. */\nimport path from \"node:path\";\nimport { parseArgs as nodeParseArgs } from \"node:util\";\nimport { DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE } from \"./const\";\n\nexport interface ParsedArgs {\n rootDir: string;\n cachePath: string;\n configPath: string | undefined;\n mermaid: boolean;\n proposeTags: boolean;\n plain: boolean;\n affectedTests: boolean;\n detectFeatures: boolean;\n featureThreshold: number | undefined;\n findUnused: boolean;\n excludeTests: boolean;\n checkCycles: boolean;\n findUncovered: boolean;\n callers: boolean;\n file: string | undefined;\n silent: boolean;\n query: string | undefined;\n queryHelp: boolean;\n entryPoints: string[];\n help: boolean;\n typeGraph: boolean;\n typeFilter: string | undefined;\n moduleResponsibility: boolean;\n filterPaths: string[] | undefined;\n minOutDegree: number | undefined;\n featureGraph: boolean;\n callGraph: boolean;\n functionName: string | undefined;\n apiSurface: boolean;\n applyTags: boolean;\n dryRun: boolean;\n}\n\n/**\n * @description Extracts and resolves the `--root` argument from raw CLI tokens, falling back to\n * the current working directory when the flag is absent.\n * @param {string[]} cliTokens - Raw process arguments (everything after `node <script>`).\n * @returns {string} Absolute path to use as the project root.\n */\nfunction resolveRootDir(cliTokens: string[]): string {\n for (let i = 0; i < cliTokens.length; i++) {\n if (cliTokens[i] === \"--root\" && cliTokens[i + 1]) {\n return path.resolve(cliTokens[i + 1] as string);\n }\n }\n return process.cwd();\n}\n\n/**\n * @description Parses raw CLI tokens into a structured options object with all paths\n * resolved to absolute values. `--root` is resolved first because the default cache path\n * derives from it; every subsequent path argument is resolved relative to that root.\n * @param {string[]} cliTokens - Raw process arguments (everything after `node <script>`).\n * @returns {ParsedArgs} A fully populated `ParsedArgs` with boolean flags set and path arguments as absolute paths.\n */\nconst OPTIONS = {\n root: { type: \"string\" },\n cache: { type: \"string\" },\n config: { type: \"string\" },\n query: { type: \"string\" },\n file: { type: \"string\" },\n type: { type: \"string\" },\n paths: { type: \"string\" },\n function: { type: \"string\" },\n \"feature-threshold\": { type: \"string\" },\n \"min-out-degree\": { type: \"string\" },\n mermaid: { type: \"boolean\" },\n \"propose-tags\": { type: \"boolean\" },\n plain: { type: \"boolean\" },\n \"affected-tests\": { type: \"boolean\" },\n \"detect-features\": { type: \"boolean\" },\n \"find-unused\": { type: \"boolean\" },\n \"exclude-tests\": { type: \"boolean\" },\n \"check-cycles\": { type: \"boolean\" },\n \"find-uncovered\": { type: \"boolean\" },\n callers: { type: \"boolean\" },\n silent: { type: \"boolean\" },\n \"query-help\": { type: \"boolean\" },\n help: { type: \"boolean\" },\n \"type-graph\": { type: \"boolean\" },\n \"module-responsibility\": { type: \"boolean\" },\n \"feature-graph\": { type: \"boolean\" },\n \"call-graph\": { type: \"boolean\" },\n \"api-surface\": { type: \"boolean\" },\n \"apply-tags\": { type: \"boolean\" },\n \"dry-run\": { type: \"boolean\" },\n} as const;\n\nconst STRING_FLAGS = new Set(\n Object.entries(OPTIONS)\n .filter(([, v]) => v.type === \"string\")\n .map(([k]) => `--${k}`),\n);\n\n/**\n * @description Strips unknown flags and dangling string flags (value-expecting flags\n * with no value following them) so `nodeParseArgs` never sees ambiguous input.\n * Unknown flags are silently dropped; dangling string flags fall back to their defaults.\n * @param {string[]} cliTokens - Raw CLI tokens to sanitize.\n * @returns {string[]} A cleaned token list safe to pass to `nodeParseArgs`.\n */\nfunction sanitizeTokens(cliTokens: string[]): string[] {\n const result: string[] = [];\n for (let i = 0; i < cliTokens.length; i++) {\n const token = cliTokens[i]!;\n if (!token.startsWith(\"--\")) {\n result.push(token);\n } else if (STRING_FLAGS.has(token)) {\n const valueToken = cliTokens[i + 1];\n if (valueToken !== undefined && !valueToken.startsWith(\"--\")) {\n result.push(token, valueToken);\n i++;\n }\n // dangling string flag — drop it, default applies\n } else if (token.slice(2) in OPTIONS) {\n result.push(token); // known boolean flag\n }\n // unknown flag — silently drop\n }\n return result;\n}\n\n/**\n * @description Parses raw CLI tokens into a structured options object with all paths\n * resolved to absolute values. `--root` is resolved first because the default cache path\n * derives from it; every subsequent path argument is resolved relative to that root.\n * @param {string[]} cliTokens - Raw process arguments (everything after `node <script>`).\n * @returns {ParsedArgs} A fully populated `ParsedArgs` with boolean flags set and path arguments as absolute paths.\n */\nexport function parseArgs(cliTokens: string[]): ParsedArgs {\n const rootDir = resolveRootDir(cliTokens);\n const defaultCachePath = path.join(path.resolve(rootDir, DEFAULT_CACHE_DIR), DEFAULT_CACHE_FILE);\n\n const { values, positionals } = nodeParseArgs({\n args: sanitizeTokens(cliTokens),\n allowPositionals: true,\n options: OPTIONS,\n });\n\n const featureThresholdRaw = values[\"feature-threshold\"];\n const minOutDegreeRaw = values[\"min-out-degree\"];\n const filterPathsRaw = values[\"paths\"];\n const cacheValue = values[\"cache\"];\n const configValue = values[\"config\"];\n\n return {\n rootDir,\n cachePath: cacheValue ? path.resolve(rootDir, cacheValue) : defaultCachePath,\n configPath: configValue ? path.resolve(rootDir, configValue) : undefined,\n query: values[\"query\"],\n file: values[\"file\"],\n typeFilter: values[\"type\"],\n functionName: values[\"function\"],\n filterPaths: filterPathsRaw\n ? filterPathsRaw.split(\",\").map((pathStr) => pathStr.trim())\n : undefined,\n featureThreshold: featureThresholdRaw ? parseInt(featureThresholdRaw, 10) : undefined,\n minOutDegree: minOutDegreeRaw ? parseInt(minOutDegreeRaw, 10) : undefined,\n mermaid: values[\"mermaid\"] ?? false,\n proposeTags: values[\"propose-tags\"] ?? false,\n plain: values[\"plain\"] ?? false,\n affectedTests: values[\"affected-tests\"] ?? false,\n detectFeatures: values[\"detect-features\"] ?? false,\n findUnused: values[\"find-unused\"] ?? false,\n excludeTests: values[\"exclude-tests\"] ?? false,\n checkCycles: values[\"check-cycles\"] ?? false,\n findUncovered: values[\"find-uncovered\"] ?? false,\n callers: values[\"callers\"] ?? false,\n silent: values[\"silent\"] ?? false,\n queryHelp: values[\"query-help\"] ?? false,\n help: cliTokens.length === 0 || (values[\"help\"] ?? false),\n typeGraph: values[\"type-graph\"] ?? false,\n moduleResponsibility: values[\"module-responsibility\"] ?? false,\n featureGraph: values[\"feature-graph\"] ?? false,\n callGraph: values[\"call-graph\"] ?? false,\n apiSurface: values[\"api-surface\"] ?? false,\n applyTags: values[\"apply-tags\"] ?? false,\n dryRun: values[\"dry-run\"] ?? false,\n entryPoints: positionals,\n };\n}\n","export const DEFAULT_CACHE_DIR = \"mokosh-cache\";\nexport const DEFAULT_CACHE_FILE = \"graph.json\";\n","/** Shared CLI command utilities: test-file filtering and git-diff resolution. */\nimport path from \"node:path\";\nimport { DefaultGitProvider } from \"../../git\";\n\nconst TEST_PATTERNS = [\".test.\", \".spec.\", \"-test.\", \"-spec.\"];\n\n/**\n * @description Filters a file list to only those whose basename matches a known test-file\n * naming pattern (.test., .spec., -test., -spec.).\n * @param {string[]} allFiles - Full list of project file paths to filter.\n * @returns {string[]} The subset of paths whose basename identifies them as test files.\n */\nexport function getTestFiles(allFiles: string[]): string[] {\n return allFiles.filter((filePath) => {\n const base = path.basename(filePath).toLowerCase();\n return TEST_PATTERNS.some((pattern) => base.includes(pattern));\n });\n}\n\n/**\n * @description Fetches files from the current git diff and returns them as paths relative\n * to rootDir, normalised to the same format used throughout the graph.\n * @param {string} rootDir - Absolute path to the project root, used as the base for relative path computation.\n * @returns {string[]} Paths of git-changed files relative to rootDir.\n */\nexport function resolveChangedFiles(rootDir: string): string[] {\n return new DefaultGitProvider()\n .getChangedFiles()\n .map((filePath) => path.relative(rootDir, path.resolve(rootDir, filePath)));\n}\n","/** CLI command: resolves git-changed files and prints the test files affected by those changes. */\nimport { createImportMap, getAllProjectFiles, proposeAffectedTests } from \"../../index\";\nimport type { CommandContext } from \"./types\";\nimport { getTestFiles, resolveChangedFiles } from \"./utils\";\n\n/**\n * @description Resolves git-changed files, lazily enriches the dependency graph with test\n * nodes if none are present, then prints every test file affected by those changes to stdout.\n * @param {CommandContext} ctx - Shared command context; `ctx.featureThreshold` tunes feature-hub detection.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n let { graph } = ctx;\n const { rootDir, scanOptions, featureThreshold } = ctx;\n\n const changedFiles = resolveChangedFiles(rootDir);\n\n const hasTestNodes = [...graph.nodes.values()].some(\n (node) => getTestFiles([node.path]).length > 0,\n );\n if (!hasTestNodes) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, getTestFiles(allFiles), graph);\n }\n\n const affectedTests = proposeAffectedTests(graph, changedFiles, {\n ...(featureThreshold !== undefined && {\n featureDetection: { minOutDegree: featureThreshold },\n }),\n });\n console.log(affectedTests.join(\"\\n\"));\n}\n","/** CLI command: outputs the public API surface of the project. */\nimport { buildApiSurface, detectAllEntryPoints } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Builds the API surface report: all publicly exported symbols, with their\n * definitions resolved through re-export chains. Entry points are taken from the CLI\n * positional args; when none are given, they are auto-detected from package.json.\n * Also partitions graph nodes into internalFiles, unreachableFromEntry, and testFiles.\n * @param {CommandContext} ctx - Shared command context; positional entry points come from the graph.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, rootDir, entryPoints } = ctx;\n const eps = entryPoints.length ? entryPoints : detectAllEntryPoints(graph, rootDir);\n\n if (eps.length === 0) {\n console.error(\n \"Error: No entry points found. Pass entry points as positional args or ensure package.json has a main/exports field.\",\n );\n process.exit(1);\n }\n\n const surface = buildApiSurface(graph, eps);\n console.log(JSON.stringify(surface, null, 2));\n}\n","/** CLI command: writes @tag annotations into test files based on the dependency graph. */\nimport { applyTags, createImportMap, getAllProjectFiles } from \"../../index\";\nimport type { CommandContext } from \"./types\";\nimport { getTestFiles } from \"./utils\";\n\n/**\n * @description Lazily enriches the graph with test nodes if needed, then writes `@tag`\n * annotations into each test file using only `import` and `comment-marker` kind tags.\n * In dry-run mode prints what would change without writing to disk.\n * @param {CommandContext} ctx - Shared command context; `ctx.dryRun` controls write behaviour.\n * @returns {Promise<void>} Resolves when all files have been processed and results printed.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n let { graph } = ctx;\n const { rootDir, scanOptions, dryRun, plain } = ctx;\n\n if (!plain) {\n console.log(dryRun ? \"Dry run: computing tag changes...\" : \"Applying tags to test files...\");\n }\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, getTestFiles(allFiles), graph);\n }\n\n const result = await applyTags(graph, rootDir, { dryRun });\n console.log(JSON.stringify(result, null, 2));\n}\n","/** CLI command: looks up callers and callees for a named function. */\nimport { queryCallGraph } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Returns the callers and callees of a named function using call edges.\n * Only TypeScript/JavaScript files carry call edges.\n * Requires `--function <name>`.\n * @param {CommandContext} ctx - Shared command context; `ctx.functionName` must be set.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, functionName } = ctx;\n\n if (!functionName) {\n console.error(\"Error: --call-graph requires --function <name>\");\n process.exit(1);\n }\n\n const result = queryCallGraph(graph, functionName);\n console.log(JSON.stringify(result, null, 2));\n}\n","/** CLI command: prints files whose exported functions call into the given file. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Prints files whose exported functions call into the given file.\n * Uses call edges (function-level) rather than import edges, so the result is\n * a subset of — and more precise than — what `get_affected` returns.\n * @param {CommandContext} ctx - Shared command context; `ctx.file` must be set via `--file`.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, file, plain } = ctx;\n\n if (!file) {\n console.error(\"Error: --callers requires --file <path>\");\n process.exit(1);\n }\n\n const callers = graph.getCallers(file);\n\n if (plain) {\n console.log(callers.join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ file, callers, count: callers.length }, null, 2));\n }\n}\n","/** CLI command: detects circular imports and exits with code 1 if any are found. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Scans the dependency graph for circular imports. Prints each cycle to stderr\n * and exits with code 1 if any are found; otherwise confirms a clean graph to stdout.\n * @param {CommandContext} ctx - Shared command context carrying the built graph.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph } = ctx;\n const cycles = graph.findCycles();\n if (cycles.length > 0) {\n process.stderr.write(`Found ${cycles.length} cycle(s):\\n`);\n for (const cycle of cycles) {\n process.stderr.write(` ${cycle.join(\" → \")}\\n`);\n }\n process.exit(1);\n }\n console.log(\"No cycles detected.\");\n}\n","/** CLI command: detects feature-hub files and prints them as JSON. */\nimport { createImportMap, detectFeatures, getAllProjectFiles } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Builds the full dependency graph if it is empty, detects feature-hub nodes\n * (files with high out-degree) sorted by importance, and prints them as JSON.\n * @param {CommandContext} ctx - Shared command context; `ctx.featureThreshold` overrides the default min-out-degree.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n let { graph } = ctx;\n const { rootDir, scanOptions, featureThreshold } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph);\n }\n\n const featureMap = detectFeatures(\n graph.nodes,\n featureThreshold !== undefined ? { minOutDegree: featureThreshold } : undefined,\n );\n const features = Array.from(featureMap.values()).sort(\n (featureA, featureB) => featureB.outDegree - featureA.outDegree,\n );\n console.log(JSON.stringify({ features }, null, 2));\n}\n","/** CLI command: groups files into feature domains under their hub orchestrators. */\nimport { buildFeatureGraph, createImportMap, getAllProjectFiles } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Groups files into feature domains under high-import hub files.\n * Builds the full project graph first if the current graph is empty.\n * Pass `--min-out-degree <N>` to override the default hub threshold.\n * @param {CommandContext} ctx - Shared command context.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n let { graph } = ctx;\n const { rootDir, scanOptions, minOutDegree } = ctx;\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, allFiles, graph);\n }\n\n const featureGraph = buildFeatureGraph(\n graph,\n minOutDegree !== undefined ? { minOutDegree } : undefined,\n );\n const features = Object.fromEntries(featureGraph.features);\n console.log(JSON.stringify({ features, unassigned: featureGraph.unassigned }, null, 2));\n}\n","/** CLI command: lists non-test files below the configured coverage threshold. */\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Lists non-test files whose line coverage is below the configured threshold.\n * The threshold is resolved in priority order: `--feature-threshold` CLI flag →\n * `coverageThreshold` in `mokosh.config.*` → 80.\n * Coverage data must have been loaded during the graph build via `coverageReportPath` in config.\n * @param {CommandContext} ctx - Shared command context; `ctx.rawConfig.coverageThreshold` is the config-level default.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, featureThreshold, rawConfig, plain } = ctx;\n const threshold = featureThreshold ?? rawConfig.coverageThreshold ?? 80;\n\n const uncovered = [...graph.nodes.values()]\n .filter((node) => node.category !== \"test\" && node.category !== \"config\")\n .filter((node) => (node.coveragePct ?? 0) < threshold)\n .map((node) => ({ file: node.path, coveragePct: node.coveragePct ?? null }));\n\n if (plain) {\n console.log(uncovered.map((uncoveredEntry) => uncoveredEntry.file).join(\"\\n\"));\n } else {\n console.log(JSON.stringify({ threshold, uncovered, count: uncovered.length }, null, 2));\n }\n}\n","/** CLI command: lists files unreachable from the entry points (candidates for deletion). */\nimport path from \"node:path\";\nimport { getAllProjectFiles } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\nconst TEST_PATH_PATTERNS = [\".test.\", \".spec.\", \"-test.\", \"-spec.\", \".stories.\"];\n\n/**\n * @description Returns true when the file's basename matches a known test or story filename\n * pattern, used to optionally exclude test files from the unused-files report.\n * @param {string} filePath - File path whose basename is tested against known test/story patterns.\n * @returns {boolean} True if the basename matches a test or story naming convention.\n */\nfunction isTestPath(filePath: string): boolean {\n const base = path.basename(filePath).toLowerCase();\n return TEST_PATH_PATTERNS.some((pattern) => base.includes(pattern));\n}\n\n/**\n * @description Finds all project files that have no incoming imports, optionally excluding\n * test and story files, then prints the list as a JSON object.\n * @param {CommandContext} ctx - Shared command context; `ctx.excludeTests` controls test-file filtering.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, rootDir, scanOptions, excludeTests } = ctx;\n const allProjectFiles = getAllProjectFiles(rootDir, scanOptions);\n let unusedFiles = graph.findUnusedFiles(allProjectFiles);\n\n if (excludeTests) {\n unusedFiles = unusedFiles.filter((filePath) => !isTestPath(filePath));\n }\n\n console.log(JSON.stringify({ unusedFiles }, null, 2));\n}\n","/** CLI command: outputs the dependency graph as Mermaid or JSON, optionally filtered by a query. */\nimport { filterGraph, Graph, MermaidExporter, parseQuery } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Serializes the dependency graph, optionally narrowing it with a query filter,\n * then prints it as a Mermaid diagram or a JSON object that includes detected cycles.\n * @param {CommandContext} ctx - Command context carrying the built graph, an optional query string, and the mermaid output flag.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, queryStr, mermaidOutput } = ctx;\n let serialized = graph.serialize();\n\n if (queryStr) {\n const query = parseQuery(queryStr);\n serialized = filterGraph(serialized, query);\n }\n\n if (mermaidOutput) {\n const filteredGraph = Graph.deserialize(serialized);\n console.log(MermaidExporter.serialize(filteredGraph));\n } else {\n const cycles = graph.findCycles();\n if (cycles.length > 0) {\n serialized.cycles = cycles;\n }\n console.log(JSON.stringify(serialized, null, 2));\n }\n}\n","/** CLI command: outputs each file's semantic role, description, and exported symbols. */\nimport { buildResponsibilityGraph } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Builds a responsibility graph and outputs what each module is responsible for.\n * Pass `--paths a,b` to filter to specific files; omit to get all modules.\n * Pass `--min-out-degree <N>` to tune hub detection threshold.\n * @param {CommandContext} ctx - Shared command context.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, filterPaths, minOutDegree } = ctx;\n const respGraph = buildResponsibilityGraph(\n graph,\n minOutDegree !== undefined ? { minOutDegree } : undefined,\n );\n\n if (filterPaths?.length) {\n const modules = filterPaths.map((modulePath) => respGraph.get(modulePath)).filter(Boolean);\n console.log(JSON.stringify({ count: modules.length, modules }, null, 2));\n } else {\n const modules = Array.from(respGraph.values());\n console.log(JSON.stringify({ count: modules.length, modules }, null, 2));\n }\n}\n","/** CLI command: infers test tags from git-changed files and prints them as JSON or plain text. */\nimport { createImportMap, getAllProjectFiles, proposeTags } from \"../../index\";\nimport type { CommandContext } from \"./types\";\nimport { getTestFiles, resolveChangedFiles } from \"./utils\";\n\n/**\n * @description Resolves git-changed files, builds a minimal test-file graph if the current\n * graph is empty, then prints inferred test tags as JSON or space-separated plain text.\n * @param {CommandContext} ctx - Shared command context; `ctx.plain` switches output format to space-separated text.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n let { graph } = ctx;\n const { rootDir, scanOptions, featureThreshold, plain } = ctx;\n\n if (!plain) console.log(\"Proposing test tags based on git diff...\");\n const changedFiles = resolveChangedFiles(rootDir);\n\n if (graph.nodes.size === 0) {\n const allFiles = getAllProjectFiles(rootDir, scanOptions);\n graph = await createImportMap(rootDir, getTestFiles(allFiles), graph);\n }\n\n const tags = proposeTags(graph, changedFiles, {\n ...(featureThreshold !== undefined && {\n featureDetection: { minOutDegree: featureThreshold },\n }),\n });\n\n if (plain) {\n console.log(tags.join(\" \"));\n } else {\n console.log(JSON.stringify({ proposedTags: tags }, null, 2));\n }\n}\n","/** CLI command: outputs the type-level import graph (interfaces, classes, enums, type aliases). */\nimport { buildTypeGraph, queryTypeGraph } from \"../../index\";\nimport type { CommandContext } from \"./types\";\n\n/**\n * @description Builds and outputs the type graph derived from the import graph.\n * When `--type <name>` is given, returns a focused view for that type (its usedByFiles and uses).\n * Otherwise returns all type nodes and their count.\n * @param {CommandContext} ctx - Shared command context; `ctx.typeFilter` is the optional type name.\n */\nexport async function run(ctx: CommandContext): Promise<void> {\n const { graph, typeFilter } = ctx;\n const typeGraph = buildTypeGraph(graph);\n\n if (typeFilter) {\n const result = queryTypeGraph(typeGraph, typeFilter);\n console.log(JSON.stringify(result, null, 2));\n } else {\n const types = Array.from(typeGraph.types.values());\n console.log(JSON.stringify({ count: types.length, types }, null, 2));\n }\n}\n","/** Resolves the final CLI configuration by merging parsed args, mokosh.config.*, and built-in defaults. */\nimport path from \"node:path\";\nimport { loadMokoshConfig, type MokoshConfig, type ScanOptions } from \"../index\";\nimport type { ParsedArgs } from \"./args\";\n\nexport interface ResolvedConfig {\n rootDir: string;\n resolvedEntryPoints: string[];\n resolvedCachePath: string;\n scanOptions: ScanOptions;\n /** Raw config returned so the caller can call applyConfig() at the right moment. */\n rawConfig: MokoshConfig;\n}\n\ntype ConfigInput = Pick<ParsedArgs, \"rootDir\" | \"entryPoints\" | \"cachePath\" | \"configPath\">;\n\n/**\n * @description Merges parsed CLI arguments with the mokosh config file into a single resolved\n * configuration ready for graph building. CLI arguments take precedence over config-file values.\n * Call `applyConfig(result.rawConfig)` after this and before starting the build.\n * @param {ConfigInput} parsed - The CLI arguments needed for config resolution.\n * @returns {ResolvedConfig} Fully resolved paths, entry points, and scan options.\n */\nexport function resolveConfig(parsed: ConfigInput): ResolvedConfig {\n const { rootDir, entryPoints, cachePath, configPath } = parsed;\n const config = configPath\n ? loadMokoshConfig(configPath, { isExplicitPath: true })\n : loadMokoshConfig(rootDir);\n\n const defaultCachePath = path.join(path.resolve(rootDir, \"mokosh-cache\"), \"graph.json\");\n\n const resolvedEntryPoints = entryPoints.length > 0 ? entryPoints : (config.entryPoints ?? []);\n\n const resolvedCachePath =\n cachePath !== defaultCachePath\n ? (cachePath ?? defaultCachePath)\n : config.cachePath\n ? path.resolve(rootDir, config.cachePath)\n : (cachePath ?? defaultCachePath);\n\n const scanOptions: ScanOptions = {\n ...(config.ignoreDirs !== undefined && { additionalIgnoreDirs: config.ignoreDirs }),\n ...(config.extensions !== undefined && { additionalExtensions: config.extensions }),\n };\n\n return { rootDir, resolvedEntryPoints, resolvedCachePath, scanOptions, rawConfig: config };\n}\n","/** Loads the dependency graph from a JSON disk cache or builds it fresh if the cache is missing. */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createImportMap, Graph } from \"../index\";\n\n/**\n * @description Reads a serialized graph from a JSON cache file and deserializes it.\n * @param {string} cachePath - Path to the JSON cache file written by `saveGraphToCache`.\n * @returns {Graph | null} The deserialized `Graph`, or `null` when the file does not exist yet.\n */\nexport function loadGraphFromCache(cachePath: string): Graph | null {\n if (!fs.existsSync(cachePath)) return null;\n const raw = fs.readFileSync(cachePath, \"utf-8\");\n return Graph.deserialize(JSON.parse(raw));\n}\n\n/**\n * @description Serializes a `Graph` to JSON and writes it to the given cache file,\n * creating any missing parent directories along the way.\n * @param {Graph} graph - The `Graph` instance to persist.\n * @param {string} cachePath - Destination path for the JSON cache file; parent directories are created automatically.\n */\nexport function saveGraphToCache(graph: Graph, cachePath: string): void {\n const cacheDir = path.dirname(cachePath);\n if (!fs.existsSync(cacheDir)) {\n fs.mkdirSync(cacheDir, { recursive: true });\n }\n fs.writeFileSync(cachePath, JSON.stringify(graph.serialize(), null, 2));\n}\n\n/**\n * @description Builds (or incrementally updates) the import graph for the given entry points.\n * @param {string} rootDir - Absolute path to the project root; entry points are resolved relative to this.\n * @param {string[]} entryPoints - File paths that seed the graph traversal.\n * @param {Graph | null} cachedGraph - A previously built `Graph` to reuse as an incremental base, or `null` for a full build.\n * @param {boolean} [silent=false] - When `true`, suppresses progress output during the build.\n * @param {boolean} [gitStats=false] - When `true`, attaches git churn data to each node.\n * @returns {Promise<Graph>} The fully-built `Graph` covering all reachable imports.\n */\nexport async function buildGraph(\n rootDir: string,\n entryPoints: string[],\n cachedGraph: Graph | null,\n silent = false,\n gitStats = false,\n): Promise<Graph> {\n return createImportMap(rootDir, entryPoints, cachedGraph, { silent, gitStats });\n}\n","/** CLI usage and options text, displayed with --help or when no arguments are given. */\nexport const HELP_TEXT = `\nUsage: mokosh [options] <entry-point1> <entry-point2> ...\n\nOptions:\n --cache [file] Path to cache file (default: mokosh-cache/graph.json)\n --config <file> Path to mokosh config file (overrides auto-discovery)\n --mermaid Output Mermaid chart instead of JSON\n --propose-tags Propose test tags based on git diff\n --plain Output tags as plain text instead of JSON (use with --propose-tags)\n --affected-tests List test files affected by git diff\n --apply-tags Write @tag annotations into test files from graph tags\n --dry-run Preview tag changes without writing to disk (use with --apply-tags)\n --detect-features Output files with high out-degree (orchestrators/aggregators)\n --feature-threshold <N> Min internal imports to be a feature hub (default: 5)\n --find-unused Find files that are not reachable from entry points\n --exclude-tests Exclude test files from --find-unused output\n --check-cycles Check for circular dependencies; exits non-zero if found (CI gate)\n --type-graph Output type-level graph (interfaces, classes, enums, type aliases)\n --type <name> Filter --type-graph to a single type name\n --module-responsibility Output each file's semantic role, description, and exports\n --paths <a,b,...> Comma-separated file paths to filter --module-responsibility output\n --min-out-degree <N> Min internal imports for hub detection (--module-responsibility, --feature-graph)\n --feature-graph Group files into feature domains under their hub orchestrators\n --call-graph Look up callers and callees for a named function\n --function <name> Function name to look up with --call-graph\n --api-surface Output the public API surface (expands export * chains)\n --silent Suppress progress output on stderr\n --query <query> Filter output using a query (e.g., category:logic,tag:auth)\n --query-help Show all supported query filter keys and examples\n --root <dir> Project root directory (default: current directory)\n --help Show help\n\nNotes:\n Add mokosh-cache/ to your .gitignore to avoid committing the cache directory.\n`;\n\n/** Reference for all supported --query filter keys, shown with --query-help. */\nexport const QUERY_HELP_TEXT = `\nQuery filter reference (--query \"key:value,key:value,...\")\nAll keys are case-insensitive. Multiple keys are AND'd together.\n\nFILTERING\n category:<value> Exact match on file category. Negate with !.\n Values: logic | ui | test | config | barrel | type-only | other\n Examples: category:logic category:!test\n\n type:<value> Exact match on language. Negate with !.\n Values: typescript | javascript | css | scss | less | stylus |\n coffeescript | livescript | lua | gherkin\n Example: type:typescript\n\n tag:<value> File has this tag (OR across multiple tag: entries).\n Negate with ! to exclude. Use + to require all (AND).\n Examples: tag:auth (has \"auth\")\n tag:!generated (does not have \"generated\")\n tag:auth+core (has both \"auth\" AND \"core\")\n\n path:<substr> File path contains substring. Negate with !.\n Examples: path:src/api path:!__tests__\n\n external:<bool> true = node has at least one external (node_modules) import.\n Example: external:true\n\n importsFile:<substr> Node directly imports a file whose path contains the substring.\n Example: importsFile:src/utils/logger\n\n importedBy:<substr> Node is directly imported by a file whose path contains the substring.\n Example: importedBy:src/index\n\n minImports:<N> Out-degree (direct import count) >= N.\n maxImports:<N> Out-degree <= N.\n Examples: minImports:5 maxImports:2\n\n minSize:<bytes> File size >= N bytes.\n maxSize:<bytes> File size <= N bytes.\n Examples: minSize:1024 maxSize:4096\n\n hasDocstring:<bool> true = node has a JSDoc description on its first statement.\n false = undocumented files only.\n Example: hasDocstring:false\n\nSORTING & LIMITING (applied after all filters)\n sort:<field> Sort results descending by one of:\n size — file size in bytes\n imports — number of direct imports\n commitCount90d — commits in the last 90 days (requires gitStats: true)\n Example: sort:imports\n\n limit:<N> Return at most N results.\n Example: limit:20\n\nCOMMON PATTERNS\n Token-efficient context (logic only):\n --query \"category:logic\"\n\n Undocumented logic files:\n --query \"category:logic,hasDocstring:false\"\n\n 10 most-imported files in a subsystem:\n --query \"path:src/api,sort:imports,limit:10\"\n\n Files using a specific library:\n --query \"tag:react,category:logic\"\n\n Files that import a specific module:\n --query \"importsFile:src/auth/session\"\n\n Large TypeScript logic files:\n --query \"type:typescript,category:logic,sort:size,limit:5\"\n`;\n","/** CLI entry point: parses arguments, loads configuration and the graph, then dispatches to the appropriate command. */\nimport { applyConfig, Graph } from \"../index\";\nimport { parseArgs } from \"./args\";\nimport { run as runAffectedTests } from \"./commands/affected-tests\";\nimport { run as runApiSurface } from \"./commands/api-surface\";\nimport { run as runApplyTags } from \"./commands/apply-tags\";\nimport { run as runCallGraph } from \"./commands/call-graph\";\nimport { run as runCallers } from \"./commands/callers\";\nimport { run as runCheckCycles } from \"./commands/check-cycles\";\nimport { run as runDetectFeatures } from \"./commands/detect-features\";\nimport { run as runFeatureGraph } from \"./commands/feature-graph\";\nimport { run as runFindUncovered } from \"./commands/find-uncovered\";\nimport { run as runFindUnused } from \"./commands/find-unused\";\nimport { run as runGraphOutput } from \"./commands/graph-output\";\nimport { run as runModuleResponsibility } from \"./commands/module-responsibility\";\nimport { run as runProposeTags } from \"./commands/propose-tags\";\nimport { run as runTypeGraph } from \"./commands/type-graph\";\nimport type { CommandHandler } from \"./commands/types\";\nimport { resolveConfig } from \"./config\";\nimport { buildGraph, loadGraphFromCache, saveGraphToCache } from \"./graph-loader\";\nimport { HELP_TEXT, QUERY_HELP_TEXT } from \"./help\";\n\n/**\n * @description Parses CLI arguments, loads configuration, builds or restores the\n * dependency graph, then dispatches to the appropriate sub-command handler.\n */\nexport async function run(): Promise<void> {\n const argv = process.argv.slice(2);\n const parsed = parseArgs(argv);\n\n if (parsed.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n }\n\n if (parsed.queryHelp) {\n console.log(QUERY_HELP_TEXT);\n process.exit(0);\n }\n\n const config = resolveConfig(parsed);\n applyConfig(config.rawConfig);\n const { rootDir, resolvedEntryPoints, resolvedCachePath, scanOptions } = config;\n const {\n proposeTags,\n plain,\n affectedTests,\n detectFeatures,\n findUnused,\n findUncovered,\n excludeTests,\n checkCycles,\n callers,\n file,\n silent,\n featureThreshold,\n query: queryStr,\n mermaid: mermaidOutput,\n typeGraph,\n typeFilter,\n moduleResponsibility,\n filterPaths,\n minOutDegree,\n featureGraph,\n callGraph,\n functionName,\n apiSurface,\n applyTags,\n dryRun,\n } = parsed;\n\n const autoScan =\n proposeTags ||\n affectedTests ||\n applyTags ||\n callers ||\n findUncovered ||\n typeGraph ||\n moduleResponsibility ||\n callGraph ||\n apiSurface;\n\n let graph: Graph = loadGraphFromCache(resolvedCachePath) ?? new Graph(new Map());\n\n if (resolvedEntryPoints.length > 0 || !autoScan) {\n if (\n resolvedEntryPoints.length === 0 &&\n !autoScan &&\n !findUnused &&\n !detectFeatures &&\n !checkCycles\n ) {\n console.error(\"Error: No entry points provided\");\n process.exit(1);\n }\n graph = await buildGraph(\n rootDir,\n resolvedEntryPoints,\n graph,\n silent,\n config.rawConfig.gitStats ?? false,\n );\n\n saveGraphToCache(graph, resolvedCachePath);\n }\n\n const ctx = {\n graph,\n rootDir,\n entryPoints: resolvedEntryPoints.map((entryPath) => entryPath.replace(rootDir + \"/\", \"\")),\n scanOptions,\n rawConfig: config.rawConfig,\n featureThreshold,\n queryStr,\n mermaidOutput,\n plain,\n excludeTests,\n file,\n typeFilter,\n filterPaths,\n minOutDegree,\n functionName,\n dryRun,\n };\n\n const commands: Array<[boolean, CommandHandler]> = [\n [proposeTags, runProposeTags],\n [applyTags, runApplyTags],\n [affectedTests, runAffectedTests],\n [detectFeatures, runDetectFeatures],\n [findUnused, runFindUnused],\n [checkCycles, runCheckCycles],\n [findUncovered, runFindUncovered],\n [callers, runCallers],\n [typeGraph, runTypeGraph],\n [moduleResponsibility, runModuleResponsibility],\n [featureGraph, runFeatureGraph],\n [callGraph, runCallGraph],\n [apiSurface, runApiSurface],\n ];\n\n const handler = commands.find(([flag]) => flag)?.[1] ?? runGraphOutput;\n await handler(ctx);\n}\n","#!/usr/bin/env node\n/** CLI binary entry point: invokes the runner and exits with code 1 on unhandled errors. */\nimport { run } from \"./cli/runner\";\n\nrun().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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","names":["import_node_fs","import_node_path","builtinConfigMatchers","userConfigMatchers","registerConfigMatcher","matcher","isConfigFile","baseName","builtinTestPatterns","userTestPatterns","registerTestPattern","pattern","getTestPatterns","builtinTestLibraries","userTestLibraries","registerTestLibrary","lib","getTestLibraries","currentBarrelThreshold","setBarrelThreshold","threshold","getBarrelThreshold","CONFIG_FILENAMES","loadMokoshConfig","rootDirOrPath","allowJs","isExplicitPath","filePath","path","fs","readJsonConfig","readJsConfig","filename","exported","applyConfig","config","pattern","registerConfigMatcher","registerTestPattern","lib","registerTestLibrary","setBarrelThreshold","DEFAULT_IGNORE_DIRS","DEFAULT_EXTENSIONS","import_node_fs","import_node_path","MermaidExporter","graph","lines","visitedEdges","node","nodeLabel","imp","targetLabel","edgeKey","edgeStyle","import_node_fs","import_node_path","tryResolveSrcEquiv","field","graph","rel","srcEquiv","resolveExportsValue","value","cond","key","resolved","inferExportKind","signature","trimmed","collectAccessibleSymbolNames","entryPoints","accessible","wildcardVisited","queue","current","node","sym","imp","target","name","detectAllEntryPoints","graph","root","found","pkgPath","path","fs","pkg","value","resolved","resolveExportsValue","tryResolveSrcEquiv","field","candidate","collectReachableFiles","entryPoints","reachableFiles","entryPoint","node","buildDefinitionsMap","definitions","filePath","isBarrel","exportedSymbol","existingDefinition","hasConcreteSignature","buildPublicExports","accessibleNames","publicExports","name","definition","entrySymbol","symbol","definedIn","publicExport","inferExportKind","exportA","exportB","partitionNodes","isTestNode","internalFiles","unreachableFiles","unreachableFromEntry","testFiles","buildApiSurface","collectAccessibleSymbolNames","queryCallGraph","graph","functionName","definedIn","callers","node","exportedSym","edge","callees","defNode","import_node_fs","import_node_path","import_node_path","buildOutDegreeMap","nodes","outDegreeMap","filePath","node","count","imp","buildFeatureMap","minOutDegree","result","outDegree","ext","path","basename","label","detectFeatures","options","DEFAULT_HUB_COMPARATOR","left","right","collectReachable","graph","hubs","reachable","hub","files","node","assignFilesToHubs","nodes","comparator","fileToHub","filePath","bestHub","buildDomains","features","featureName","ownerHub","collectUnassigned","unassigned","buildFeatureGraph","options","detectFn","detectFeatures","GraphAnalyzer","nodes","allFiles","usedFiles","file","threshold","results","node","tightest","best","imp","left","right","cycles","visited","recStack","currentPath","find","current","cycleIndex","nodePath","Graph","_Graph","nodes","serialized","node","incoming","imp","list","startPath","visitor","options","getNeighbors","visited","maxDepth","walk","currentPath","depth","parentPath","neighbor","direction","path","importEdge","cache","edge","callIncoming","callEdge","filePath","callers","allFiles","GraphAnalyzer","inferRole","node","filePath","seg","fileBasename","segment","name","buildResponsibilityGraph","graph","featureOptions","featureGraph","buildFeatureGraph","fileToHub","featureName","domain","filePath","result","node","hub","inferRole","exportedSym","SymbolTraversalContext","startPath","affectedSymbols","visitedNode","childPath","currentSymbols","importEdge","imp","importedSymbols","relevantSymbols","sym","existing","symbol","inferKind","signature","isTypeExport","sym","category","sig","buildTypeGraph","graph","types","node","exp","key","edges","imp","queryTypeGraph","typeGraph","typeName","target","typeNode","usedByFiles","usesMap","edge","dep","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","isDirectory","filePath","fs","isFile","buildPackage","monorepoRoot","pkgRoot","pkgJsonPath","path","fs","pkgJson","name","resolveEntryPoints","candidates","exp","dot","src","c","existing","isFile","resolveGlobPatterns","root","patterns","packages","seen","pattern","normalised","resolvePattern","resolveLiteralPattern","segments","resolveRecursivePattern","resolveShallowPattern","abs","isDirectory","pkg","base","walkRecursive","starIdx","segment","entries","entry","dir","npmDetector","rootDir","pkgPath","path","fs","workspaces","patterns","resolveGlobPatterns","import_node_fs","import_node_path","nxDetector","rootDir","fs","path","walkForProjectJsonDirs","pkgRoot","buildNxPackage","pkg","dir","seen","depth","entries","found","entry","name","fullPath","monorepoRoot","projJsonPath","projJson","pkgMain","pkgExports","pkgJsonPath","pkgJson","resolveNxEntryPoints","candidates","buildMain","repoRootGuess","exp","dot","srcRoot","c","existing","isFile","import_node_fs","import_node_path","import_js_yaml","pnpmDetector","rootDir","yamlPath","path","fs","patterns","yaml","resolveGlobPatterns","import_node_fs","import_node_path","turborepoDetector","rootDir","fs","path","import_node_fs","import_node_path","yarnDetector","rootDir","fs","path","pkgPath","workspaces","patterns","resolveGlobPatterns","registry","registerMonorepoDetector","detector","registerMonorepoDetector","turborepoDetector","nxDetector","pnpmDetector","yarnDetector","npmDetector","parserRegistry","registerParser","type","parser","getParserForType","matchesStr","nodeValue","queryValue","matchesPath","nodePath","queryPath","matchCategory","node","query","matchType","matchPath","matchIsExternal","importEdge","matchTags","positiveTags","tag","negativeTags","structuredTag","matchAllTags","matchImportsFile","matchImportedBy","reverseIndex","importerPath","matchMinImports","matchMaxImports","matchMinSize","matchMaxSize","matchHasDocstring","matchMinCoverage","matchMaxCoverage","matchMinExportUsage","matchMaxExportUsage","NODE_MATCHERS","matchNode","node","query","reverseIndex","NODE_MATCHERS","matcher","filterGraph","graph","imp","arr","filteredNodes","nodePaths","resultNodes","nodeA","nodeB","cycle","path","parseQuery","queryString","query","parts","part","colonIdx","key","value","import_promises","import_node_path","import_node_path","import_typescript","import_node_path","import_typescript","import_typescript","ANNOTATABLE_NAMES","findTopLevelCalls","sourceFile","calls","stmt","ts","expr","callee","readArrayProp","call","propName","sf","arg","prop","candidate","element","buildInjectReplacement","tagsLiteral","i","existingProp","closeBrace","callback","buildRemoveReplacement","args","idx","applyReplacements","source","replacements","sorted","left","right","result","replacement","toArrayLiteral","tags","tag","TS_EXTENSIONS","toCypressLiteral","tags","toArrayLiteral","tag","normaliseExisting","raw","CypressStrategy","absPath","TS_EXTENSIONS","path","source","sf","ts","calls","findTopLevelCalls","rawExisting","readArrayProp","sortedTags","replacements","call","replacement","buildRemoveReplacement","buildInjectReplacement","applyReplacements","import_node_path","BLOCK_REGEX","EXISTING_TAG_REGEX","buildBlock","tags","tag","readManualTags","content","found","match","GherkinStrategy","absPath","path","_absPath","source","manualContent","manualTags","netNewTags","newBlock","matchesGlob","pattern","relPath","normalizedPattern","normalizedPath","regexSource","i","char","import_node_path","MOKOSH_BUILD_TAG_RE","PACKAGE_LINE_RE","buildBuildTag","tags","tag","readExistingTags","source","match","line","re","tagMatch","GoStrategy","absPath","path","_absPath","existing","sortedTags","buildTag","packageMatch","insertAt","import_node_path","GROUP_BLOCK_RE","GROUP_LINE_RE","buildBlock","tags","tag","readExistingGroups","block","found","match","JestStrategy","absPath","TS_EXTENSIONS","path","_absPath","source","existing","sortedTags","stripped","import_node_path","import_typescript","toPlaywrightLiteral","tags","toArrayLiteral","tag","normaliseExisting","raw","PlaywrightStrategy","absPath","TS_EXTENSIONS","path","source","sf","ts","calls","findTopLevelCalls","rawExisting","readArrayProp","sortedTags","replacements","call","replacement","buildRemoveReplacement","buildInjectReplacement","applyReplacements","import_node_path","PYTESTMARK_RE","PYTEST_IMPORT_RE","buildPytestmark","tags","marks","tag","readExistingMarks","source","match","line","re","markMatch","PytestStrategy","absPath","path","_absPath","existing","sortedTags","pytestmarkLine","hasImport","importBlockEnd","findImportBlockEnd","before","after","importLine","separator","lines","lastImportLine","i","offset","import_node_path","import_typescript","LEGACY_BLOCK_REGEX","VitestStrategy","absPath","TS_EXTENSIONS","path","source","tags","stripped","sf","ts","calls","findTopLevelCalls","existing","readArrayProp","sortedTags","replacements","call","r","buildRemoveReplacement","buildInjectReplacement","toArrayLiteral","applyReplacements","FRAMEWORK_STRATEGIES","VitestStrategy","PlaywrightStrategy","CypressStrategy","JestStrategy","FRAMEWORK_IMPORT_MARKERS","detectFrameworkFromImports","source","sf","ts","stmt","framework","AutoFrameworkStrategy","rootDir","defaultFramework","frameworkOverrides","absPath","TS_EXTENSIONS","path","tags","relPath","pattern","matchesGlob","createStrategies","GherkinStrategy","PytestStrategy","GoStrategy","getStrategyForFile","strategies","strategy","VALID_TAG_NAME_RE","ALLOWED_TAG_KINDS","GENERIC_TAG_BLOCKLIST","applyTagsToFile","absPath","tags","dryRun","strategies","original","fs","err","strategy","getStrategyForFile","newContent","applyTags","graph","rootDir","options","config","loadMokoshConfig","framework","frameworkOverrides","createStrategies","result","node","seen","tagNames","tag","path","fileResult","DefaultTestNodeIdentifier","node","tag","import_node_fs","import_node_path","import_node_child_process","DefaultGitProvider","allFiles","cmd","filePath","error","getGitFileStats","rootDir","relativePath","lines","import_node_fs","import_node_path","import_js_yaml","stripVersionSuffix","descriptor","lastAt","parseYarnDescriptors","line","part","trimmed","parsePnpmId","id","pkgVersion","raw","tryParseYarnBerry","content","lock","yaml","result","key","value","name","parseYarnClassic","block","lines","header","names","version","parsePackageLock","filePath","fs","pkgPath","pkgData","parseYarnLock","berryResult","parsePnpmLock","versionData","loadLockFile","rootDir","candidates","filename","parser","path","import_node_path","getFileType","filePath","path","isStyleFile","specifier","ext","import_coffeescript","extractTags","content","tags","tagRegex","match","resolveCategory","filePath","lower","edgeFromImportDeclaration","node","specifier","isStyleFile","edgeFromRequireCall","isRequire","visitNode","out","className","edge","traverse","key","child","c","parseCoffeeScript","category","imports","coffee","name","import_gherkin","import_messages","uuidFn","parseGherkin","_filePath","content","rawTags","builder","matcher","gherkinDocument","tag","child","example","ruleChild","error","name","registerParser","import_node_path","import_go","TAG_RE","BUILD_NEW_RE","BUILD_OLD_RE","parseGo","filePath","content","imports","exportMap","tags","buildTags","cursor","text","tagM","newBuild","extractBuildTokens","oldBuild","stringNode","specifier","nameNode","name","importsTestingPkg","importEdge","category","path","allTagNames","expr","out","tok","import_livescript","stripQuotes","value","extractTags","content","tags","tagRegex","match","classifyFile","filePath","lower","POSITIONAL_KEYS","extractEdge","node","type","raw","specifier","stripQuotes","isStyleFile","call","collectEdges","edges","edge","key","child","c","parseLiveScript","category","imports","ls","name","import_luaparse","extractTagAnnotations","content","tagNames","tagAnnotationRegex","annotationMatch","classifyCategory","filePath","lowerCasePath","collectRequireEdges","ast","importEdges","visitNode","node","specifier","requireArgument","stripQuotes","isStyleFile","key","childValue","childNode","parseLua","category","imports","luaparse","name","import_node_path","import_python","TEST_LIBS","parsePython","filePath","content","imports","exports","tags","baseName","path","cursor","tagMatch","edge","extractImportEdges","parentNode","nameNode","target","category","resolveCategory","name","node","src","first","extractFromImport","extractBareImport","fromKw","importKw","rawModule","importedNames","collectImportedNames","dotCount","modulePart","makeEdge","prefix","edges","childNode","modName","start","names","rawSpecifier","symbols","isExternal","imp","import_node_path","import_typescript","import_typescript","computeCyclomaticComplexity","rootNode","complexity","walkCyclomatic","node","ts","operatorKind","computeCognitiveComplexity","cognitiveComplexity","walkCognitive","depth","isElseIf","bodyDepth","child","computeComplexity","import_typescript","TEST_CALL_NAMES","handleTagging","node","ctx","collectDeclarationNameTags","collectStringLiteralAtTags","collectCommentAnnotationTags","collectVitestOptionBagTags","ts","isTopLevel","kind","init","stmt","matches","tag","tagRegex","fullText","match","isTestCallExpression","arg","collectTagsFromObjectLiteral","callee","obj","prop","initializer","values","el","parseCodeFile","filePath","content","fileType","imports","exports","tags","sourceFile","ts","context","visit","node","analyzeNode","category","determineCategory","collectRawCallEdges","firstStatement","description","extractJsDoc","complexity","cognitiveComplexity","computeComplexity","functions","collectFunctionComplexity","results","record","name","line","className","nextClassName","child","makeExportedSymbol","declNode","stmtNode","sym","doc","flags","extractJsDocFlags","sig","extractSignature","cmts","cmtNode","KNOWN","jsDocTag","printer","print","tsNode","params","ret","tp","fn","ctx","updateStatementCounts","updateCategoryHints","handleImports","handleExports","handleCalls","handleTagging","statements","statement","hasExportModifier","isTypeOnlyExportDecl","el","symbols","element","type","isStyleFile","handleExportDeclaration","handleInlineExport","handleReExport","specifier","extractReExportSymbols","edge","decl","arg","baseName","path","ext","getTestPatterns","pattern","isConfigFile","imp","getTestLibraries","lib","getBarrelThreshold","modifier","edges","importSymbolMap","stmt","clause","fnName","getTopLevelExportedFunctionName","body","getFunctionBody","walkCallExpressions","collectClassMethodCallEdges","classDecl","member","result","callee","callEdgeEntry","detectCssBarrel","root","imports","hasRule","node","import_postcss","lessParser","SIDE_EFFECT_KEYWORDS","isExternalCss","specifier","isLocalUrl","trimmed","extractAtImportEdge","params","filePath","lessMatch","keyword","type","urlMatch","extractUrlDeclarationEdges","value","edges","urlPattern","match","collectEdgesFromRoot","root","imports","node","edge","stripLineComments","content","regexFallbackImports","atImportPattern","parseCssContent","postcss","parseLessContent","import_postcss_scss","isScssExternal","specifier","parseScssParams","params","specMatch","alias","parseScssContent","content","filePath","root","scssParse","imports","node","name","edge","parseStylusImports","content","filePath","imports","atRequirePattern","match","specifier","bareImportPattern","detectStylusCategory","stylusLib","astNode","parseStyleFile","filePath","content","fileType","getFileType","imports","parseStylusImports","detectStylusCategory","root","parseScssContent","detectCssBarrel","parseLessContent","parseCssContent","type","parser","path","content","parseCodeFile","parseStyleFile","parseCoffeeScript","parseLiveScript","parseLua","parsePython","parseGo","parseGherkin","registerParser","parseFile","filePath","fileType","getFileType","getParserForType","import_node_path","enrichCoverage","nodes","coverageMap","node","pct","enrichLibraryTags","imports","tags","imp","path","libName","existingTag","enrichTestedBy","target","round4","value","enrichExportUsage","ratios","ratio","sum","addUniqueTag","name","kind","addFilenameTag","testNode","importEdge","toPath","filenameTag","addSymbolTags","symbolName","propagateCommentMarkers","sourceNode","sourceTag","enrichTestNodeTags","import_node_fs","import_node_path","import_node_fs","import_node_path","GoLangResolver","_currentFile","specifier","rootDir","_resolveLocal","mod","replaces","redirected","goFilesInDir","rel","path","cached","empty","content","fs","data","parseGoMod","from","toDir","sub","lines","inReplaceBlock","raw","line","parseReplaceLine","out","lhs","rhs","side","fromModule","absTarget","absDir","entries","files","dirent","resolvedA","resolvedB","import_node_fs","import_node_path","LuaLangResolver","_currentFile","specifier","rootDir","resolveLocal","luaSpecifier","path","searchBases","base","fs","resolved","import_node_fs","import_node_path","PythonLangResolver","_currentFile","specifier","rootDir","_resolveLocal","pyPath","path","pyFile","isFile","initFile","filePath","fs","DefaultResolver","rootDir","options","PythonLangResolver","LuaLangResolver","GoLangResolver","currentFile","specifier","aliased","resolved","resolveLocal","cf","spec","lr","ext","locals","workspace","dir","path","fullPath","isExternal","extensions","esmMatch","strippedPath","candidatePath","indexP","initP","filePath","fs","searchDir","tsconfigPath","paths","alias","match","regex","pattern","substitutions","wildcardMatch","baseDir","sub","resolvedSub","pkgName","pkgRoot","subPath","base","candidate","abs","deepResolved","CONVENTIONAL_TEST_DIR_NAMES","commonAncestorDir","absPaths","rootDir","segmentLists","p","path","common","segments","i","candidate","rel","GraphBuilder","previousGraph","resolver","progressCallback","enableGitStats","coverageMap","DefaultResolver","loadLockFile","entryPoints","entryPaths","entry","entryPath","enrichTestNodeTags","enrichTestedBy","enrichExportUsage","enrichCoverage","Graph","scanRoot","patterns","getTestPatterns","ignoreDirs","walk","dir","entries","fs","fullPath","pattern","parent","name","filePath","stats","relativePath","node","cachedNode","parsed","enrichLibraryTags","callEdges","content","parseFile","err","getFileType","rawCallEdges","rce","resolved","imports","exports","tags","category","description","complexity","cognitiveComplexity","functions","git","getGitFileStats","resolvedImports","imp","results","edge","libName","dep","resolveOptions","graph","options","DefaultTestNodeIdentifier","detectFeatures","traverseAffected","changedFiles","featureMap","onFeatureHub","onNode","changed","startNode","context","SymbolTraversalContext","exportedSym","visitedNode","depth","childPath","feature","proposeTags","identifier","proposedTags","node","tag","proposeAffectedTests","affectedTests","import_node_fs","import_node_path","createImportMap","rootDir","entryPoints","previousGraph","options","progressCallback","count","GraphBuilder","path","getAllProjectFiles","rootDir","options","files","ignoreDirs","DEFAULT_IGNORE_DIRS","extensions","DEFAULT_EXTENSIONS","walk","dir","entries","fs","entry","fullPath","path","import_node_path","import_node_util","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","resolveRootDir","cliTokens","i","path","OPTIONS","STRING_FLAGS","v","k","sanitizeTokens","result","token","valueToken","parseArgs","rootDir","defaultCachePath","DEFAULT_CACHE_DIR","DEFAULT_CACHE_FILE","values","positionals","nodeParseArgs","featureThresholdRaw","minOutDegreeRaw","filterPathsRaw","cacheValue","configValue","pathStr","import_node_path","TEST_PATTERNS","getTestFiles","allFiles","filePath","base","path","pattern","resolveChangedFiles","rootDir","DefaultGitProvider","run","ctx","graph","rootDir","scanOptions","featureThreshold","changedFiles","resolveChangedFiles","node","getTestFiles","allFiles","getAllProjectFiles","createImportMap","affectedTests","proposeAffectedTests","run","ctx","graph","rootDir","entryPoints","eps","detectAllEntryPoints","surface","buildApiSurface","run","ctx","graph","rootDir","scanOptions","dryRun","plain","allFiles","getAllProjectFiles","createImportMap","getTestFiles","result","applyTags","run","ctx","graph","functionName","result","queryCallGraph","run","ctx","graph","file","plain","callers","run","ctx","graph","cycles","cycle","run","ctx","graph","rootDir","scanOptions","featureThreshold","allFiles","getAllProjectFiles","createImportMap","featureMap","detectFeatures","features","featureA","featureB","run","ctx","graph","rootDir","scanOptions","minOutDegree","allFiles","getAllProjectFiles","createImportMap","featureGraph","buildFeatureGraph","features","run","ctx","graph","featureThreshold","rawConfig","plain","threshold","uncovered","node","uncoveredEntry","import_node_path","TEST_PATH_PATTERNS","isTestPath","filePath","base","path","pattern","run","ctx","graph","rootDir","scanOptions","excludeTests","allProjectFiles","getAllProjectFiles","unusedFiles","run","ctx","graph","queryStr","mermaidOutput","serialized","query","parseQuery","filterGraph","filteredGraph","Graph","MermaidExporter","cycles","run","ctx","graph","filterPaths","minOutDegree","respGraph","buildResponsibilityGraph","modules","modulePath","run","ctx","graph","rootDir","scanOptions","featureThreshold","plain","changedFiles","resolveChangedFiles","allFiles","getAllProjectFiles","createImportMap","getTestFiles","tags","proposeTags","run","ctx","graph","typeFilter","typeGraph","buildTypeGraph","result","queryTypeGraph","types","import_node_path","resolveConfig","parsed","rootDir","entryPoints","cachePath","configPath","config","loadMokoshConfig","defaultCachePath","path","resolvedEntryPoints","resolvedCachePath","scanOptions","import_node_fs","import_node_path","loadGraphFromCache","cachePath","fs","raw","Graph","saveGraphToCache","graph","cacheDir","path","buildGraph","rootDir","entryPoints","cachedGraph","silent","gitStats","createImportMap","HELP_TEXT","QUERY_HELP_TEXT","run","argv","parsed","parseArgs","HELP_TEXT","QUERY_HELP_TEXT","config","resolveConfig","applyConfig","rootDir","resolvedEntryPoints","resolvedCachePath","scanOptions","proposeTags","plain","affectedTests","detectFeatures","findUnused","findUncovered","excludeTests","checkCycles","callers","file","silent","featureThreshold","queryStr","mermaidOutput","typeGraph","typeFilter","moduleResponsibility","filterPaths","minOutDegree","featureGraph","callGraph","functionName","apiSurface","applyTags","dryRun","autoScan","graph","loadGraphFromCache","Graph","buildGraph","saveGraphToCache","ctx","entryPath","flag","run","err"]}
|